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

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

  1. 封装动态链接库
    我们通过下面的例子来封装一个接口。这个接口会需要传递一个数据库。数据的传递,在C++中我们知道可以按照指针的方式、可以采用值传递的方式,也可以采用引用的方式。但是在Java(或者说JNA)中,只有采用值传递(byValue)和引用方式(ByReference)的方式来进行值的传递。对应于C++中的值传递和指针方式。
    我们在封装动态链接库的时候,把这两种方式都封装进去。下面是要使用到的结构体的头文件:
#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程序“,这个地方,对于报出这样的错误我也不能解释,但是的确是做到版本号一致就可以避免。
要做到版本一致,首先打开上方工具栏中的该下来,选择“配置管理器”,如下图:
JNA调用C++的相关点总结_第1张图片
在”配置管理器“的”活动平台“下面选择”新建“,如下所示:
JNA调用C++的相关点总结_第2张图片
会得到“新建”的窗口。其中第一个下拉选择X64,第二个“copy settings from”一定要选择””:
JNA调用C++的相关点总结_第3张图片
比如选择“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界面的动态链接库,上面也有不少雷。希望能够从中汲取教训,分享给大家。

你可能感兴趣的:(工作上的学习)