Head First JNA

问题描述虚拟化项目,需要用到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 youngstruct { 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方法,当读取数据时,设置联合的类型为结构体类型。@Overridepublic void read() { super.read(); union.setType(HostStatusStruct.class); union.read();}怪事当时这个问题把我愁坏了,捯饬了一整天才学明白。数字一直是这个数字:-1073741824,这个数字是不是有什么问题?把-1073741824转换为32机的二进制表示:1100 0000 0000 0000 0000 0000 0000 00001073741824是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,自己从头到尾搭建一个项目骨架。最后表扬一下潘佳琦,上周基本我有一半的时间都在上课,我能做的就是前一天晚上把任务建好,然后写一些基础代码或示例代码,然后给潘佳琦讲,再让他去写。潘佳琦效率还是很高的,我记得周一的时候建了一堆任务,我想怎么着也得写两天吧,当我上课回来,发现“当当当”,潘佳琦都给写完了,代码也十分的规范。对小组员的开发效率在心中也有了一个重新的定位。 ...

March 1, 2019 · 3 min · jiezi

JNA 使用总结

引言新系统起步,带领两个可爱的小组员李宜衡、潘佳琦一起学习、讨论、编码。系统中使用了JNA调用C++代码。之前对这项技术也只是听说过,也没用到过。以前帮同学调试过调用百度地图SDK一个地图项目,那个项目中就用到了百度地图提供的.so文件(动态链接库,Linux下为.so,Windows下为.dll),只是当时用的时候是直接调百度封装好的代码,也没有手动调用的机会。参考潘老师给出的示例代码,再配合他人的博客,一步步完成功能。感谢开源,感谢如此之多热爱分享技术的人。本文可能有些枯燥,但JNA的例子好像也举不出什么有意思的。JNAC++在学校学过C++,也不陌生了,但是当我打开头文件时,才发现,原来我学的不叫C++!(考高分有什么用呢?)C++太伟大了,甚至连它的设计者都不敢说能完全掌握C++。其实去看看我们平常使用的类,原来我们和C++息息相关。JDK中就有一些使用C++实现的原生方法。C++的好处不必说,就是快。目前对性能要求很高的主流系统会采用C++或Go作为开发语言。这里我们使用并不是因为性能,而是需要通过调用动态链接库来使用已经编写好的基础服务。JNAJava Native Access,拥有4600多Star的流行项目,我们可以使用其进行使用Java对C++的访问。Github地址:JNA - Github这个README写的挺好的,就是太枯燥了(至少我是看着看着就困了),如果是首次接触的话,不建议通过README来入门。可以去看看有关这方面的博客,虽然年代久远,但质量都是非常高的。映射JNA最大的难点就是:C++和Java的数据类型不同,如何在两种语言之间进行映射。public interface CLibrary extends Library { CLibrary INSTANCE = Native.loadLibrary(“PSA5”, CLibrary.class);}编写接口,继承Library,然后加载动态链接库获取INSTANCE,该接口就与动态链接库进行了映射。结构体映射看了好多篇文章,找到了一种算是被大多数开发者推崇的写法:继承Structure表示映射结构体: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”); }}这里有两个和我们平常开发不同的地方:1.为什么属性是public的?这个也没找到原因,最开始使用private和get、set去映射时,一直报错,具体记不清了,应该是类似字段找不到的错误。应该是private的框架访问不到。2.这两个内部类是干什么的?一个ByReference,一个ByValue,应该能猜到,实现ByReference接口表示该类映射结构体的指针类型(指向结构体的指针),实现ByValue接口表示该类映射结构体的值类型(就是结构体)。所以实际使用的并不是HostStruct,而是他的两个内部类。例子某方法在头文件中如此声明:typedef PSA_STATUS (*LPFN_PSA_RebootHost)( IN PSA_HOST *psa_host );映射为如下的接口方法:NativeLong _PSA_RebootHost(HostStruct.ByReference host);小坑文档中的数据类型的表示非常好的,就是有一个缺陷,关于char *的问题。经过StackOverflow的参考与自己的亲身实践,只有const char *才能映射为String。普通的char *需要使用字节数组byte[]来实现,然后调用Native.toString()方法将字节数组转化为String。总结从看官方README,到放弃README看相关博客,到最后学会实现功能并在此总结,前后大概花了三天的时间。感谢官方的仓库,同时也感谢每一位分享技术的人。国家职业教育改革实施方案想到了学校的教育,在这里感慨一下几天前看到的新闻,国务院2月13日发布关于印发国家职业教育改革实施方案的通知。这么多年了,国家终于发现学校培养的和企业要的不是一回事。国家鼓励企业参与职业教育,2020年初步建设300个示范性职业教育集团。过去,是科教兴国。这么多年,这条路是对的,我国也在蓬勃发展,勇夺世界前列。科学,需要发展,但推动经济发展的,却是工程。一直以来,我始终坚信:既然存在于这世界,那每个人都是完美的,每个人都是平等的,只是教育资源短缺,所以非要拿分数来分个三六九等。来了河北工业大学,和衡水的学生比一比,我上的简直不叫高中。他们在晨读,我们还未起;他们在学习,我们在过周末。也羡慕过考上复旦的同学,看看人家的努力,一切都是公平的。为了弥补之前的遗憾,我一直努力着~加油!别放弃往昔的梦想,努努力,你我都可以! ...

February 17, 2019 · 1 min · jiezi