Java调用C++类的代码实现

646 阅读4分钟

Java调用C++类的代码实现

1、编写C++的程序

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

1.1、建立PrintProfile工程

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

工程如下图所示:

image.png

1.2、在src目录中编写一个常量定义头文件ConstDef.h

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 现在工程的结构变为:

image.png 1.3、在src目录中编写类定义头文件PrintProfile.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 现在工程的结构变为:

image.png 1.4、在src目录中编写类实现文件PrintProfile.cpp

PrintProfile.cpp文件的内容如下:

#include <string.h> #include <stdio.h> #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]=='\n')
{
	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); } 现在工程的结构变为:

image.png 1.5、对PrintProfile.h和PrintProfile.cpp实现的解释

考虑到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的函数。

1.6、在src目录中编写Makefile文件

现在,我们为编译做准备,制作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):(TARGETFILE): (OBJFILES) (CC)(CC) (INCLUDEDIRS) -g -shared -fPIC <o< -o @

obj/PrintProfile.o: src/PrintProfile.cpp (INCLUDEFILES)(INCLUDEFILES) (CC) (INCLUDEDIRS)csharedfPIC(INCLUDEDIRS) -c -shared -fPIC < -o $@ 现在工程的结构变为:

image.png 2、编译C++程序

2.1、将文件拷贝到Linux环境

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

image.png 2.2、执行编译

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

image.png 3、编写Java程序

3.1、建立Java工程

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

image.png 3.2、在JavaProject工程下建立SpringBoot项目call-cpp

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

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

org.springframework.boot spring-boot-starter org.projectlombok lombok true net.java.dev.jna jna 5.8.0 3.3、新建一个接口PrintProfile

接口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);

} 此时视图变为:

image.png 3.4、修改CallCppApplication类

修改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();
}

} 4、编译和运行

4.1、执行编译命令

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

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

4.2、拷贝程序到Linux

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

image.png 4.3、将libPrintProfile.so库文件拷贝到Java程序运行目录

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

image.png 4.4、执行程序

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

LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/call-cpp 然后执行Java程序,程序的运行情况如下:

image.png