JNA调用C++的相关点总结

来源:互联网 发布:传世数据库 编辑:程序博客网 时间:2024/06/10 09:18

最近的项目需要使用到Java调用C++写的动态链接库,所以了解了一下实现的方法。期间也踩了不少雷,甚至至今有些还不是很清楚,今天写出来,记录一下, 也希望大神能够给我解决一些未知的地方。
首先,思路是通过C++完成主要的任务处理部分,然后形成一个动态链接库。然后当Java的服务执行到特定条件下面的时候,调用该接口,启动C++的服务。期间,Java会将需要的参数都传递给该接口(参数的传递上面有雷)。

  1. 封装动态链接库
    我们通过下面的例子来封装一个接口。这个接口会需要传递一个数据库。数据的传递,在C++中我们知道可以按照指针的方式、可以采用值传递的方式,也可以采用引用的方式。但是在Java(或者说JNA)中,只有采用值传递(byValue)和引用方式(ByReference)的方式来进行值的传递。对应于C++中的值传递和指针方式。
    我们在封装动态链接库的时候,把这两种方式都封装进去。下面是要使用到的结构体的头文件:
#ifndef _H_STRUC_H#define _H_STRUC_H////#include <string>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 <Windows.h>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<String> getFieldOrder() {        // TODO Auto-generated method stub//      List<String> lst_str = new ArrayList<String>();//      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<String> 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界面的动态链接库,上面也有不少雷。希望能够从中汲取教训,分享给大家。

原创粉丝点击