【JNA探索之路系列】之二:JNA API

作者:郭嘉
邮箱:[email protected]
博客:http://blog.csdn.net/allenwells
github:https://github.com/AllenWell

工欲善其事,必先利其器,本篇文章就来详细探讨在JNA开源项目的工程中提供了哪些应用接口,它们分别提供了怎样的功能。

JNA4.1.0的包结构如下所示:

  • com.sun.jna
  • com.sun.jna.platform
  • com.sun.jna.platform.dnd
  • com.sun.jna.platform.mac
  • com.sun.jna.platform.unix
  • com.sun.jna.platform.win32
  • com.sun.jna.platform.win32.COM
  • com.sun.jna.platform.win32.COM.tlb
  • com.sun.jna.platform.win32.COM.tlb.imp
  • com.sun.jna.platform.wince
  • com.sun.jna.ptr
  • com.sun.jna.win32

这些包结构的功能可以分为三个大类,下面来一一介绍。

一 Java本地访问

com.sun.jna
提供了简化的本地库的访问。
com.sun.jna.ptr
提供各种本地指针到类型(<类型>*)表示。
com.sun.jna.win32
提供所需的Windows平台上标准的API类型和功能映射器。

1.1 JNA加载

JNA包括一个小型的,特定于平台的共享库,使所有本地访问。当第一次访问本机类,JNA将首先尝试加载从jna.boot.library.path指定的目录这个库。如果失败,它会掉下来的系统库的路径返回装货。最后,它会尝试从JNA的jar文件中提取存根库,并加载它。
该jna.boot.library.path属性主要是为了支持jna.jar被列入-Xbootclasspath,那里的java.library.path和LD_LIBRARY_PATH被忽略。这也是用于指定版本的库优先使用任何可能已在系统上安装有用。

从系统加载不能jna.nosys = true来禁止,并从jar文件解压缩可以通过jna.nounpack = true来禁用。

用于搜索JNA的本机库的库名可通过设定jna.boot.library.name,默认为“jnidispatch”来改变。如果你的系统需要共享库的唯一名称(而不是唯一的路径)也可能是有用的设置这个值,如果你的系统必须存储在同一目录下不同版本的JNA共享库(例如,用于不同的架构)。

1.2 库映射

当你确定哪些共享库保存的方法,你需要访问,创建对应于该库的类。例如,对于C库映射本身看起来像下列之一:

// Alternative 1: interface-mapped class, dynamically load the C library
public interface CLibrary extends Library {
    CLibrary INSTANCE = (CLibrary)Native.loadLibrary("c", CLibrary.class);
}

// Alternative 2: direct-mapped class
public class CLibrary {
    static {
        Native.register("c");
    }
}

传递给Native.loadLibrary(字符串,类)(或NativeLibrary.getInstance(字符串))方法的字符串是共享库文件的未装饰名称。下面是库名称映射的一些例子:

【JNA探索之路系列】之二:JNA API_第1张图片

具有唯一的文件系统路径的任何给定的本机库由NativeLibrary的一个实例表示,并且经由NativeLibrary.getInstance(字符串)获得。当不再受任何Java代码中引用的本机库将被卸载。

如果库名称为空,你的映射会应用到当前进程,而不是一个单独装入的库。这可能有助于避免冲突,如果有多个不兼容的版本可用的库。

加载的本地库的搜索路径可以通过设置jna.library.path和一些其他属性进行修改。您也可以在JAR文件中捆绑本机库,并有自动JNA提取它们的负载。见NativeLibrary了解详细信息。

1.3 函数映射

函数名是直接从他们的Java接口名称映射到本地库导出的符号。例如,该功能为ASCII字符串转换为整数是这样的:

public interface CLibrary extends Library {
    int atol(String s);
}

或者,你可以直接映射到一个声明本地方法:

public class CLibrary {
    public static native int atol(String s);
}

如果你喜欢重命名Java方法,以符合Java编码约定,那么你就可以在提供的选项中的条目(Library.OPTION_FUNCTION_MAPPER/ FunctionMapper)地图传给Native.loadLibrary(),它映射Java名称的本地名称。虽然这样可以使你的Java代码显得简洁,名称的其他映射可能使其少一些明显的被称为原生功能。
通过与NativeLibrary实例中获得的功能类的一个实例。这个功能实例处理参数编组代表团原生功能。

1.4 类型映射

Java类型必须选择符合本地类型相同的大小。以下是由JNA库支持的类型:

【JNA探索之路系列】之二:JNA API_第2张图片

无符号值可以通过分配对应二进制补码表示为相同大小的符号类型予以通过。
基本类型的Java阵列可以由缓冲器,以访问该阵列的一个子集被包裹(改变有效尺寸和/或offest)。
原始类型的Java数组只适用于单一的通话范围内使用。如果本机代码保持一个参考的内存,使用内存或缓冲液代替。
原始数组和结构作为结构成员都覆盖在母体结构存储器。
位域必须手动装入一个整数类型。
所有其他类型的最终必须被转换为在该表中的类型之一。方法与参数或类型比这些必须使用从类型或NativeMapped供应类型转换信息导出为不支持其他类型的返回值。
类型映射行为可以通过初始化库接口时,提供了一个TypeMapper为Library.OPTION_TYPE_MAPPER选项来进行定制。见W32APITypeMapper的例子​​,它提供布尔和字符串类型的自定义转换。您可以自由使用任何类型方便您定义的接口,但是所有的自定义类型必须提供一个映射到上面所列的基本或派生类型之一。
类型映射也可以通过使用户定义类型实现NativeMapped接口定制基于每个类基础为用户定义的类型。
结构和联盟都不会转换为指针时,按值传递。

1.5 原始数组

Java基本数组可以用于任何一个本地基本数组。一个函数调用中的本地代码做出一个数组的任何更改将反映在Java数组中。如果本机代码将使用该数组的函数调用,其中被提供在数组的外面,存储器或缓冲器应该使用(见缓冲器)。
要映射本地多维数组,使用一维Java数组与许多元素等同于全面的本地数组。

举例

// Original C code
#define DIM0 2
#define DIM1 3
int array[DIM0][DIM1];
int i,j;

for (i=0;i < DIM0;i++) {
  for (j=0;j < DIM1;j++) {
    array[i][j] = i*DIM1 + j;
  }
}

// Equivalent JNA code
final int DIM0 = 2;
final int DIM1 = 3;
int[] array = new int[6];
for (int i=0;i < DIM0;i++) {
  for (int j=0;j < DIM1;j++) {
    array[i*DIM1 + j] = i*DIM1 + j;                   
  }                   
}

1.6 指针

指针可以被用作不透明的类型从该其它数据类型可被提取。指针类型是合理的后备任何基于指针的类型(包括数组)。用户一般不允许构造指针从头。
类型安全指针可以通过从PointerType类派生来定义。任何这样的用户定义类型将被视为等同于指针。

1.7 字符串

Java字符串执行相同的功能原生类型const char 与常量为wchar_t (NULL结尾的数组)。为了调用本地函数时使用正确的类型,我们就来介绍一些注释,以确定如何在Java字符串应转换。 Java的字符串通常转换为char ,因为这是一个字符串的最常见的用法。字符串被自动转换为字符跨越函数调用一个NULL结尾的数组。返回的char 值将自动复制到一个字符串,如果该方法返回的签名字符串(的strdup,例如)。
如果本机方法返回的char *和实际分配内存,指针的返回类型应使用,以避免泄漏内存。然后由你来采取必要的措施来释放分配的内存。

当转换的Java Unicode字符到字符数组,默认平台编码被使用,除非系统属性jna.encoding被设置为一个有效的编码。此属性可被设置为“UTF8”,例如,以确保所有的本地字符串使用该编码。

字符串数组传递给本机代码(无论是作为一个函数的参数或回调返回值)将被转换成的char (或wchar_t的一个NULL结尾数组中WSTRING数组的情况下。

1.8 宽(UNICODE)字符串

该WSTRING类是用来识别宽字符的字符串。 Unicode值是直接从Java字符数组为本地wchar_t的阵列复制。

1.9 基本数组

1.10 缓存/内存模块

使用数组来表示仅在函数调用原始类型传递到功能使用的缓冲区。本地方法不能返回Java数组,因为没有规范的方法来指示返回数组的长度预期。相反,使用中的指针类数组访问方法之一,供应返回数组的长度。
缓冲剂也可以用作存储器缓冲器的输入参数;直接字节的缓冲区往往能提供超过基本数组大大改善性能。通过本机代码提供的指针可以通过调用Pointer.getByteBuffer(长,长)被转换成一个缓冲。

如果你需要传递一个基本类型数组的一个子集,您可以通过它环绕在一个缓冲区子,如的ByteBuffer,使用ByteBuffer.wrap(字节[],INT,INT)方法做到这一点。包装的阵列中的缓冲区也允许你传递一个Java数组的一个子集到本机的功能。

1.11 回调/函数指针

JNA提供支持Java的回调为本地代码。你必须定义扩展回调接口的接口,并与由本机代码所需的函数指针相匹配的签名定义一个回调方法。该方法的名称可能会比“回调”只是如果在延伸回调或回调实现类的接口只有一个方法,其他的东西。参数和返回值遵循相同的规则,作为一个直接的函数调用。
当访问Windows的API,有时文件表明一个函数指针参数必须是指驻留在DLL中的函数。在这些情况下,添加DLLCallback接口回调定义。函数指针所看到的视窗将位于jnidispatch.dll模块中。

如果回调返回一个字符串或字符串[],返回的内存将是有效的,直到返回的对象是GC’d。

如果您的本机代码初始化一个结构内的函数指针,JNA将自动生成一个回调实例匹配声明的类型。这使您可以方便地调用使用正确的Java语法的本机代码所提供的功能。

举例

// Original C code
struct _functions {
  int (*open)(const char*,int);
  int (*close)(int);
};

// Equivalent JNA mapping
public class Functions extends Structure {
  public static interface OpenFunc extends Callback {
    int invoke(String name, int options);
  }
  public static interface CloseFunc extends Callback {
    int invoke(int fd);
  }
  public OpenFunc open;
  public CloseFunc close;
}
...
Functions funcs = new Functions();
lib.init(funcs);
int fd = funcs.open.invoke("myfile", 0);
funcs.close.invoke(fd);

回调,也可以使用作为返回值。本地函数指针被包裹在一个代理实现的声明回调的类型,以方便从Java调用。

// Original C code
typedef void (*sig_t)(int);
sig_t signal(int signal, sig_t sigfunc);

// Equivalent JNA mapping
public interface CLibrary extends Library {
    public interface SignalFunction extends Callback {
        void invoke(int signal);
    }
    SignalFunction signal(int signal, SignalFunction func);
}

如果你需要在一个回调运作的线程上下文的控制,你可以为任何给定的回调对象安装CallbackThreadInitializer。在第一时间回调上调用当前未连接到VM线程,初始化将被查询,以确定如何线程应设置。你可以指示所需的名称,线程组,并且后台程序状态的线程,以及指示所述线程是否应保留附加到VM回调出口后。后者可以提高性能,如果你知道你将在同一个线程越来越多的回调,避免了需要虚拟机生成多个Java线程对象为同一本地线程。如果你这样做离开本地线程连接,您应该确保你在以后的点取下(由内回调之前返回调用Native.detach(布尔)),或者从CallbackThreadInitializer.isDaemon(回调)方法返回true使得本地线程不会阻止该VM退出。
如果您不需要定制,否则回调线程,你可以简单地从你的回调中调用Native.detach(布尔)指示线程附件是否应保持与否。

1.12 变量参数列表(可变参数)

的C可变参数函数定义可以被映射到一个Java可变参数的方法定义。例如,

// Original C code
extern int printf(const char* fmt, ...);

// Equivalent JNA mapping
interface CLibrary extends Library {
    int printf(String fmt, ...);

1.13 结构

Java的结构代表了本地结构。默认情况下,这种类型的处理作为一个指针作为参数或返回值使用时,构造(结构*)在本机侧。当作为结构领域中使用,该结构被解释为通过值。要强制补充解释,标注接口Structure.ByValue和Structure.ByReference提供。
一个Java结构中的数据会自动写入到本机内存只是一个结构参数的本地函数调用之前,并从本机内存在函数返回后自动读取。

1.13.1 Pointer-to-Structure Arguments

通过一个指针结构作为参数,只需使用Java结构的子类,并指向本地数据存储将被使用。的结构中的内容将被传递给函数,当该函数返回更新。结构可根据该平台的原生C结构的默认对齐规则包装。

// Original C code
typedef struct _Point {
  int x, y;
} Point;

Point* translate(Point* pt, int dx, int dy);

// Equivalent JNA mapping
class Point extends Structure { public int x, y; }
Point translate(Point pt, int x, int y);
...
Point pt = new Point();
Point result = translate(pt, 100, 100);

1.13.2 Structure by Value Arguments/Return

要通过结构按值,先定义结构,然后定义从它实现Structure.ByValue一个空类。使用根据值类作为参数或者返回类型。

// Original C code
typedef struct _Point {
  int x, y;
} Point;

Point translate(Point pt, int dx, int dy);

// Equivalent JNA mapping
class Point extends Structure {
    public static class ByValue extends Point implements Structure.ByValue { }
    public int x, y;
}
Point.ByValue translate(Point.ByValue pt, int x, int y);
...
Point.ByValue pt = new Point.ByValue();
Point result = translate(pt, 100, 100);

1.13.3 Array-of-Structure Arguments

要通过结构的数组,只需使用所需的结构类型的Java数组。如果阵列未初始化,它会自动初始化函数调用之前。

// Original C code
void get_devices(struct Device[], int size);

// Equivalent JNA mapping
int size = ...
Device[] devices = new Device[size];
lib.get_devices(devices, devices.length);

或者,您可以重新分配一个单独结构实例到一个数组,如下所示:

Device dev = new Device();
// As an array of Structure
Structure[] structs = dev.toArray(size);
// As an array of Device
Device[] devices = (Device[])dev.toArray(size);

1.13.4 Returning an Array of struct

声明方法返回相应类型的结构,然后调用Structure.toArray(INT)转换成适当大小的初始化结构数组。请注意,您的结构类必须有一个无参数的构造函数,你是负责释放返回的内存(如果适用)以任何方式适合被调用的函数。

// Original C code
struct Display* get_displays(int* pcount);
void free_displays(struct Display* displays);

// Equivalent JNA mapping
Display get_displays(IntByReference pcount);
void free_displays(Display[] displays);
...
IntByReference pcount = new IntByReference();
Display d = lib.get_displays(pcount);
Display[] displays = (Display[])d.toArray(pcount.getValue());
...
lib.free_displays(displays);

1.13.5 Nested Structure Definitions

嵌套结构被视为连续的存储器(相对于指针结构)。例如:

// Original C code
typedef struct _Point {
  int x, y;
} Point;

typedef struct _Line {
  Point start;
  Point end;
} Line;

// Equivalent JNA mapping
class Point extends Structure {
  public int x, y;
}

class Line extends Structure {
  public Point start;
  public Point end;
}

不需要嵌套结构的明确的初始化;根据需要和正确映射到母体结构的存储器中的对象将被创建。
如果需要的结构内的指针的结构中,则可以使用Structure.ByReference标记接口,以指示场应被视为一个指针,而不是内联的完整结构。

// Original C code
typedef struct _Line2 {
  Point* p1;
  Point* p2;
} Line2;

// Equivalent JNA mapping
class Point extends Structure {
    public static class ByReference extends Point implements Structure.ByReference { }
    public int x, y;
}
class Line2 extends Structure {
  public Point.ByReference p1;
  public Point.ByReference p2;
}

更一般的情况是仅有一个指针存储器。这允许您定义字段,而不必限定内结构本身,类似于声明一个结构没有定义在C:

// Original C code
typedef struct _Line2 {
  Point* p1;
  Point* p2;
} Line2;

// Equivalent JNA mapping
class Line2 extends Structure {
  public Pointer p1;
  public Pointer p2;
}

Line2 line2;
Point p1, p2;
...
line2.p1 = p1.getPointer();
line2.p2 = p2.getPointer();

1.13.6 Nested arrays

结构与嵌套数组需要一个明确的构造,以确保结构尺寸正确计算。

typedef struct _Buffer {
  char buf1[32];
  char buf2[1024];
} Buffer;

class Buffer extends Structure {
  public byte[] buf1 = new byte[32];
  public byte[] buf2 = new byte[1024];
}

结构的本地尺寸需要等到结构被实际使用才进行计算。

1.13.7 Variable-sized structures

结构具有可变的尺寸,或者与原始的数组元素,例如:

// Original C code
typedef struct _Header {
  int flags;
  int buf_length;
  char buffer[1];
} Header;

需要一个构造它建立所需要的尺寸的结构,并初始化适当的事情。例如:

// Equivalent JNA mapping
class Header extends Structure {
  public int flags;
  public int buf_length;
  public byte[] buffer;
  public Header(int bufferSize) {
    buffer = new byte[bufferSize];
    buf_length = buffer.length;
    allocateMemory();
  }
}

1.13.8 Volatile fields

通常情况下,JNA会写一个结构的全部内容的函数调用前和函数调用后读取本机内存回来。有时一个结构域无意对客户使用,得到由硬件异步改性,或以其他方式被有效地只读。如果你期望结构的任何字段由Java程序之外的任何代理人进行修改,你应该标记字段波动。这可以防止JNA自动更新从Java值的本机内存。您仍然可以通过调用Structure.writeField(字符串)有问题的场力从Java值机内存的更新。

class Data extends com.sun.jna.Structure {
  public volatile int refCount;
  public int value;
}
...
Data data = new Data();

在上面的例子中,场的refcount将仅基于Java值与一个呼叫到data.writeField(“引用计数”)写入到本地存储器。要获取本机内存的当前状态,调用Structure.read()(更新整个结构)或data.readField(“引用计数”)(更新只是引用计数字段)。

1.13.9 Read-only fields

如果你想绝对防止Java代码的修改结构的内容,你可能标志着其字段决赛。结构读取仍然可以覆盖基于本机内存内容的价值,但是没有Java代码将能够修改任何字段。

class ReadOnly extends com.sun.jna.Structure {
  // Do not initialize the field here, or the compiler will inline the value!
  public final int refCount;
  {
    // Initialize fields here, to ensure the values are not inlined
    refCount = -1;
    read();
    // refCount might now have a different value
  }
}
...
ReadOnly ro = new ReadOnly();
// Will not compile!
ro.refCount = 0;

请确保你参加以下几点:
所有最终字段应该在构造函数初始化。
如果调用Structure.read()从任何地方,但构造,记住,编译器和/或热点将假定字段值跨越该函数调用不会改变。

1.14 联合体

联合体是一种特殊类型的结构。联盟中的每个领域宣布在覆盖本机内存同一个空间。当写入本机内存联合体,你必须指定哪些领域将被写入通过提供所需的字段的类的Union.setType(java.lang.Class中)的方法。在读,所有非基于指针的领域将来自本机内存进行初始化。结构,String和WSTRING成员将不会被初始化,除非它们通过Union.setType(java.lang.Class中)选择。

1.15 Java对象

在某些情况下,如直接调用本地虚拟机的功能,有必要通过Java对象的本地方法。缺省情况下,JNA禁止使用未明确支持,除非它从NativeMapped派生任何Java对象,因为它是通常不需要使用这样的对象和通常标志着一个程序员错误。为了避免错误标记使用Java对象,使用该库加载选项Library.OPTION_ALLOW_OBJECTS与Boolean.TRUE。

1.16 上次错误

UIF功能设置系统错误属性(错误或GetLastError函数()),如果您在测绘JNA申报异常的错误代码将被抛出一个LastErrorException。或者,你可以使用Native.getLastError()来检索它,前提是Native.setPreserveLastError(布尔)被称为一个真正的价值。抛出异常是优选的,因为它具有更好的性能。

1.17 调用映射

有时,本地函数只存在为C预处理器宏或内联函数。如果你需要做更多的不是简单地改变调用函数的名称(可以通过功能映射处理),一个InvocationMapper可以让你随意重新配置的函数调用,包括更改的方法名称和重新排序,添加或删除参数。见InvocationMapper文档的详细信息。

1.18 库全局数据

该方法NativeLibrary.getGlobalVariableAddress(java.lang.String中)可以被用于获取全局变量作为一个指针的地址。指针的方法可以被用来读取或写入的值以适合变量类型

1.19 VM碰撞保护

定义一个新的库,并编写测试遇到内存访问错误而崩溃的虚拟机时,它的情况并不少见。这些通常是由不正确的映射或传递到机库的参数无效引起的。生成Java错误,而不是崩溃的虚拟机,调用Native.setProtected(真)。不是所有的平台都支持这种保护;如果不是,Native.isProtected()的值将保持假。
注:如果启用保护模式,你应该利用JSIG库,如果有(见链接的信号),以避免与JVM的使用信号干扰。总之,设置环境变量LD_PRELOAD(或LD_PRELOAD_64)的路径(通常/libjsig.so java.home/lib/ {} os.arch)之前在JRE的lib目录libjsig.so启动您的Java APPLICAT。

1.20 性能

下面给出几种提升JNA运行性能的方法:

(1)使用方法直接映射

使用方法直接映射效率比确实接口映射使本地调用。直接映射不支持可变参数调用或指针,字符串,或WSTRING阵列作为一个参数或返回值。对于optimium结果,只用原始的参数和返回值;你必须显式转换和从对象自己。

(2)避免使用类型映射

类型映射招致每个函数调用额外的开销。您可以通过确保您的参数和/或返回类型都已经基本类型避免这种情况。

(3)指针/阵列/缓冲器变体

Java原始阵列通常慢于直接存储器(指针,内存,或ByReference)或NIO缓冲器来使用,由于Java的存储器必须被钉扎和可能复制整个本地呼叫,由于Java数组不必连续地分配。

(4)大型结构

结构通常写入本机内存前和函数调用后读取本机内存回来。具有非常大的结构,可以使用反射来遍历所有字段是一个性能命中。结构自动同步可以通过调用Structure.setAutoSynch(布尔)与false参数被禁用。然后由你来使用Structure.readField(String)和Structure.writeField(字符串)或Structure.writeField(字符串对象)进行同步与感兴趣的只是领域。

(5)扔在最后一个错误异常

为了避免在每次调用原生保系统的最后一个错误信息的开销,调用setPreserveLastError(假)或设置系统属性jna.preserve_last_error = FALSE。
在那些你感兴趣的错误号/ GetLastError函数()的值的方法,声明你的方法抛出LastErrorException。

二 公用平台

com.sun.jna.platform
提供了基于特定平台的库跨平台的工具。
com.sun.jna.platform.dnd
提供集成,扩展拖放功能,允许在所有平台上使用的幻像拖动图像。

三 特定平台

com.sun.jna.platform.mac
提供公共库映射为OS X平台。
com.sun.jna.platform.unix
提供公共库映射Unix和X11的平台。
com.sun.jna.platform.win32
提供公共库映射为Windows平台。
com.sun.jna.platform.win32.COM
提供…
com.sun.jna.platform.win32.COM.tlb
提供…
com.sun.jna.platform.win32.COM.tlb.imp
提供…
com.sun.jna.platform.wince

你可能感兴趣的:(编程设计,-,Java)