分享

Java第65讲——商用级Java调用C++类的代码实现

 新用户248587GZ 2022-02-15

Java第65讲——商用级Java调用C++类的代码实现

  昨天使用某大公司提供的开发库,发现该公司底层只有一套共享库(.so文件),但是却为客户提供了C++、Java、C Sharp、Python四套接口。

  根据项目需要,我们使用了其中的Java开发接口,使用该接口时,发现Java接口代码使用JNA技术,写得非常棒,因此产生了自己也模拟写一个的想法。

  现在,我们将这个想法付诸实现吧。

  (特此说明:由于供货商未提供共享库的源代码,因此我猜测共享库用C++语言开发实现,下面实现方案中的C++代码,纯属猜测和自创,欢迎感兴趣者、高水平者提供指导意见)

  现在,我们编写一个读取Linux操作系统中/etc/profile文件内容,并且打印出来的功能。

  这里使用Eclipse工具,建立一个PrintProfile工程,工程中包含src、obj、bin三个子目录,分别用于存储源代码、目标文件、最终输出的动态库文件。

  工程如下图所示:

  Java第65讲——商用级Java调用C++类的代码实现

  ConstDef.h文件的内容如下:

  #ifndef __CONST_DEF_H__

  #define __CONST_DEF_H__

  const int MAX_LINE_LENGTH=1024;

  const int MAX_PROGRAM_NAME_LENGTH=64;

  const int MAX_PROGRAM_VERSION_LENGTH=32;

  const char* PROGRAM_NAME="PrintProfile";

  const char* PROGRAM_VERSION="0.8.1";

  #endif//__CONST_DEF_H__

  现在工程的结构变为:

  Java第65讲——商用级Java调用C++类的代码实现

  PrintProfile.h文件的内容如下:

  #ifndef __PRINT_PROFILE_H__

  #define __PRINT_PROFILE_H__

  class PrintProfile

  {

  public:

  PrintProfile();

  int OpenProfile();

  int ReadLine(char* pcLine, int iMaxLineLength);

  void CloseProfile();

  void GetProgramName(char* pcProgramName, int iMaxProgramNameLength);

  void GetProgramVersion(char* pcProgramVersion, int iMaxProgramVersionLength);

  private:

  FILE* m_fpProfile;

  };

  extern "C" int OpenProfile();

  extern "C" int ReadLine(char* pcLine, int iMaxLineLength);

  extern "C" void CloseProfile();

  extern "C" void GetProgramName(char* pcProgramName, int iMaxProgramNameLength);

  extern "C" void GetProgramVersion(char* pcProgramVersion, int iMaxProgramVersionLength);

  #endif//__PRINT_PROFILE_H__

  现在工程的结构变为:

  Java第65讲——商用级Java调用C++类的代码实现

  PrintProfile.cpp文件的内容如下:

  #include

  #include

  #include "ConstDef.h"

  #include "PrintProfile.h"

  PrintProfile::PrintProfile()

  {

  m_fpProfile=NULL;

  }

  int PrintProfile::OpenProfile()

  {

  m_fpProfile=fopen("/etc/profile", "r");

  if (m_fpProfile==NULL)

  {

  return -1;

  }

  return 0;

  }

  int PrintProfile::ReadLine(char* pcLine, int iMaxLineLength)

  {

  if (m_fpProfile==NULL)

  {

  return -1;

  }

  if (feof(m_fpProfile))

  {

  return -1;

  }

  memset(pcLine, 0, iMaxLineLength);

  fgets(pcLine, iMaxLineLength-1, m_fpProfile);

  int iLineLen=strlen(pcLine);

  if (iLineLen > 0 && pcLine[iLineLen-1]=='

  ')

  {

  pcLine[iLineLen-1]='\0';

  }

  return 0;

  }

  void PrintProfile::CloseProfile()

  {

  fclose(m_fpProfile);

  m_fpProfile=NULL;

  }

  void PrintProfile::GetProgramName(char* pcProgramName, int iMaxProgramNameLength)

  {

  memset(pcProgramName, 0, iMaxProgramNameLength);

  strncpy(pcProgramName, PROGRAM_NAME, iMaxProgramNameLength-1);

  }

  void PrintProfile::GetProgramVersion(char* pcProgramVersion, int iMaxProgramVersionLength)

  {

  memset(pcProgramVersion, 0, iMaxProgramVersionLength);

  strncpy(pcProgramVersion, PROGRAM_VERSION, iMaxProgramVersionLength-1);

  }

  static PrintProfile s_PrintProfile;

  extern "C" int OpenProfile()

  {

  return s_PrintProfile.OpenProfile();

  }

  extern "C" int ReadLine(char* pcLine, int iMaxLineLength)

  {

  return s_PrintProfile.ReadLine(pcLine, iMaxLineLength);

  }

  extern "C" void CloseProfile()

  {

  s_PrintProfile.CloseProfile();

  }

  extern "C" void GetProgramName(char* pcProgramName, int iMaxProgramNameLength)

  {

  s_PrintProfile.GetProgramName(pcProgramName, iMaxProgramNameLength);

  }

  extern "C" void GetProgramVersion(char* pcProgramVersion, int iMaxProgramVersionLength)

  {

  s_PrintProfile.GetProgramVersion(pcProgramVersion, iMaxProgramVersionLength);

  }

  现在工程的结构变为:

  Java第65讲——商用级Java调用C++类的代码实现

  考虑到C++代码在编译后,将会生成C语言形式的函数,但是函数名特别长,不容易被使用,因此对于PrintProfile类的每个成员函数,都由一个全局的C语言函数来包装。

  例如,这是本文写完之后,使用nm命令,查询的动态库的导出函数名:

  #nm -D libPrintProfile.so

  00000000000014d2 T CloseProfile

  w __cxa_finalize

  U fclose

  U feof

  U fgets

  U fopen

  00000000000014e9 T GetProgramName

  0000000000001515 T GetProgramVersion

  w __gmon_start__

  w _ITM_deregisterTMCloneTable

  w _ITM_registerTMCloneTable

  U memset

  0000000000001491 T OpenProfile

  0000000000004088 D PROGRAM_NAME

  0000000000004090 D PROGRAM_VERSION

  00000000000014a7 T ReadLine

  U strlen

  U strncpy

  00000000000012b4 T _ZN12PrintProfile11OpenProfileEv

  00000000000013bc T _ZN12PrintProfile12CloseProfileEv

  00000000000013ea T _ZN12PrintProfile14GetProgramNameEPci

  000000000000143e T _ZN12PrintProfile17GetProgramVersionEPci

  00000000000012f8 T _ZN12PrintProfile8ReadLineEPci

  000000000000129a T _ZN12PrintProfileC1Ev

  000000000000129a T _ZN12PrintProfileC2Ev

  可以看到,对于PrintProfile类的OpenProfile成员函数,在动态库对外提供的接口中,变成了名为

  _ZN12PrintProfile11OpenProfileEv的函数。

  现在,我们为编译做准备,制作Makefile文件。Makefile文件的内容如下:

  CC=g++

  TARGETFILE=bin/libPrintProfile.so

  OBJFILES=obj/PrintProfile.o

  INCLUDEDIRS=-I src

  INCLUDEFILES=src/ConstDef.h \

  src/PrintProfile.h

  .PHONY: build

  build: $(TARGETFILE)

  @echo "build successfully."

  clean:

  rm -f obj/*.o

  $(TARGETFILE): $(OBJFILES)

  $(CC) $(INCLUDEDIRS) -g -shared -fPIC $< -o $@

  obj/PrintProfile.o: src/PrintProfile.cpp $(INCLUDEFILES)

  $(CC) $(INCLUDEDIRS) -c -shared -fPIC $< -o $@

  现在工程的结构变为:

  Java第65讲——商用级Java调用C++类的代码实现

  除了Eclipse生成的ject文件外,其它文件都拷贝到Linux:

  Java第65讲——商用级Java调用C++类的代码实现

  在Linux环境下,执行make命令,将生成libPrintProfile.so文件:

  Java第65讲——商用级Java调用C++类的代码实现

  使用IDEA开发工具,建立一个消费者社区空的JavaProject工程。建立后视图如下:

  Java第65讲——商用级Java调用C++类的代码实现

  在JavaProject下建立SpringBoot项目,名称为call-cpp,建立后视图如下:

  Java第65讲——商用级Java调用C++类的代码实现

  程序的pom.xml文件使用到了下面三个依赖:

  org.springframework.boot

  spring-boot-starter

  orgjectlombok

  lombok

  true

  net.java.dev.jna

  jna

  5.8.0

  接口PrintProfile的代码如下:

  package com.flying.call.cpp;

  import com.sun.jna.Library;

  public interface PrintProfile extends Library {

  public static final int MAX_LINE_LENGTH=1024;

  public static final int MAX_PROGRAM_NAME_LENGTH=64;

  public static final int MAX_PROGRAM_VERSION_LENGTH=32;

  int OpenProfile();

  int ReadLine(byte[] pcLine, int iMaxLineLength);

  void CloseProfile();

  void GetProgramName(byte[] pcProgramName, int iMaxProgramNameLength);

  void GetProgramVersion(byte[] pcProgramVersion, int iMaxProgramVersionLength);

  }

  此时视图变为:

  Java第65讲——商用级Java调用C++类的代码实现

  修改CallCppApplication类的代码,加入对PrintProfile的调用。修改后的代码如下:

  package com.flying.call.cpp;

  import com.sun.jna.Native;

  import lombok.extern.slf4j.Slf4j;

  import org.springframework.boot.SpringApplication;

  import org.springframework.boot.autoconfigure.SpringBootApplication;

  @Slf4j

  @SpringBootApplication

  public class CallCppApplication {

  //程序入口方法

  public static void main(String[] args) throws Exception{

  SpringApplication.run(CallCppApplication.class, args);

  PrintProfile printProfile=Native.load("PrintProfile", PrintProfile.class);

  byte[] programName=new byte[PrintProfile.MAX_PROGRAM_NAME_LENGTH];

  byte[] programVersion=new byte[PrintProfile.MAX_PROGRAM_VERSION_LENGTH];

  byte[] profileLine=new byte[PrintProfile.MAX_LINE_LENGTH];

  printProfile.GetProgramName(programName, PrintProfile.MAX_PROGRAM_NAME_LENGTH);

  printProfile.GetProgramVersion(programVersion, PrintProfile.MAX_PROGRAM_VERSION_LENGTH);

  String programNameString=new String(programName, "UTF-8");

  String programVersionString=new String(programVersion, "UTF-8");

  System.out.println("Program name: " + programNameString);

  System.out.println("Program version: " + programVersionString);

  if (printProfile.OpenProfile() !=0){

  System.err.println("Open profile failed.");

  return;

  }

  System.out.println("Content of /etc/profile is : ");

  while (printProfile.ReadLine(profileLine, PrintProfile.MAX_LINE_LENGTH)==0)

  {

  String profileLineString=new String(profileLine, "UTF-8");

  System.out.println(profileLineString);

  }

  printProfile.CloseProfile();

  }

  }

  执行mvn clean package -DskipTests命令,完成编译:

  Java第65讲——商用级Java调用C++类的代码实现

  编译完成后,将会得到target目录。

  将target目录中的cpp_call_lib目录、resources目录、cpp-call.jar文件拷贝到Linux:

  Java第65讲——商用级Java调用C++类的代码实现

  将libPrintProfile.so库文件拷贝到Java程序运行目录,拷贝后目录情况如下:

  Java第65讲——商用级Java调用C++类的代码实现

  首先执行下面的命令,将动态库的目录加入到LD_LIBRARY_PATH环境变量:

  LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/call-cpp

  然后执行Java程序,程序的运行情况如下:

  Java第65讲——商用级Java调用C++类的代码实现

  后记,编写这个例子程序,花了我3小时的时间,但是最终确实让Java调用C++类的成员函数提供的功能时,就像直接调用Java方法一样,感觉非常方便。

  前段时间研究JavaCV时,发现JavaCPP也提供了类似的功能,以后有空,看能不能分析一下,也写一篇文章,共享给大家。

  谢谢大家!

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多