• Java工作笔记:工作中使用JNA调用C++库的一些细节(转载)


    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/zjutzmh/article/details/53574504
    1.调用本地接口:

    先看最基本的调用代码:

    public interface testFunction extends Library{

    testFunction INSTANCE = (testFunction) Native.loadLibrary(Platform.isWindows() ? "win_sdk" : "linux_sdk", testFunction.class);

    int SDK_init();
    int SDK_Login(byte[] szUserLoginName, byte[] szPassword, String szIpAddress, SDKStructure.LOGIN_INFO_S pstSDKLoginInfo);

    }
    调用C++  DLL库的接口时一般继承Library类就可以了,但如果DLL文件是用stdcall编译的话,DLL中的函数名变为“_functionname@number”这样的形式,所以添加继承这个类会提示Error looking up function 'SDK_init'。这时候就要继承StdCallLibrary这个类了。 
    可是!这里我想说,有时候 JNA就算继承StdCallLibrary类还是有可能提示找不到函数。我就遇到了,网上查了各种办法都没有解决(如果有人知道有效的解决办法还请告知啊)。于是我要求公司提供C库的程序员给我提供cdecl编译的库,我也写过C++代码,方法就是把cpp文件和h文件里面的__stdcall换成__cdecl。 
    当然linux下就没有出现过这样的问题,上面的例子中根据不同的操作系统调用不同名字的库。

    注意SDK_Login()这个函数,下面是C++的代码:


    __declspec( dllexport ) ULONG_32 STDCALL SDK_Login
    (
    IN CHAR szUserLoginName[IMOS_NAME_LEN],
    IN CHAR szPassword[IMOS_PASSWD_ENCRYPT_LEN],
    IN CHAR szIpAddress[IMOS_IPADDR_LEN],
    OUT LOGIN_INFO_S *pstSDKLoginInfo
    );
    是不是很奇怪,CHAR类型的变量,在转成java的时候,既有byte[]类型也有String类型?因为中文编码,当szUserLoginName变量为中文时(支持中文用户名登录),在传输过程中总会出现各种各样的问题,最后按照UTF8编码转换成byte[]类型,再往下传就没什么问题了。


    2.结构体和java间的转换: 
    同样先看代码:


    /**
    * @struct tagLoginInfo
    * @brief 用户登录信息结构体
    * @attention 无 */
    public static class LOGIN_INFO_S extends Structure {

    public USER_LOGIN_ID_INFO_S stUserLoginIDInfo= new USER_LOGIN_ID_INFO_S();
    public byte[] szOrgCode = new byte[48];
    public byte[] szDomainName = new byte[64];
    public int ulDomainType;

    public LOGIN_INFO_S() {
    }

    @Override
    protected List<String> getFieldOrder() {
    return Arrays.asList(new String[]{"stUserLoginIDInfo","szOrgCode","szDomainName","ulDomainType",});
    }
    }

    C++ 代码:

    typedef struct tagLoginInfo
    {
    USER_LOGIN_ID_INFO_S stUserLoginIDInfo;
    CHAR szOrgCode[48];
    CHAR szDomainName[64];
    ULONG_32 ulDomainType;
    }LOGIN_INFO_S;

    其中getFieldOrder()方法中的内容必填完整,字符串数字中的值必须和结构体中的成员名一一对应,成员必须是public。记住这三个必须,否则运行时会抛异常。这个是在jna 4.2.2中新加的,在jna3.*中没有强制要写这部分,但是在运行中也会出现问题,但是不会抛异常,然后一脸懵逼。。。 
    类型转换的时候,注意要把char换成byte[],声明并且new好长度。结构体中有结构体也要new好。为什么?留个念想,这个后面再说。 

    3.回调推送信息处理 
    回调相对比较复杂一点,先看C++代码的头文件中相关内容:


    typedef VOID (STDCALL *CALL_BACK_PROC_PF)( IN VOID *pParam);
    声明了一个函数指针。

    __declspec( dllexport ) ULONG_32 STDCALL IMOS_RegCallBackPrcFunc
    (
    IN ULONG_32 LoginID,
    IN CALL_BACK_PROC_PF pfnCallBackProc
    );
    这里不讨论C++回调的实现。我们只知道,我们需要通过该函数,获取C那边的推送消息就可以了。可以看出该函数的入参有一个函数指针。那么这种指针是怎么在java中使用呢? 
    Java的调法: 
    首先,在testFunction接口中加一个内部类接口和C++中的CALL_BACK_PROC_PF对应(入参类型使用jna自带的Pointer类)该接口继承jna库中的CallBack接口: 

    interface CALL_BACK_PROC_PF extends Callback {
    void invoke(Pointer pParam);
    }
    然后,继承该接口写一个新类来获取推送过来的信息(以告警相关的信息举例): 

    public static class AS_ALARMPUSH_UI_S extends Structure {
    public byte[] szAlarmSrcName = new byte[64];
    public byte[] szAlarmTime = new byte[32];
    public AS_ALARMPUSH_UI_S() {

    }
    @Override
    protected List<String> getFieldOrder() {
    return Arrays.asList(new String[]{"szAlarmSrcName","szAlarmTime"});
      }
    }
    最后调用:

    pfnCallBackProc = new SingleCallBackProcPF();
    ret = testFunction.INSTANCE.IMOS_RegCallBackPrcFunc(LoginID, pfnCallBackProc);
    if (0 != ret) {
    System.out.println(serverIP + ":" + serverPort + "]注册推送信息处理的回调函数失败,返回错误码:" + ret);
    return;
    }


    灵活处理C++和Java之间的转换: 

    Java和C++中都有用到指针的概念,只是Java对指针又重新封装了一层,让人基本感觉不到指针的存在。但是个人认为对指针的进一步理解,是对Java的学习很有帮助的,尤其是C++和Java之间转换这一块。 
    这就是之前为什么说要把char换成byte[],声明好长度。因为要保持C++和Java两边声明结构体的时候内存大小相等,这样每个成员变量的指针才能一一对应。 
    当遇到C++函数中的入参出现结构体数组,或者结构体中出现结构体数组的时候,使用常用的方法给数组中每一项new一下是错误的。 
    LOGIN_INFO_S [] stu = new LOGIN_INFO_S [3];
    for(int i = 0; i < 3; i ++)
    {
    stu[i] = new LOGIN_INFO_S ();

    这种方法是错误的,因为用这种方法new出来的内存不一定是连续的,到了C++那边就乱了。正确的方法是调用jna库中structure类的toArray方法: 

    LOGIN_INFO_S [] stu = new LOGIN_INFO_S [3];
    stu = (LOGIN_INFO_S [])(new LOGIN_INFO_S()).toArray(3);
    该方法申请了一段连续的内存。 
    也同样用指针的原理处理char[][]这种二维数组的情况,我在处理char[x][y]二维数组的时候,在Java方面的映射为byte[x*y]。看到这里明白了吧,只要值在数组中的位置放对了,这两个类型就没什么区别了。 


    Java中再做好相应的String到byte[]间的转换,就万事OK了(两种类型转换的时候记得加入中文编码类型哦,不然的话麻烦一连串啊)。附转换代码: 


    /**
    * @apiNote 给结构体中的字符串赋值
    */
    public static void setSdkBytes(byte[] dst, String content) {
    byte[] srcBytes = new byte[0];
    try {
    srcBytes = content.getBytes("utf-8");
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    }
    int size = Math.min(srcBytes.length, dst.length);
    System.arraycopy(srcBytes, 0, dst, 0, size == dst.length ? dst.length - 1 : size);
    }

    /**
    * @apiNote JSONArray转byte[]
    *
    * JSONArray转换为C代码中Char[x][y]
    * 既JSONArray -> byte[x*y] -> Char[x][y]
    */
    public static void byte2Copy(byte[] dst, JSONArray array, int x, int y){
    if(array.size() < x){
    x = array.size();
    }
    int f = 0;
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < x; i++){
    sb.insert(f, array.getString(i));
    f = f + y;
    }
    StringUtils.setSdkBytes(dst, sb.toString());
    }

    ————————————————
    版权声明:本文为CSDN博主「呆萌的我」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/zjutzmh/article/details/53574504

  • 相关阅读:
    PAT乙级题库“傻瓜”题解之跟奥巴马一起编程
    PAT乙级题库“傻瓜”题解之划拳
    PAT乙级题库“傻瓜”题解之数素数
    PAT乙级题库“傻瓜”题解之编程团体赛
    PAT乙级题库“傻瓜”题解之判断题
    PAT乙级题库“傻瓜”题解之输出PATest
    有始有终,后会无期。
    今日德语大学习
    【day
    [day 3] Deutsch Studie
  • 原文地址:https://www.cnblogs.com/zhongshiqiang/p/11720829.html
Copyright © 2020-2023  润新知