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",});
- }
- }
- typedef struct tagLoginInfo
- {
- USER_LOGIN_ID_INFO_S stUserLoginIDInfo;
- CHAR szOrgCode[48];
- CHAR szDomainName[64];
- ULONG_32 ulDomainType;
- }LOGIN_INFO_S;
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中使用呢? - 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之间的转换: - 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);
该方法申请了一段连续的内存。 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());
- }
联系客服