昨天使用某大公司提供的开发库,发现该公司底层只有一套共享库(.so文件),但是却为客户提供了C++、Java、C Sharp、Python四套接口。 根据项目需要,我们使用了其中的Java开发接口,使用该接口时,发现Java接口代码使用JNA技术,写得非常棒,因此产生了自己也模拟写一个的想法。 现在,我们将这个想法付诸实现吧。 (特此说明:由于供货商未提供共享库的源代码,因此我猜测共享库用C++语言开发实现,下面实现方案中的C++代码,纯属猜测和自创,欢迎感兴趣者、高水平者提供指导意见) 现在,我们编写一个读取Linux操作系统中/etc/profile文件内容,并且打印出来的功能。 这里使用Eclipse工具,建立一个PrintProfile工程,工程中包含src、obj、bin三个子目录,分别用于存储源代码、目标文件、最终输出的动态库文件。 工程如下图所示:
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__ 现在工程的结构变为:
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__ 现在工程的结构变为:
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); } 现在工程的结构变为:
考虑到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 $@ 现在工程的结构变为:
除了Eclipse生成的ject文件外,其它文件都拷贝到Linux:
在Linux环境下,执行make命令,将生成libPrintProfile.so文件:
使用IDEA开发工具,建立一个消费者社区空的JavaProject工程。建立后视图如下:
在JavaProject下建立SpringBoot项目,名称为call-cpp,建立后视图如下:
程序的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); } 此时视图变为:
修改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命令,完成编译:
编译完成后,将会得到target目录。 将target目录中的cpp_call_lib目录、resources目录、cpp-call.jar文件拷贝到Linux:
将libPrintProfile.so库文件拷贝到Java程序运行目录,拷贝后目录情况如下:
首先执行下面的命令,将动态库的目录加入到LD_LIBRARY_PATH环境变量: LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/call-cpp 然后执行Java程序,程序的运行情况如下:
后记,编写这个例子程序,花了我3小时的时间,但是最终确实让Java调用C++类的成员函数提供的功能时,就像直接调用Java方法一样,感觉非常方便。 前段时间研究JavaCV时,发现JavaCPP也提供了类似的功能,以后有空,看能不能分析一下,也写一篇文章,共享给大家。 谢谢大家! |
|
来自: 新用户248587GZ > 《待分类》