最近的项目需要使用到Java调用C++写的动态链接库,所以了解了一下实现的方法。期间也踩了不少雷,甚至至今有些还不是很清楚,今天写出来,记录一下, 也希望大神能够给我解决一些未知的地方。
首先,思路是通过C++完成主要的任务处理部分,然后形成一个动态链接库。然后当Java的服务执行到特定条件下面的时候,调用该接口,启动C++的服务。期间,Java会将需要的参数都传递给该接口(参数的传递上面有雷)。
#ifndef _H_STRUC_H
#define _H_STRUC_H
////#include
struct stu
{
char* name;
int age;
int score;
};
#endif
在结构体中,我们装进去一个char指针,两个整型数据。其中的整型数据类型在Java中石油对应的类型。但是char*,尤其是指针类型的数据,在Java中是不存在的(Java中的指针需要使用intPointer?)。通过查找相关的资料,Java中的String类型是对应于char指针指示的字符串的。
上面降到的数据类型的映射关系十分的重要,比如在一开始的时候,我以为Java中的String类型和C++中的std::string是对应的,但是却发现调试的时候会造成Java在调用接口的时候爆出“Invalid memory access”的错误。同样,char*不能够使用具有相似性质的char数组来代替,也会报出错误来。
下面是头文件。
#include "stru.h"
#include
void mesBox(void);
extern "C" __declspec(dllexport) void mesBox1(void);
extern "C" __declspec(dllexport) void mesBox2(struct stu *st);
extern "C" __declspec(dllexport) void mesBox3(struct stu st);
extern "C" __declspec(dllexport) void mesBox4(int a);
头文件中需要注意的就是记得带上extern "C" __declspec(dllexport)
。前面的extern "C"
是能够保障生成和声明的函数名字保持一致。后面部分声明为导出函数。
下面是实现的部分。其实C++部分在头文件确定之后基本上没有什么问题。由于几个函数具有较大的相似性,所以只贴出其中一个。
#include "stru.h"
#include "dllHeader.h"
extern "C" __declspec(dllexport) void mesBox1(void)
{
stu *st = new stu;
st->age = 25;
st->score = 97;
memset(st->name, 0, 64);
memcpy(st->name,"caojintaoo", 11);
//stu st;
//st.age = 25;
//st.score = 97;
//st.name = "caojintao";
std::string str = std::string(st->name);
char buf[16];
memset(buf, 0, 16);
_itoa(st->age, buf, 10);
str = str + ", " + std::string(buf);
memset(buf, 0, 16);
_itoa(st->score, buf, 10);
str = str + " : " + std::string(buf);
delete st;
::MessageBoxA(NULL, str.c_str(), "Your Info", MB_OK);
}
上面的就是C++部分的编码。编码完成之后需要生成dll文件,需要配置项目,配置项目属性的时候有几个地方需要格外注意。
1、首先是需要针对Java的版本,配置dll的位数。比如,我自己的JDK是64位的,那么生成的dll就必须是64位。否则会在调用完成之后报出“不是正确的win32程序“,这个地方,对于报出这样的错误我也不能解释,但是的确是做到版本号一致就可以避免。
要做到版本一致,首先打开上方工具栏中的该下来,选择“配置管理器”,如下图:
在”配置管理器“的”活动平台“下面选择”新建“,如下所示:
会得到“新建”的窗口。其中第一个下拉选择X64,第二个“copy settings from”一定要选择””:
比如选择“win32”下面可能混入不确定的设置,最终导致无法生成匹配的库文件。
上面的版本对应问题困扰了我很久,之后我会把设置的截图贴上来,更加直观。
下面的部分是在Java中建立项目,并且调用的过程。后期在发上来。
完成了在C++中的工程创建,需要在Java中进行编程来进行调用动态链接库。这部分主要有三个要点:1、编写对应于C中的结构体;2、编写对应于动态链接库的Java接口类;3、动态链接库文件放置的位置。
1、Java中的结构体:前面讲到过,Java中的结构体传递的时候,可以采用值传递和引用传递。两种方案都是采用声明类继承(extends)结构体(Structure),然后采用(implements)各自的方案(Structure.ByValue和Structure.ByValue)。
package testDllTest;////类所在的包名
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;/////结构体所需要用到的JNA的Structure
//////下面的类生命之后表明了采用何种方式进行传递
public class StudentVal extends Structure implements Structure.ByValue{
public String name;
public int age;
public int score;////三个成员
@Override
///// 调用该函数能够返回一个list,依次是成员的名称。这里的顺序错误可能会导致赋值的错误
protected List getFieldOrder() {
// TODO Auto-generated method stub
// List lst_str = new ArrayList();
// lst_str.add("name");
// lst_str.add("age");
// lst_str.add("score");///这种方法也可以
return Arrays.asList(new String[]{"name","age","score"});
}
}
package testDllTest;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
public class StudentRef extends Structure implements Structure.ByReference{
public String name;
public int age;
public int score;
@Override
protected List getFieldOrder() {
// TODO Auto-generated method stub
return Arrays.asList(new String[] {"name","age","score"});
}
}
2、编写接口类。完成了Java的结构体之后,就需要编写一个接口,来加载动态链接库,同时将链接库的函数再声明一次。
package testDllTest;
import com.sun.jna.Platform;
import testDllTest.StudentRef;
import testDllTest.StudentVal;
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface Java_C_IF extends Library{
Java_C_IF jci = (Java_C_IF)Native.loadLibrary(Platform.isWindows()?"dllTest1":"c",Java_C_IF.class);
public void dlgShow();
public void mesBox1();
public void mesBox2(StudentRef st);
public void mesBox3(StudentVal st);
public void mesBox4(int a);
}
上面的接口类中,直接通过native.loadLibrary加载动态链接库。但是我们看到,在这个加载的过程中,并没有指明动态链接库的位置。正是因为如此,才对后面造成了大量的问题。但是如果我加上了路径,然后在路径下面放上了动态链接库的dll文件,也不能加载成功。这一点着实让我不能明白,也希望有大神能够指导一下。
3、在完成了加载之后,就是调用的问题了。调用的方式仔也比较古怪。按理说,上面的接口中,方法(函数)是类的成员,适合st(成员变量)平等地位的。但是调用的时候,会发现,方法是成员变量的成员,而不是类的成员了。
import testDllTest.StudentRef;
import testDllTest.StudentVal;;
public class Start {
public static void main(String args[]) {
// System.out.println(0);
///这个直接调用会出问题,会涉及到访问未申请空间,
///所以 报invalid memory access的问题
// Java_C_IF.jci.mesBox1();
// StudentRef stuRef = new StudentRef();
// stuRef.name = "caojintao";
// stuRef.age = 25;
// stuRef.score = 97;
// System.out.println(stuRef);
// Java_C_IF.jci.mesBox2(stuRef);
//
StudentVal stuVal = new StudentVal();
stuVal.name = "caojint0 o";
stuVal.age = 25;
stuVal.score = 97;
Java_C_IF.jci.mesBox3(stuVal);
Java_C_IF.jci.mesBox4(16);
// Java_C_IF.jci.dlgShow();
}
}
至此,Java利用JNA技术调用C++的动态链接库的问题,大致就展开完了。一些容易出错的地方,我根据自己的教训,也作出了标记,希望在以后能够减少在这上面犯错。
但是,上面看到的只是最简单的调用。下一期的博客中,将会测试一下被调用的dll中带有MFC界面的动态链接库,上面也有不少雷。希望能够从中汲取教训,分享给大家。