Head First JNA

70次阅读

共计 5938 个字符,预计需要花费 15 分钟才能阅读完成。

问题描述
虚拟化项目,需要用到 Java 调用原生代码的技术,我们使用的是开源库 JNA(Java Native Access)。
Native(C/C++) 代码,编译生成动态链接库 Dynamic-link library。
在 Windows 下常见的.dll 文件。这是我们项目中用到的动态链接库。

而在 unix 环境下,为.so 文件。这是百度地图的动态链接库。

与动态链接库配套的,会有相应的头文件,来声明动态链接库中对外暴露的方法。

百度地图是直接封装好,给了.so,但是不给头文件,直接把写好的 jar 包给你,直接调用就行。

之前也是用过百度地图的 SDK,现在自己手写代码调用动态链接库才明白,原来之前用的都是别人封装好的,如今自己参照头文件手写,感觉理解还是深刻了不少。
入门
待解决的问题
我们使用 JNA,主要是去调用动态链接库中已经实现的方法,所以要解决的问题就是:如何在 Java 代码中调用动态链接库的方法?
打开头文件,这个方法要求传输的数据是指针,而 Java 是没有指针的,另一个问题:Java 数据类型与 C /C++ 的数据类型如何映射?

方法映射
打开 JNA 的官方 README,点击 Getting Started。
直接看代码,里面的 sample,入门足够了。

定义接口,继承 Library。
定义该接口的一个实例,加载动态链接库。
在接口中声明方法,该方法需要与原生代码方法声明一致。
然后该方法就映射到了原生的方法,我们只需调用该接口中的方法,即可调用到原生的对应方法。

// This is the standard, stable way of mapping, which supports extensive
// customization and mapping of Java to native types.

public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)
Native.load((Platform.isWindows() ? “msvcrt” : “c”),
CLibrary.class);

void printf(String format, Object… args);
}
类型映射
默认类型映射
这是官方 README 中给的类型映射。
学习与实践的区别,看着这个表格感觉挺简单的,实际用起来真难。

结构体映射
结构体 PSA_HOST:
typedef struct {
char name[33];
DWORD context;
} PSA_HOST;
映射类 HostStruct:

编写类 HostStruct,继承 Structure,表示这个一个结构体。
声明字段 name 与 context,并且设置访问属性为 public。

重写 getFieldOrder 方法,表示本类中各字段以何顺序映射原生结构体。

/**
* @author zhangxishuo on 2019-02-16
* 结构体 PSA_HOST
*/
public class HostStruct extends Structure {

public byte[] name = new byte[33];

public int context;

@Override
protected List<String> getFieldOrder() {
return Arrays.asList(“name”, “context”);
}
}
注意
const char * 才能映射为 String 类型。
而 char * 只能映射为 byte 数组,然后使用 Native.toString() 方法将 byte 数组转换为 String。
方法映射
typedef PSA_STATUS (*LPFN_PSA_ShutdownHost)(PSA_LOGON_HANDLE *handle, IN PSA_HOST *psa_host);
参数中需要 PSA_HOST 结构体的指针。
参考了好多篇文章,最常用的就是下面这种写法。写静态内部类,内部类实现 ByReference 与 ByValue 接口,分别表示映射指针,与映射值。
/**
* @author zhangxishuo on 2019-02-16
* 结构体 PSA_HOST
*/
public class HostStruct extends Structure {

/**
* 结构体指针
*/
public static class ByReference extends HostStruct implements Structure.ByReference {
}

/**
* 结构体具体的值
*/
public static class ByValue extends HostStruct implements Structure.ByValue {
}

public byte[] name = new byte[33];

public int context;

@Override
protected List<String> getFieldOrder() {
return Arrays.asList(“name”, “context”);
}
}
映射
/**
* 关闭计算机
* @param pointerByReference 认证 pointer
* @param host 主机指针
* @return 参见枚举类 PsaStatus
*/
NativeLong _PSA_ShutdownHost(PointerByReference pointerByReference,
HostStruct.ByReference host);
复杂类型
打起精神,重点来了!
开发过程中,有这样一种复杂的数据结构,嵌套关系比较复杂。
typedef struct {
union {
DWORD flags;
struct {
DWORD rev:23;
DWORD copy_status:3;
DWORD timeout:1;
DWORD disconnect:1;
DWORD rev1:1;
DWORD os_logoned:1;
DWORD logoned:1;
DWORD online:1;
};
};
} PSA_HOST_STATUS_FLAGS;

知识不用就忘,谁还记得 C 语言里的联合是啥?
Too young
struct {
DWORD rev:23;
DWORD copy_status:3;
DWORD timeout:1;
DWORD disconnect:1;
DWORD rev1:1;
DWORD os_logoned:1;
DWORD logoned:1;
DWORD online:1;
};
DWORD 就是 int,先映射里面的结构体。
把这些属性一写,然后再重写 getFieldOrder 方法。
/**
* @author zhangxishuo on 2019-02-26
* 计算机状态结构体
*/
public class HostStatusStruct extends Structure {

/**
* 结构体指针
*/
public static class ByReference extends HostStatusStruct implements Structure.ByReference {
}

/**
* 结构体具体的值
*/
public static class ByValue extends HostStatusStruct implements Structure.ByValue {
}

public int rev;

public int copy_status;

public int timeout;

public int disconnect;

public int rev1;

public int os_logoned;

public int logoned;

public int online;

@Override
protected List<String> getFieldOrder() {
return Arrays.asList(“rev”, “copy_status”, “timeout”, “disconnect”, “rev1”, “os_logoned”, “logoned”, “online”);
}
}
union {
DWORD flags;
struct {
DWORD rev:23;
DWORD copy_status:3;
DWORD timeout:1;
DWORD disconnect:1;
DWORD rev1:1;
DWORD os_logoned:1;
DWORD logoned:1;
DWORD online:1;
};
};
然后再映射联合,编写一个类继承 Union,该类即映射到联合。
/**
* 联合
*/
public static class UNION extends Union {
public int flags;
public HostStatusStruct hostStatusStruct;
}
最后映射最外层的 PSA_HOST_STATUS_FLAGS。
/**
* @author zhangxishuo on 2019-02-26
* 结构体 PSA_HOST_STATUS_FLAGS
*/
public class HostStatusFlagsStruct extends Structure {

/**
* 联合
*/
public static class UNION extends Union {
public int flags;
public HostStatusStruct hostStatusStruct;
}

/**
* 结构体指针
*/
public static class ByReference extends HostStatusFlagsStruct implements Structure.ByReference {
}

/**
* 结构体具体的值
*/
public static class ByValue extends HostStatusFlagsStruct implements Structure.ByValue {
}

public UNION union;

@Override
protected List<String> getFieldOrder() {
return Collections.singletonList(“union”);
}
}
看上去好像没什么毛病,一切都这么简单吗?当然不是。
-1073741824
一调用,就炸了。

看 API 文档的声明,flags 应该是没什么具体含义的,而结构体中应该是我们想要获取的信息。
那为什么 flags 有数,而结构体中却没有值呢?
联合
struct TEST_STRUCT {
int a,
int b
};

union TEST_UNION {
int a,
int b
};

声明映射类型
重写 read 方法,当读取数据时,设置联合的类型为结构体类型。
@Override
public void read() {
super.read();
union.setType(HostStatusStruct.class);
union.read();
}
怪事

当时这个问题把我愁坏了,捯饬了一整天才学明白。
数字
一直是这个数字:-1073741824,这个数字是不是有什么问题?
把 -1073741824 转换为 32 机的二进制表示:
1100 0000 0000 0000 0000 0000 0000 0000
1073741824 是 2 的 30 次方。
问题
问题还是出在这个上:
struct {
DWORD rev:23;
DWORD copy_status:3;
DWORD timeout:1;
DWORD disconnect:1;
DWORD rev1:1;
DWORD os_logoned:1;
DWORD logoned:1;
DWORD online:1;
};
虽然 DWORD 就映射为 int,但这里不是简单的映射:
rev:23 表示 rev 占 32 位。
copy_status:3 表示 copy_status 占 3 位。
timeout:1 表示 timeout 占 1 位。
内存是倒着存的,所以数据应该是这样。

映射
原理知道了,那怎么映射呢?怎么映射一个一位的内容呢?

StackOverflow 上一老哥给出了解决方案,先写个 int 把所有的都映射过来,然后我想要第几位再从里面扒。

/**
* @author zhangxishuo on 2019-02-26
* 计算机状态结构体
*/
public class HostStatusStruct extends Structure {

/**
* 结构体指针
*/
public static class ByReference extends HostStatusStruct implements Structure.ByReference {
}

/**
* 结构体具体的值
*/
public static class ByValue extends HostStatusStruct implements Structure.ByValue {
}

public int value;

public int getRev() {
return value & 0x3FFFFF;
}

public int getCopyStatus() {
return (value >> 23) & 0x3;
}

public int getTimeout() {
return (value >> 26) & 0x1;
}

public int getDisconnect() {
return (value >> 27) & 0x1;
}

public int getRev1() {
return (value >> 28) & 0x1;
}

public int getOsLogoned() {
return (value >> 29) & 0x1;
}

public int getLogoned() {
return (value >> 30) & 0x1;
}

public int getOnline() {
return (value >> 31) & 0x1;
}

@Override
protected List<String> getFieldOrder() {
return Collections.singletonList(“value”);
}
}
至此,功能完成。
总结
又过去了忙碌的一周,很高兴我们的新项目已经完成大半。
感谢潘佳琦与李宜衡在本项目中的支持,第一次用 Angular,好多地方我也不懂,我先学着,然后设计一套架构付诸实践,潘佳琦与李宜衡也都能遵从我制定的规范。
起初,我也提出了许多错误的规范,但当我用着用着发现原来那套不行的时候,及时改正,修改架构再重新设计,潘佳琦与李宜衡也在前台经历了大约三次的代码重构。

前台架构变更多次,感觉最后的设计还让人满意,也能让他人快速理解这种设计理念。
争取下一个项目,不使用 ng-alain,自己从头到尾搭建一个项目骨架。
最后表扬一下潘佳琦,上周基本我有一半的时间都在上课,我能做的就是前一天晚上把任务建好,然后写一些基础代码或示例代码,然后给潘佳琦讲,再让他去写。
潘佳琦效率还是很高的,我记得周一的时候建了一堆任务,我想怎么着也得写两天吧,当我上课回来,发现“当当当”,潘佳琦都给写完了,代码也十分的规范。
对小组员的开发效率在心中也有了一个重新的定位。

正文完
 0