当前位置: 首页 > 后端技术 > Java

Java进阶用法:JNA中的Structure

时间:2023-04-01 18:32:10 Java

简介前面我们讲了JNA中JAVA代码和native代码的映射。虽然TypeMapper可以用来映射JAVA中的类型和native中的类型,但是native中的数据类型都是基本类型。如果native中的数据类型是一个复杂的struct类型,应该如何映射呢?不要害怕,JNA提供了Structure类来帮助我们完成这些映射过程。native中的struct什么时候会用到struct?一般来说,当我们需要自定义一个数据类的时候,一般情况下,我们需要在JAVA中定义一个类(在JDK17中,可以使用更简单的记录来代替),但是为一个数据结构定义一个类显然是一个有点臃肿,所以在母语中,有一些更简单的数据结构,称为结构。我们先来看一个struct的定义:typedefstruct_Point{intx,y;}Point;在上面的代码中,我们定义了一个Pointerstruct数据类,其中我们定义了int的x和y值来表示PointY轴的水平位置。struct的使用有两种情况,一种是值传递,一种是引用传递。我们先看看这两种情况在native方法中是如何使用的:按引用传递:Point*translate(Point*pt,intdx,intdy);按值传递:Pointtranslate(Pointpt,intdx,intdy);Structure那么在native方法中我们应该如何映射struct数据类型的使用呢?JNA为我们提供了Structure类。默认情况下,如果使用Structure作为参数或返回值,则映射为struct*;如果它表示Structure中的字段,则映射为struct。当然你也可以强制使用Structure.ByReference或者Structure.ByValue来表示是引用传递还是值传递。我们看一下上面原生的例子,如果使用JNA的Structure进行映射是如何实现的:Pointermapping:classPointextendsStructure{publicintx,y;}Pointtranslate(Pointpt,intx,inty);...Pointpt=newPoint();Pointresult=translate(pt,100,100);传递值映射:classPointextendsStructure{publicstaticclassByValueextendsPointimplementsStructure.ByValue{}publicintx,y;}Point.ByValuetranslate(Point.ByValuept,intx,inty);...Point.ByValuept=newPoint.ByValue();Pointresult=translate(pt,100,100);Structure提供了两个接口,分别是ByValue和ByReference:publicabstractclassStructure{publicinterfaceByValue{}publicinterfaceByReference{}要使用,需要继承相应的接口。除了上面提到的按值传递或按引用传递结构之外,特殊类型的结构还有其他更复杂的结构用途。作为参数的结构体数组首先,我们来看一下作为参数的结构体数组:voidget_devices(structDevice[],intsize);对应一个结构体数组,可以直接使用JNA中对应的结构体数组进行映射:intsize=...Device[]devices=newDevice[size];lib.get_devices(设备,设备长度);结构体数组作为返回值如果native方法返回的是一个结构体指针,本质上就是一个结构体数组,我们该如何处理呢?先看native方法的定义:structDisplay*get_displays(int*pcount);voidfree_displays(structDisplay*displays);get_displays方法返回一个指向结构体数组的指针,pcount是结构体的数量。对应的JAVA代码如下:Displayget_displays(IntByReferencepcount);voidfree_displays(Display[]displays);对于第一种方法,我们只返回一个Display,但是可以通过Structure.toArray(int)方法将其转换成一个结构数组。传递给第二种方法,具体调用方法如下:IntByReferencepcount=newIntByReference();Displayd=lib.get_displays(pcount);Display[]displays=(Display[])d.toArray(pcount.getValue());...lib.free_displays(显示器);结构中的结构也可以嵌入结构中,先看native方法的定义:typedefstruct_Point{intx,y;}Point;typedefstruct_Line{Pointstart;点结束;}线;对应的JAVA代码如下:classPointextendsStructure{publicintx,y;}classLineextendsStructure{publicPointstart;publicPointend;}如果是下面的结构体中的结构体指针:typedefstruct_Line2{Point*p1;点*p2;}线2;那么对应的代码如下:publicPoint.ByReferencep2;}或者直接用Pointer作为Structure的属性值:classLine2extendsStructure{publicPointerp1;publicPointerp2;}Line2line2;Pointp1,p2;...line2.p1=p1.getPointer();line2.p2=p2.getPointer();结构主体中的数组如果结构具有固定大小的数组:typedefstruct_Buffer{charbuf1[32];charbuf2[1024];}缓冲区;那么我们需要在JAVA中指定数据的大小:classBufferextendsStructure{publicbyte[]buf1=newbyte[32];publicbyte[]buf2=newbyte[1024];}如果结构是一个动态大小的数组:typedefstruct_Header{intflags;intbuf_length;字符缓冲区[1];}标题;那么我们需要在JAVA结构中定义一个构造函数,传入bufferSize的大小,并分配相应的内存空间:classHeaderextendsStructure{publicintflags;公共intbuf_length;公共字节[]缓冲区;publicHeader(intbufferSize){buffer=newbyte[bufferSize];buf_length=buffer.length;分配内存();}}结构体中的变量字段默认情况下,结构体中的内容与本机内存中的内容一致JNA会在函数调用前将结构体的内容写入本机内存,并将本机内存的内容写回函数调用后的结构。默认是写入结构中的所有字段。但在某些情况下,我们希望某些字段不自动更新。这时候可以使用volatile关键字,如下所示:classDataextendscom.sun.jna.Structure{publicvolatileintrefCount;publicintvalue;}...数据data=newData();当然也可以强制使用Structure.writeField(String)将字段信息写入内存,或者使用Structure.read()更新整个结构的信息或者使用data.readField("refCount")更新特定字段信息。结构体中的只读字段如果不想从JAVA代码中修改结构体的内容,可以将相应的字段标记为final。这种情况下,JAVA代码虽然不能直接修改,但是仍然可以调用read方法从native内存中读取相应的内容,覆盖Structure中相应的值。让我们看看如何在JAVA中使用final字段:classReadOnlyextendscom.sun.jna.Structure{publicfinalintrefCount;{//初始化refCount=-1;//从内存中读取数据read();}}注意所有字段初始化都应该在构造函数或静态方法块中完成。总结结构体是native方法中经常用到的一种数据类型,而在JNA中映射它的方法是我们需要掌握的。本文已收录于http://www.flydean.com/08-jna-structure/最流行的解读,最深刻的干货,最简洁的教程,很多你不知道的小技巧等着你去探索!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!