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
Java工作笔记:工作中使用JNA调用C++库的一些细节(转载)的更多相关文章
- JAVA学习笔记--方法中的参数调用是引用调用or值调用
文献来源:<JAVA核心技术卷Ⅰ>,第4章第5节 (没有相关书籍的可看传送门) ps:测试demo因为偷懒,用的是String对象 结论:Java使用的是对象的值引用.即将任何对象所在内存 ...
- [笔记]Python中模块互相调用的例子
python中模块互相调用容易出错,经常是在本地路径下工作正常,切换到其他路径来调用,就各种模块找不到了. 解决方法是通过__file__定位当前文件的真实路径,再通过sys.path.append( ...
- java学习笔记-继承中super关键字
背景: 在java继承的概念中我们得知,被声明为私有的类成员对所属的类来说仍然是私有的.类之外的任何代码都不能访问,包括子类. super关键字的两种用法: 1.用于调用超类的构造函数: 2.用于访问 ...
- Linux下JNA 调用 so 库
原文:https://blog.csdn.net/withiter/article/details/8077470 博文链接:https://i.cnblogs.com/EditPosts.aspx? ...
- android调用第三方库——第二篇——编写库android程序直接调用第三方库libhello.so (转载)
转自:http://blog.csdn.net/jiuyueguang/article/details/9449737 版权声明:本文为博主原创文章,未经博主允许不得转载. 0:前言 1:本文主要作为 ...
- Java使用JNA调用DLL库
Java调用DLL方法有三种,JNI.JNA.JNative, 本文为JNA JNA为使用jna.jar包,下载地址:http://www.java2s.com/Code/Jar/j/Download ...
- Java学习笔记--HashMap中使用object做key的问题【转】
在HashMap中,如果需要使用多个属性组合作为key,可以将这几个属性组合成一个对象作为key.但是存在的问题是,要做get时,往往没办法保存当初put操作时的key object的referenc ...
- java继承的对象中构造函数的调用顺序
建立两个继承关系的对象 public class Machine { public String machieNameString; public Machine() { System.out.pri ...
- Java学习笔记-----eclipse中建立Java项目并成功运行
环境:WIN7 64位 +eclipse 2018 12version 具体方法:https://jingyan.baidu.com/album/9c69d48fefa53113c9024eb3.ht ...
随机推荐
- 第四篇:python基础之杂货铺
在这一篇中我们将对上几篇的Python零碎的知识进行补充,即字符串的格式化输出,以及深浅拷贝,接下来我们将对这两种进行一一介绍. 一.字符串格式化输出 关于字符串的格式化输出,我们需要了解为什么需要字 ...
- WindowsPE
什么是WindowsPE Windows Preinstallation Environment(Windows PE),Windows预引导环境,是带有有限服务的最小Win32子系统,基于以保护模式 ...
- P2458 [SDOI2006]保安站岗[树形dp]
题目描述 五一来临,某地下超市为了便于疏通和指挥密集的人员和车辆,以免造成超市内的混乱和拥挤,准备临时从外单位调用部分保安来维持交通秩序. 已知整个地下超市的所有通道呈一棵树的形状:某些通道之间可以互 ...
- mailto标签来调用邮箱客户端
最近项目需要使用mailto标签来调用客户端,并且把邮件模板填到客户端. mailto 的用法: a标签直接调用: <a href="mailto:example@qq.com?cc= ...
- Springboot与ActiveMQ、Solr、Redis中分布式事物的初步探索
Springboot与ActiveMQ.Solr.Redis中分布式事物的初步探索 解决的场景:事物中的异步问题,当要求数据库与solr服务器的最终一致时. 程序条件: 利用消息队列,当数据库添加成功 ...
- 表单中submit和button按钮的区别!
对于表单的按钮以前知道submit和button有区别,但没有深入探索,今天刚好又碰到这个问题,看了下网络上这位朋友已经有现成的总结了,而且比较到位,拿来跟大家分享下(原文地址:http://blog ...
- Selenium常用API的使用java语言之19-调用JavaScript代码
虽然WebDriver提供了操作浏览器的前进和后退方法,但对于浏览器滚动条并没有提供相应的操作方法.在这种情况下,就可以借助JavaScript来控制浏览器的滚动条.WebDriver提供了execu ...
- 淘宝上的大智慧L2数据,月卡最便宜是8元钱,这个也可以获取BBD、DDX等数据!
Want:从顶牛股网上下载DDX数据. 1.下载历史DDE数据:获取最近120个交易日的DDE数据 #define SFURL_DNG_SINGLEL"http://www.dingniug ...
- Centos7 安装相关软件
1.安装 wget : yum -y install wget
- 工作流学习之--TPFlow数据库分析
一.TPFlow项目数据库表: 1. 流程相关: a. leipi_flow工作流表: b. leipi_flow_process流程步骤表: c. leipi_run_process运行过程表:记录 ...