JNA的使用

项目地址:http://jna.java.net/
API:http://jna.java.net/javadoc/overview-summary.html


JNA全称Java Native Access,是一个创建在经典的JNI技术之上的Java开源框架(https://github.com/twall/jna)。JNA提供一组Java工具类用于在运行期动态访问系统本地库(native library:如Window的dll)而不须要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。html


JNA包:java

https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna/4.0.0/jna-4.0.0.jarlinux

https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna-platform/4.0.0/jna-platform-4.0.0.jargit

 

JNA在线帮助文档:http://twall.github.io/jna/4.0/javadoc/github

JNA入门示例:https://github.com/twall/jna/blob/master/www/GettingStarted.md编程

1,dll和so是C函数的集合和容器,这与Java中的接口概念吻合,因此JNA把dll文件和so文件当作一个个接口。在JNA中定义一个接口就是至关于了定义一个DLL/SO文件的描述文件,该接口表明了动态连接库中发布的全部函数。并且,对于程序不须要的函数,能够不在接口中声明。数组

2,JNA定义的接口通常继承com.sun.jna.Library接口,若是dll文件中的函数是以stdcall方式输出函数,那么,该接口就应该继承com.sun.jna.win32.StdCallLibrary接口。安全

3,Jna难点:编程语言之间的数据类型不一致数据结构


Java和C的数据类型对照表架构

Java 类型

类型

原生表现

 

 boolean

 int

 32位整数(可定制)

 

 byte

 char 

 8位整数

 

 char

 wchar_t

 平台依赖

 

 short

 short

 16位整数

 

 int

 int

 32位整数

 

 long

long long, __int64

 64位整数

 

 float

 float

 32位浮点数

 

 double

 double

 64位浮点数

 

 Buffer/Pointer

 pointer

 平台依赖(3264位指针)

 

 <T>[] (基本类型的数组)

 pointer/array

3264位指针(参数/返回值)

邻接内存(结构体成员)

 

 String

 char*

/0结束的数组 (native encoding or jna.encoding)

 

 WString

 wchar_t*

 /0结束的数组(unicode)

 

 String[]

 char**

 /0结束的数组的数组

 

 WString[]

 wchar_t**

 /0结束的宽字符数组的数组

 

 Structure

 struct*/struct

指向结构体的指针(参数或返回值) (或者明确指定是结构体指针)结构体(结构体的成员) (或者明确指定是结构体)

 

 Union

union 

 等同于结构体

 

 Structure[]

 struct[]

 结构体的数组,邻接内存

 

 Callback

 <T> (*fp)()

 Java函数指针或原生函数指针

 

 NativeMapped

 varies

 依赖于定义

 

 NativeLong

 long

 平台依赖(3264位整数)

 

 PointerType

 pointer

 Pointer相同

 

 

通用入门案例:

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

/** Simple example of JNA interface mapping and usage. */
public class HelloWorld {

    // 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.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
                               CLibrary.class);

        void printf(String format, Object... args);
    }

    public static void main(String[] args) {
        CLibrary.INSTANCE.printf("Hello, World\n");
        for (int i=0;i < args.length;i++) {
            CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);
        }
    }
}
运行程序,若是没有带参数则只打印出“Hello, World”,若是带了参数,则会打印出全部的参数。

很简单,不须要写一行C代码,就能够直接在Java中调用外部动态连接库中的函数!

 

下面来解释下这个程序。

(1)须要定义一个接口,继承自Library StdCallLibrary

默认的是继承Library ,若是动态连接库里的函数是以stdcall方式输出的,那么就继承StdCallLibrary,好比众所周知的kernel32库。好比上例中的接口定义:

public interface CLibrary extends Library {

}


(2)接口内部定义

接口内部须要一个公共静态常量:INSTANCE,经过这个常量,就能够得到这个接口的实例,从而使用接口的方法,也就是调用外部dll/so的函数。

该常量经过Native.loadLibrary()这个API函数得到,该函数有2个参数:

  • 第一个参数是动态连接库dll/so的名称,但不带.dll或.so这样的后缀,这符合JNI的规范,由于带了后缀名就不能够跨操做系统平台了。搜索动态连接库路径的顺序是:先从当前类的当前文件夹找,若是没有找到,再在工程当前文件夹下面找win32/win64文件夹,找到后搜索对应的dll文件,若是找不到再到WINDOWS下面去搜索,再找不到就会抛异常了。好比上例中printf函数在Windows平台下所在的dll库名称是msvcrt,而在其它平台如Linux下的so库名称是c。
  • 第二个参数是本接口的Class类型。JNA经过这个Class类型,根据指定的.dll/.so文件,动态建立接口的实例。该实例由JNA经过反射自动生成。
CLibrary INSTANCE = (CLibrary)
            Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
                               CLibrary.class);

接口中只须要定义你要用到的函数或者公共变量,不须要的能够不定义,如上例只定义printf函数:

void printf(String format, Object... args);

注意参数和返回值的类型,应该和连接库中的函数类型保持一致。

(3)调用连接库中的函数

定义好接口后,就可使用接口中的函数即相应dll/so中的函数了,前面说过调用方法就是经过接口中的实例进行调用,很是简单,如上例中:

CLibrary.INSTANCE.printf("Hello, World\n");
        for (int i=0;i < args.length;i++) {
            CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);
        }

这就是JNA使用的简单例子,可能有人认为这个例子太简单了,由于使用的是系统自带的动态连接库,应该还给出一个本身实现的库函数例子。其实我以为这个彻底没有必要,这也是JNA的方便之处,不像JNI使用用户自定义库时还得定义一大堆配置信息,对于JNA来讲,使用用户自定义库与使用系统自带的库是彻底同样的方法,不须要额外配置什么信息。好比我在Windows下创建一个动态库程序:

#include "stdafx.h"

extern "C"_declspec(dllexport) int add(int a, int b);

int add(int a, int b) {
    return a + b;
}

而后编译成一个dll文件(好比CDLL.dll),放到当前目录下,而后编写JNA程序调用便可:
public class DllTest {

    public interface CLibrary extends Library {
        CLibrary INSTANCE = (CLibrary)Native.loadLibrary("CDLL", CLibrary.class);

        int add(int a, int b);
    }

    public static void main(String[] args) {
        int sum = CLibrary.INSTANCE.add(3, 6);

        System.out.println(sum);
    }
}





简单案例
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

interface HelloInter extends Library{
 int toupper(int ch);
 double pow(double x,double y);
 void printf(String format,Object... args);
}
public class HelloWorld {

 public static void main(String [] args){
 HelloInter INSTANCE =
(HelloInter)Native.loadLibrary(
Platform.isWindows()?"msvcrt":"c",
HelloInter.class);
 INSTANCE.printf("Hello, Worldn");
 String [] strs = new String[]{"芙蓉","如花","凤姐"};
 for (int i=0;i < strs.length;i++) {
 INSTANCE.printf("人物 %d: %sn", i, strs[i]);
}
 System.out.println("pow(2d,3d)=="+INSTANCE.pow(2d, 3d));
System.out.println("toupper('a')=="+(char)INSTANCE.toupper((int)'a'));
}

}

显示结果:
pow(2d,3d)==8.0
toupper('a')==A
Hello, World
人物 0: 芙蓉
人物 1: 如花
人物 2: 凤姐

说明:

HelloInter接口中定义的3个函数全是C语言函数库中的函数,其定义格式以下:

int toupper(int ch)
double pow( double x, double y )
int printf(const char* format, ...)

C语言函数库中有不少个函数,可是咱们只用到了这3个函数,因此其余的函数不须要声明在接口中。


JNA模拟结构体

例:使用 JNA调用使用 Struct的 C函数
假设咱们如今有这样一个C 语言结构体
struct UserStruct{
long id;
wchar_t* name;
int age;
};
使用上述结构体的函数
#define MYLIBAPI extern "C" __declspec( dllexport )

MYLIBAPI void sayUser(UserStruct* pUserStruct);
对应的Java 程序中,在例1 的接口中添加下列代码:

public static class UserStruct extends Structure{
    public NativeLong id;
    public WString name;
    public int age;
    public static class ByReference extends UserStruct
implements Structure.ByReference { }
    public static class ByValue extends UserStruct implements
Structure.ByValue
{ }
}
public void sayUser(UserStruct.ByReference struct);

Java中的代码
   UserStruct userStruct=new UserStruct ();
   userStruct.id=new NativeLong(100);
   userStruct.age=30;
   userStruct.name=new WString("奥巴马");
   TestDll1.INSTANCE.sayUser(userStruct);

Structure说明

如今,咱们就在Java 中实现了对C 语言的结构体的模拟。这里,咱们继承了Structure 类,用这个类来模拟C 语言的结构体。

必须注意,Structure 子类中的公共字段的顺序,必须与C 语言中的结构的顺序一致。不然会报错!由于,Java 调用动态连接库中的C 函数,实际上就是一段内存做为函数的参数传递给C函数。动态连接库觉得这个参数就是C 语言传过来的参数。同时,C 语言的结构体是一个严格的规范,它定义了内存的次序。所以,JNA 中模拟的结构体的变量顺序绝对不能错。若是一个Struct 有2 个int 变量。Int a, int b若是JNA 中的次序和C 中的次序相反,那么不会报错,可是数据将会被传递到错误的字段中去。
Structure 类表明了一个原生结构体。当Structure 对象做为一个函数的参数或者返回
值传递时,它表明结构体指针。当它被用在另外一个结构体内部做为一个字段时,它表明结构
体自己。
另外,Structure 类有两个内部接口Structure.ByReference 和Structure.ByValue。这两个接
口仅仅是标记,若是一个类实现Structure.ByReference 接口,就表示这个类表明结构体指针
若是一个类实现Structure.ByValue 接口,就表示这个类表明结构体自己
使用这两个接口的实现类,能够明肯定义咱们的Structure 实例表示的是结构体的指针
仍是结构体自己。
上面的例子中,因为Structure 实例做为函数的参数使用,所以是结构体指针。因此这

里直接使用了UserStruct userStruct=new UserStruct ();

也可使用UserStruct userStruct=new UserStruct.ByReference ();
明确指出userStruct 对象是结构体指针而不是结构体自己。

JNA模拟复杂结构体
C 语言最主要的数据类型就是结构体。结构体能够内部能够嵌套结构体,这使它能够模
拟任何类型的对象。
JNA 也能够模拟这类复杂的结构体。

struct CompanyStruct{
long id;
wchar_t* name;
UserStruct users[100];
int count;
};


JNA 中能够这样模拟:
public static class CompanyStruct extends Structure{
public NativeLong id;
public WString name;
public UserStruct.ByValue[] users=new UserStruct.ByValue[100];
public int count;
}


这里,必须给users 字段赋值,不然不会分配100 个UserStruct 结构体的内存,这样JNA
中的内存大小和原生代码中结构体的内存大小不一致,调用就会失败。


例:结构体内部能够包含结构体对象的指针的数组

struct CompanyStruct2{
long id;
wchar_t* name;
UserStruct* users[100];
int count;
};


JNA 中能够这样模拟:
public static class CompanyStruct2 extends Structure{
public NativeLong id;
public WString name;
public UserStruct.ByReference[] users=new
UserStruct.ByReference[100];
public int count;
}


测试代码:
CompanyStruct2.ByReference companyStruct2=new CompanyStruct2.ByReference();
companyStruct2.id=new NativeLong(2);
companyStruct2.name=new WString("Yahoo");
companyStruct2.count=10;
UserStruct.ByReference pUserStruct=new
UserStruct.ByReference();
pUserStruct.id=new NativeLong(90);
pUserStruct.age=99;
pUserStruct.name=new WString("杨致远");
// pUserStruct.write();
for(int i=0;i<companyStruct2.count;i++){
companyStruct2.users[i]=pUserStruct;
}

TestDll1.INSTANCE.sayCompany2(companyStruct2);
执行测试代码,报错了。这是怎么回事?
考察JNI 技术,咱们发现Java 调用原生函数时,会把传递给原生函数的Java 数据固定
在内存中,这样原生函数才能够访问这些Java 数据。对于没有固定住的Java 对象,GC 能够
删除它,也能够移动它在内存中的位置,以使堆上的内存连续。若是原生函数访问没有被固
定住的Java 对象,就会致使调用失败。
固定住哪些java 对象,是JVM 根据原生函数调用自动判断的。而上面的CompanyStruct2
结构体中的一个字段是UserStruct 对象指针的数组,所以,JVM 在执行时只是固定住了
CompanyStruct2 对象的内存,而没有固定住users 字段引用的UserStruct 数组。所以,形成
了错误。
咱们须要把users 字段引用的UserStruct 数组的全部成员也所有固定住,禁止GC 移动
或者删除。
若是咱们执行了pUserStruct.write();这段代码,那么就能够成功执行上述代码。
Structure 类的write()方法会把结构体的全部字段固定住,使原生函数能够访问。


案例一:获取本地时间(Get local time)

若是你在Java Native Access 首页 看过“JNA如何入门”,你就会知道一个很简单的关于调用Windows 平台下的API函数:GetSystemTime() 的JNA示例。这个不完整的例子只是展现了JNA的基本特色。(在例子的基础上,我作了一个更完整的基于Windows的例子来介绍JNA)我在Windows平台下完善了这个例子来介绍JNA。

第一例子基于Windows GetLocalTime() API函数返回本地当前的时间和日期。和GetSystemTime()不一样的是,返回的时间/日期是协调通用时间(UTC)格式的,GetLocalTime()返回的时间/日期信息的格式是根据当前时区来表示。

在一个Java程序中使用JNA调用GetLocalTime,你须要知道这个函数所在的Windows平台下的动态连接库(DLL)的名称(和可能所在的地理区域)。咱们发现GetLocalTime()和GetSystemTime在同一个DLL文件中:kernel32.dll。你还须要知道GetLocalTime()在C语言环境中的申明。申明以下Listing 1:

Listing 1. GetLocalTime在C语言中的申明

typedef struct
{
   WORD wYear;
   WORD wMonth;
   WORD wDayOfWeek;
   WORD wDay;
   WORD wHour;
   WORD wMinute;
   WORD wSecond;
   WORD wMilliseconds;
}
SYSTEMTIME, *LPSYSTEMTIME;

VOID GetLocalTime(LPSYSTEMTIME lpst);

这个基于C语言的申明代表传到这个函数的参数数目和类型。在这个例子中,只有一个参数---一个指向Windows SYSTEMTIME结构体的指针。并且,每一个结构体成员的类型是16bit长度的无符号整型。根据这些信息,你可以建立一个彻底描述GetLocalTime()函数的接口,如Listing 2中所示:

Listing 2. Kernel32.java

// Kernel32.java

import com.sun.jna.*;
import com.sun.jna.win32.*;

public interface Kernel32 extends StdCallLibrary
{
   public static class SYSTEMTIME extends Structure
   {
      public short wYear;
      public short wMonth;
      public short wDayOfWeek;
      public short wDay;
      public short wHour;
      public short wMinute;
      public short wSecond;
      public short wMilliseconds;
   }

   void GetLocalTime (SYSTEMTIME result);
}




Kernel32 接口(The Kernel32 interface)

由于JNA使用经过一个接口来访问某个库中的函数,Listing 2表示了一个描述GetLocalTime()的接口。根据约定,我把接口命名为Kernel32是由于GetLocalTime()在Windows的kernel32.dll库。

这个接口必须继承com.sun..jna.Library接口。由于Windows API函数遵循stdcall调用协议(stdcall calling convention),为Windows API申明的接口也必须继承com.sun.jna.win32. StdCallLibrary接口。所以这个接口共继承了Library 和 com.sun.jna.win32.StdCall两个接口。

在前面,你已经知道了GetLocalTime() 须要一个指向SYSTEMTIME结构体的指针做为它惟一的参数。由于Java不支持指针,JNA是经过申明一个com.sun.jna.Structure的子类来代替的。根据java文档中抽象类的概念,在参数环境中,Structure至关于C语言的struct*。

在SYSTEMTIME类中的字段和C结构体中的相对应的属性字段的顺序是一一对应的。保证字段顺序的一致性是很是重要的。例如,我发现交换wYear和wMonth会致使wYear和wMonth值互换。

每一个字段在java中是short integer类型的。按照JNA首页上 “默认类型映射”章节给出的提示,这个short integer分配类型是正确。然而,咱们应该知道一个重要的区别:Windows平台下的WORD类型等同于C语言环境中的16-bit的无符号的short integer,而java中short integer是16-bit有符号的short integer。

一个类型映射的问题

经过比较一个API 函数返回的整型值,你会发现Windows/C 语言的无符号整型和Java语言的有符号整型的JNA类型映射是有问题的。在比较的过程当中,若是你不细心,那么错误的执行过程可能致使决定性状况。致使这种后果是由于忘记任何数值的符号位的肯定是根据:在无符号整型的状况下会被解释为正号,而在有符号整型的进制中被理解为负号的。

经过Kernel32获取本地时间(Access the local time with Kernel32)

JNA首页上的GetSystemTime()示例已经代表必须使用预先申明的接口为本地库分配一个实例对象。你能够经过com.sun.jna.Native类中静态公用方法loadLibrary(String name, Class interfaceClass)来完成上述的目标。Listing 3 所示:

Listing 3. LocalTime.java

// LocalTime.java

import com.sun.jna.*;

public class LocalTime
{
   public static void main (String [] args)
   {
      Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32",
                                                    Kernel32.class);
      Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();
      lib.GetLocalTime (time);
      System.out.println ("Year is "+time.wYear);
      System.out.println ("Month is "+time.wMonth);
      System.out.println ("Day of Week is "+time.wDayOfWeek);
      System.out.println ("Day is "+time.wDay);
      System.out.println ("Hour is "+time.wHour);
      System.out.println ("Minute is "+time.wMinute);
      System.out.println ("Second is "+time.wSecond);
      System.out.println ("Milliseconds are "+time.wMilliseconds);
   }
}




Listing 3 执行Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32", Kernel32.class);来分配一个Kernel32实例对象而且装载kernel32.dll。由于kernel32.dll是Windows平台下标准的dll文件,因此不要指定访问这个库的路径。然而,若是找不到这个dll文件,loadLibrary()会抛出一个UnsatisfiedLinkError异常。

Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();建立了一个SYSTEMTIME结构体的示例。初始化后下面是lib.GetLocalTime (time);,这句话使用本地的时间/日期来给这个实例赋值。几个System.out.println()语句是输出这些值。

编译和运行这个应用(Compile and run the application)

这部分很容易。假设jna.jar、Kernel32.java和LocalTime.java是放在当前文件夹中,调用java –cp jna.jar;. LocalTime.java来编译这个应用的源代码。若是在Windows平台下,调用invoke java –cp jna.jar;. LocalTime 来运行这个应用。你能够获得相似与Listing 4的输出结果:

Listing 4. 从LocalTime.java生成的输出

Year is 2007
Month is 12
Day of Week is 3
Day is 19
Hour is 12
Minute is 35
Second is 13
Milliseconds are 156

案例二:调用本地的
使用JNA的调用本地方法的时候须要自定义数据结构,下面咱们经过调用Windows提供的的锁定工做站方法来了解一下JNA。

    一、首先查询Windows API知道锁定工做站的方法在user32.dll中定义,接下来定义一个接口来继承JNA的Library.java接口,用做声明DLL库文件,这里咱们就把它命名为User32:

    public interface User32 extends Library {}

复制代码
二、查询user32.dll提供的API得知锁定工做方法是LockWorkStation,返回类型是boolean型,在User32.java中新增相应的方法:

    boolean LockWorkStation();

复制代码
这样咱们的User32.java这个类就定义好了。接下来咱们写测试程序进行调用。

    三、编写测试类好比LockWorkStation.java,首先经过JNA的Native类加载对应的dll:

    User32 user32 = (User32) Native.loadLibrary("user32", User32.class);

复制代码
而后就能够调用LockWorkStation方法了,完整代码以下:

 
   public class LockWorkStation {
        public static void main(String[] args) {
          User32 user32 = (User32) Native.loadLibrary("user32", User32.class);
          user32.LockWorkStation();
        }
    }



复制代码
这里说明一下loadLibrary方法中第一个参数是须要加载的dll文件名称,第二个参数的做用是让JNA使用这个类的加载器去加载DLL文件,加载顺序是,先从Users.class类的当前文件夹找,若是没有找到,再在工程当前文件夹下面找win32/win64文件夹,找到后搜索对应的dll文件,若是找不到再到WINDOWS下面去搜索,再找不到就会抛异常了。以TWAINDSM.dll将文件放到工程的根文件夹能够按照下面这个格式放:

附件: jnaexplorer.JPG

  上面的User32定义的是dll库文件,有时会碰到好比HANDLE、POINT、WORD和MSG等数据类型,有些数据类型JNA中没有提供,须要本身定义,根据做用的不一样,定义的时候继承的父类也不同,好比HANDLE定义方法是:

  
 class HANDLE extends PointerType {
            private boolean immutable;
            public HANDLE() { }
            public HANDLE(Pointer p) { setPointer(p); immutable = true; }
          public Object fromNative(Object nativeValue, FromNativeContext context) {
                Object o = super.fromNative(nativeValue, context);
                if (INVALID_HANDLE_VALUE.equals(o))
                    return INVALID_HANDLE_VALUE;
                return o;
            }
            public void setPointer(Pointer p) {
                if (immutable)
                    throw new UnsupportedOperationException("immutable reference");
                super.setPointer(p);
            }
        }


        HANDLE被定义为类型安全的指针。而POINT用做表示坐标,不须要这么复杂,定义方式为:
  
 class POINT extends Structure {
            public int x, y;
            public POINT() { }
            public POINT(int x, int y) { this.x = x; this.y = y; }
      }


使用JNA的过程当中也不必定会一路顺风,好比会抛出”非法内存访问”,这时候检查一下变量是否==null。还有内存对齐的问题,当从内存中获取图片信息进行保存的时候,若是内存对齐处理很差,就会抛出很严重的异常,致使JVM异常退出,JNA提供了四种内存对齐的方式,分别是:ALIGN_DEFAULT、ALIGN_NONE、ALIGN_GNUC和ALIGN_MSVC。ALIGN_DEFAULT采用平台默认的对齐方式(推荐);ALIGN_NONE是不采用对齐方式;ALIGN_GNUC为针对linux/gcc操做系统的对齐方式。ALIGN_MSVC为针对win32/msvc架构的内存对齐方式。

    JNA也提供了一种保护机制.好比防止JNA出现异常不会致使JVM异常退出,默认是开启这个功能的,开启方式为 System.setProperty(“jna.protected”,”true”); 记得要在JNA加载dll文件以前调用,而后try {...} catch(Throwable e)异常,不过你也不要指望太高,不要觉得加上这个就万事大吉,出现”非法内存访问”的时候仍是会一筹莫展。JNA也提供了一种保护机制.好比防止JNA 出现异常不会致使JVM异常退出,默认是开启这个功能的,开启方式为 System.setProperty(“jna.protected”,”true”); 记得要在JNA加载dll文件以前调用,而后try {...} catch(Throwable e)异常,不过你也不要指望太高,不要觉得加上这个就万事大吉,出现”非法内存访问”的时候仍是会一筹莫展。


参考文章:

深刻浅出JNA—快速调用原生函数:http://www.doc88.com/p-31975835542.html

JNA—JNI终结者:http://blog.csdn.net/shendl/article/details/3589676

JNA的使用:http://xbgd.iteye.com/blog/1044864

深刻理解JNA—模拟C语言结构体:http://blog.csdn.net/shendl/article/details/3599849

相关文章
相关标签/搜索