JNA实战笔记汇总⼀简单认识JNA成功调⽤JNA
⼀、简介
先说JNI(Java Native Interface)吧,有过不同语⾔间通信经历的⼀般都知道,它允许Java代码和其他语⾔(尤其C/C++)写的代码进⾏交互,只要遵守调⽤约定即可。⾸先看下JNI调⽤C/C++的过程,注意写程序时⾃下⽽上,调⽤时⾃上⽽下。
可见步骤⾮常的多,很⿇烦,使⽤JNI调⽤.dll/.so共享库都能体会到这个痛苦的过程。如果已有⼀个编译好的.dll/.so⽂件,如果使⽤JNI技术调⽤,我们⾸先需要使⽤C语⾔另外写⼀个.dll/.so共享库,使⽤SUN规定的数据结构替代C语⾔的数据结构,调⽤已有的 dll/so中公布的函 数。然后再在Java中载⼊这个库dll/so,最后编写Java native函数作为链接库中函数的代理。经过这些繁琐的步骤才能在Java中调⽤ 本地代码。因此,很少有Java程序员愿意编写调⽤dll/.so库中原⽣函数的java程序。这也使Java语⾔在客户端上乏善可陈,可以说JNI是Java的⼀⼤弱点!
那么JNA是什么呢?
JNA(Java Native Access)框架是⼀个开源的Java框架,是SUN公司主导开发的,建⽴在经典的JNI的基础之上的⼀个框架。使⽤JNI调⽤共享类库(.dll/.so⽂件)是⾮常⿇烦的事情,既需要编写java代码,⼜要编写C语⾔的代理⽅法,这其中需要很多数据类型的转换,是让⼈⾮常头痛。JNA框架就是为了解决这些问题和繁琐的事情⽽开发的,它提供⼀组Java⼯具类⽤于在运⾏期动态访问系统本地共享类库⽽不需要编写任何Native/JNI代码。开发⼈员只要在⼀个java接⼝中描述⽬标native library的函数与结构,JNA将⾃动实现Java接⼝到native function的映射,⼤⼤降低了Java调⽤本体共享库的开发难度。JNA与.NET平台上的P/Invoke机制⼀样简单和⽅便。
之所以说它是JNI的替 代者,是因为JNA⼤⼤简化了调⽤本地⽅法的过程,使⽤很⽅便,基本上不需要脱离Java环境就可以完成。
如果要和上图做个⽐较,那么JNA调⽤C/C++的过程⼤致如下:
可以看到步骤减少了很多,最重要的是我们不需要重写我们的动态链接库⽂件,⽽是有直接调⽤的API,⼤⼤简化了我们的⼯作量。
JNA只需要我们写Java代码⽽不⽤写JNI或本地代码。功能相对于Windows的Platform/Invoke和Python的ctypes。
⼆、原理
JNA使⽤⼀个⼩型的JNI库插桩程序来动态调⽤本地代码。开发者使⽤Java接⼝描述⽬标本地库的功能和结构,这使得它很容易利⽤本机平台的功能,⽽不会产⽣多平台配置和⽣成JNI代码的⾼开销。这样的性能、准确性和易⽤性显然受到很⼤的重视。
此外,JNA包括⼀个已与许多本地函数映射的平台库,以及⼀组简化本地访问的公⽤接⼝。
注意:
JNA是建⽴在JNI技术基础之上的⼀个Java类库,它使您可以⽅便地使⽤java直接访问动态链接库中的函数。
原来使⽤JNI,你必须⼿⼯⽤C写⼀个动态链接库,在C语⾔中映射Java的数据类型。
JNA中,它提供了⼀个动态的C语⾔编写的转发器,可以⾃动实现Java和C的数据类型映射,你不再需要编写C动态链接库。
也许这也意味着,使⽤JNA技术⽐使⽤JNI技术调⽤动态链接库会有些微的性能损失。但总体影响不⼤,因为JNA也避免了JNI的⼀些平台配置的开销。
三、相关jna.jar包和.so库⽂件的下载
JNA的项⽬是放在,⽬前最新版本是4.4.0,已有打包好的jar⽂件可供下载。
我这⾥使⽤的是4.2.1版本的,并提供了可以直接使⽤的jar包和.so库⽂件,,
四、配置环境,编译sayhello.so库⽂件
1、在app下⾯建⽴⼀個jni⽂件夾,添加库函数⽂件sayhello.c:
#include "sayhello.h"
int sayHello(){
printf("Hello World!");
return 1;
}
以及头⽂件sayhello.h:
#include <stdio.h>
int sayHello();
2、其他就是配置我们编译C/C++代码的环境了,如果不懂可以参考这篇⽂章:
3、成功配置我们的C/C++编译环境后,在lib⽬录下⾯成功编译出我们需要的.so⽂件。然后把jna.jar和相关的.so⽂件添加到项⽬中
4、接着我们在src⽬录下⾯添加jniLibs⽂件夹:把JNA的libjnidispatch.so.so库⽂件和我们⽣成的libsayhello.so库⽂件全部添加合并到jniLibs
⽂件夹下⾯,并配置我们的adle⽂件,添加jniLibs.srcDirs = [‘src/main/jniLibs’]:
android {
compileSdkVersion 25
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "com.jna_01"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "st.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile(''), 'proguard-rules.pro'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.straint:constraint-layout:1.0.2'
}
五、创建Library,成功实现Java调⽤C/C++函数代码库
创建⼀个Clibrary对象:
package com.jna_01;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
//继承Library,⽤于加载库⽂件
public interface Clibrary extends Library {
/
python转java代码/加载libhello.so链接库
Clibrary INSTANTCE = (Clibrary) Native.loadLibrary("sayhello", Clibrary.class);
//此⽅法为链接库中的⽅法
int sayHello();
}
1、定义接⼝对象。
在上⾯的代码中,我们定义了⼀个接⼝,继承⾃Library 或StdCallLibrary,默认的是继承Library ,
如果动态链接库⾥的函数是以stdcall⽅式输出的,那么就继承StdCallLibrary,⽐如众所周知的kernel32库。
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通过反射⾃动⽣成。
然后在MainActivity中调⽤sayHello⽅法:
接⼝中只需要定义你要⽤到的函数或者公共变量,不需要的可以不定义,注意参数和返回值的类型,应该和链接库中的函数类型保持⼀致。
定义好接⼝后,就可以使⽤接⼝中的函数即相应dll/so中的函数了
六、调⽤C++函数代码
定义好接⼝后,就可以使⽤接⼝中的函数即相应函数库中的函数了,这⾥我们在MainActivity中响应点击事件调⽤C++函数代码,并打印出结果:
package;
import Bundle;
import AppCompatActivity;
import Log;
import View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState){
setContentView(R.layout.activity_main);
}
//按钮点击事件
public void onClick(View view){
int result = Clibrary.INSTANTCE.sayHello();
Log.d("MainActivity","sayHello的放回结果:"+result);
}
}
到这⾥位置,如果成功运⾏项⽬,整个项⽬结构以及结果基本是这样的:
七、避免错误的建议:
1.要注意我们⽣成的库⽂件都是lib**.so,但是我们在加载.so库⽂件的时候是不需要lib前缀的。
2.要检查adle中是否指定了.so库⽂件的地址,在这⾥我之所以吧所有的.so库⽂件都放在了jniLibs⽂件夹中,
是因为我在adle中指定了 jniLibs.srcDirs = [‘src/main/jniLibs’]
3.可以解压成功编译的apk,查看该apk的lib⽬录下⾯是否有libsayhello.so和libjnidispatch.so两个库⽂件。
4.项⽬中偶时候需要⽤到Java调⽤c++函数代码,但是始终出错,主要错误原因是undefined symbol,不到c++ ⽅法。需要我们使⽤extern “C” 给C++代码做标记,否则⽆法到。
#include <stdlib.h>
#include <iostream>
using namespace std;
extern "C"
{
void test() {
cout << "TEST" << endl;
}
int addTest(int a,int b)
{
int c = a + b ;
return c ;
}
}
项⽬地址:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论