Java之JNI的介绍与应用20170622
/******************************************************************************************************************/
JNI(Java Native Interface(Java本地(c语言写的)接口))
一、JAVA调用C
1.Java如何调用c库的函数
1)加载C库(找到C库)
static { /* 1. load */
System.loadLibrary("native");//加载C库导致c文件中的JNI_OnLoad被调用
}
2)找到函数(JAVA和C库之间建立映射)
(1)显示建立(一般选择这个,更灵活)
/* System.loadLibrary */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
//先获得java程序的运行环境
return JNI_ERR; /* JNI version not supported */
}
//从而找到java程序的那个类
cls = (*env)->FindClass(env, "JNIDemo");
if (cls == NULL) {
return JNI_ERR;
}
/* 2. map java hello <-->c c_hello 最后建立对应关系,将类和C函数注册到Native中*/
if ((*env)->RegisterNatives(env, cls, methods, 1) < 0)
return JNI_ERR;
return JNI_VERSION_1_4;
}
其中methods数组定义如下,
static const JNINativeMethod methods[] = {
{"hello", "()V", (void *)c_hello},
};
关于数组成员类型中的含义如下:
typedef struct {
char *name;/* Java里调用的函数名 */
char *signature; /* JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型。 */
//即JAVA程序中要调用的java方法(*name指向的函数)所对应的参数和返回值,用一些符号表示,具体见图表。
(获取JNI字段描述符除了对应图标找之外,还可以先编译(java -d .JNIDemo.java)再用javah命令来生成头文件(javah -jni a.b.c.d.JNIDemo),头文件中就会含有那些描述符)
void *fnPtr; /* C语言实现的本地函数 */
} JNINativeMethod;
JNI字段描述符图表:
(2)隐式建立(自然用不到显示建立中需要做的事情,但是不灵活)
I、类a.b.c.d.JNIDemo要调用hello函数
II、C语言中要实现Java_a_b_c_d_JNIDemo_hello
//函数名固定,所以不灵活
//可以使用javah命令来获得这个复杂的函数名(头文件中已经声明好了这个函数)
III、可以用工具生成头文件
javac -d . JNIDemo.java
javah -jni a.b.c.d.JNIDemo
(3)注意,c程序中被java调用的函数,永远比对应java程序中的方法多两个参数(见javah生成的头文件,两个参数(JNIEnv *env, jobject cls))
3)调用函数
public native void hello();//使用前先使用native 关键字声明这是本地的方法(c实现的)
public static void main (String args[]) {
JNIDemo d = new JNIDemo();
/* 3. call */
d.hello();//调用
}
4)Java和C库传递数据
(1)传递基本类型数据
直接使用、直接返回
JAVA://传入123,打印返回(修改了java本地方法,自然要修改JNINativeMethod数组内容为相对应)
System.out.println(d.hello(123));
C://打印接收,返回100(可以从javah生成的头文件中获取到对应的声明来书写,去掉声明中的JNIEXPORT关键字(因为我们不需要导出,我们是注册进去的))
jint c_hello(JNIEnv *env, jobject cls, jint m)
{//使用jni.h中的类型
printf("Hello, world! val = %d\n", m);
return 100;
}
(2)传递字符串 (jni.pdf P39)
需要借用运行环境中的某些函数
JAVA:(修改了java本地方法,自然要修改JNINativeMethod数组内容为相对应)
System.out.println(d.hello("this is java"));
C:(可以从javah生成的头文件中获取到对应的声明来书写,去掉声明中的JNIEXPORT关键字)
jstring JNICALL c_hello(JNIEnv *env, jobject cls, jstring str)
{//JNICALL 可以不要,是个空的宏
//printf("this is c : %s\n", str);
//return "return from C";
const jbyte *cstr;
cstr = (*env)->GetStringUTFChars(env, str, NULL);//将传进来的String类转换为字符串
if (cstr == NULL) {//里面分配了内存,这里要判断
return NULL; /* OutOfMemoryError already thrown */
}
printf("Get string from java :%s\n", cstr);
(*env)->ReleaseStringUTFChars(env, str, cstr);//使用完了要释放
return (*env)->NewStringUTF(env, "return from c");//返回字符串
}
(3)传递数组 (jni.pdf P48)
JAVA:(修改了java本地方法,自然要修改JNINativeMethod数组内容为相对应)
int [] a = {1, 2, 3};
int [] b = null;
int i;
/* 3. call */
b = d.hello(a);
for (i = 0; i < b.length; i++)
System.out.println(b[i]);
C:(可以从javah生成的头文件中获取到对应的声明来书写,去掉声明中的JNIEXPORT关键字)
jintArray c_hello(JNIEnv *env, jobject cls, jintArray arr)
{
jint *carr;
jint *oarr;
jintArray rarr;
jint i, n = 0;
carr = (*env)->GetIntArrayElements(env, arr, NULL);//获取所有元素
if (carr == NULL) {
return 0; /* exception occurred */
}
n = (*env)->GetArrayLength(env, arr);//获取数组长度
oarr = malloc(sizeof(jint) * n);
if (oarr == NULL)
{
(*env)->ReleaseIntArrayElements(env, arr, carr, 0);//使用完了要释放
return 0;
}
for (i = 0; i < n; i++)
{
oarr[i] = carr[n-1-i];
}
(*env)->ReleaseIntArrayElements(env, arr, carr, 0);//使用完了要释放
/* create jintArray */
rarr = (*env)->NewIntArray(env, n);//构造jintArray (New<Type>Array)
if (rarr == NULL)//NewIntArray使用的参数可以搜索jni.h
{//创建失败
return 0;
}
//把oarr中所有的内容拷贝到raar中
//要设置的内容rarr,从0位开始设置,长度n,数据来源oarr。
(*env)->SetIntArrayRegion(env, rarr, 0, n,oarr);//SetIntArrayRegion使用的参数可以搜索jni.h
free(oarr);
return rarr;返回构造的jintArray类型的rarr
}
/******************************************************************************************************************/
二、C调用JAVA
1. 创建虚拟机
JavaVM* jvm;
JNIEnv* env;
/* 1. create java virtual machine */
if (create_vm(&jvm, &env)) {
printf("can not create jvm\n");
return -1;
}
jint create_vm(JavaVM** jvm, JNIEnv** env)
{
JavaVMInitArgs args;
JavaVMOption options[1];
args.version = JNI_VERSION_1_6;//表明虚拟机是哪个版本
args.nOptions = 1;
options[0].optionString = "-Djava.class.path=./"; //虚拟机查找类的路径./表示当前目录
args.options = options;
args.ignoreUnrecognized = JNI_FALSE;
return JNI_CreateJavaVM(jvm, (void **)env, &args);
}
2. 获得class
jclass cls;
/* 2. get class */
cls = (*env)->FindClass(env, "Hello");//字符串内为要找的类(Hello是要调用的类)
if (cls == NULL) {
printf("can not find hello class\n");
ret = -1;
goto destroy;
}
3. 实例化对象(非静态方法才需要实例化) : 获得构造方法(方法名为"<init>"), 构造参数, 创建对象
1)获得构造方法(方法名为"<init>")
jmethodID cid;
/* Get the method ID for the String constructor */
cid = (*env)->GetMethodID(env, cls,"<init>", "()V");//构造方法固定名为"<init>"
if (cid == NULL) {
ret = -1;
printf("can not get constructor method");
goto destroy;
}
2)构造参数(要调用的构造方法没有参数则不需要进行构造参数)
3)创建对象
jobject jobj;
jobj = (*env)->NewObject(env, cls, cid);
if (jobj == NULL) {
ret = -1;
printf("can not create object");
goto destroy;
}
4. 调用方法 : 又分为获得方法, 构造参数, 调用方法
1)获得方法
jmethodID mid;//方法ID
mid = (*env)->GetMethodID(env, cls, "sayhello_to","(Ljava/lang/String;)I");
if (mid == NULL) {//"sayhello_to"方法名,"(Ljava/lang/String;)I"sayhello_to方法参数和返回值对应的signature(用于分辨同名方法)
ret = -1;
printf("can not get method\n");
goto destroy;
}
signature(JNI字段描述符)获取可使用:
javac Hello.java
javap -p -s Hello.class(Hello.class为编译好的Hello.java)
2)构造参数(要调用的方法没有参数则不需要进行构造)
int r;//存放返回值
jstring jstr;
jstr = (*env)->NewStringUTF(env, "abcdefg");//构造传入参数,传入字符串"abcdefg"
3)调用方法
(1)静态方法:
(*env)->CallStaticVoidMethod(env, cls, mid, NULL);//NULL最后一项为要调用的方法的参数
(2)非静态方法:
int r;//存放返回值
jstring jstr;
jstr = (*env)->NewStringUTF(env, "abcdefg");//构造传入参数,传入字符串"abcdefg"
r = (*env)->CallIntMethod(env, jobj, mid, jstr);//非静态传入的是jobject jobj,传入参数jstr
printf("ret = %d\n", r);
5.读取/设置类中的属性(数据成员):
1). 获得属性ID
jfieldID nameID;
jfieldID ageID;
//get field id
nameID = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
if (nameID == NULL) {//"name"java类中的数据成员名称
ret = -1;//"Ljava/lang/String;"signature获取见上
printf("can not get field name");
goto destroy;
}
2). 读取/设置
//get/set field
jstr = (*env)->NewStringUTF(env, "Bill");
(*env)->SetObjectField(env, jobj, nameID, jstr);//设置类中的数据成员name
//name数据成员必然属于实例化对象的,先用创建类的实例化对象jobj
获取数据成员直接把SetObjectField的Set改为Get即可
/* Read the instance field s */
jstr = (*env)->GetObjectField(env, obj, fid);
3)读取/设置类中的Int型属性
ageID = (*env)->GetFieldID(env, cls, "age", "I");
if (ageID == NULL) {
ret = -1;
printf("can not get field age");
goto destroy;
}
(*env)->SetIntField(env, jobj, ageID, 10);//int类型数据成员设置
获取int数据成员直接把SetIntField中的Set改为Get即可
jint value = env->GetIntField(env, obj, fid);
6.使用结束后
destroy:
(*jvm)->DestroyJavaVM(jvm);
return ret;
Java之JNI的介绍与应用20170622的更多相关文章
- Android JNI技术介绍【转】
本文转载自:http://blog.csdn.net/yangwen123/article/details/8085833 JNI是JavaNative Interface 的缩写,通过JNI,Jav ...
- Java筑基 - JNI到底是个啥
在前面介绍Unsafe的文章中,简单的提到了java中的本地方法(Native Method),它可以通过JNI(Java Native Interface)调用其他语言中的函数来实现一些相对底层的功 ...
- Java 16 新功能介绍
点赞再看,动力无限.Hello world : ) 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 程序猿阿朗博客 已经收录,有很多知识点和系列文章. Ja ...
- Java 17 新功能介绍(LTS)
点赞再看,动力无限.Hello world : ) 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章. Jav ...
- Java 19 新功能介绍
点赞再看,动力无限. 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章. Java 19 在2022 年 9 ...
- JAVA使用JNI调用C++动态链接库
JAVA使用JNI调用C++动态链接库 使用JNI连接DLL动态链接库,并调用其中的函数 首先 C++中写好相关函数,文件名为test.cpp,使用g++编译为DLL文件,指令如下: g++ -sha ...
- 【Java的JNI快速学习教程】
1. JNI简介 JNI是Java Native Interface的英文缩写,意为Java本地接口. 问题来源:由于Java编写底层的应用较难实现,在一些实时性要求非常高的部分Java较难胜任(实时 ...
- java通过jni方式获取硬盘序列号(windows,linux)
linux系统java通过jni方式获取硬盘序列号 http://blog.csdn.net/starter110/article/details/8186788 使用jni在windows下读取硬盘 ...
- JAVA中JNI的简单使用
了解JNI:JAVA因其跨平台特性而受人们喜爱,也正因此,使得它和本机各种内部联系变得很少,所以JNI(Java Native Interface)就是用来解决JAVA本地操作的一种方式.JAVA通过 ...
随机推荐
- jquery中国地图插件
插件下载地址: http://www.17sucai.com/preview/1266961/2018-09-18/map/js/jsMap-1.1.0.min.js jsMap 项目介绍 这是一个功 ...
- 《图解 HTTP 》阅读 —— 第五章
第5章 与HTTP协作的web服务器 一台服务器可以托管多个域名. 在相同的IP地址下,虚拟主机可以寄存多个不同主机名和域名的网站,所以在发送HTTP请求时,必须在Host首部内指定完整的主机名和域名 ...
- Numpy入门笔记第一天
# 导入包 import numpy as np # 创建一维数组 a = np.arange(5) print "一维numpy数组", a print "数组的类型& ...
- redis 学习记录
http://www.yiibai.com/redis/redis_quick_guide.html Redis 是一款依据BSD开源协议发行的高性能Key-Value存储系统(cache and s ...
- 在windows和unbuntu上安装octave
windows安装octave 安装wiki Octave ftp库 从上述的库中可以找到对应的版本的octave的exe安装程序,或者是zip等的压缩包,建议直接下载对应系统的exe执行文件.安装. ...
- 小白用Android MVP-初体验(一)
写android以来,一直都是采用MVC的模式,所有的业务逻辑,网络请求等都放在了View中,即Activity或者Fragment中.看了一些mvp文章,很多跨度较大,也因为自己造诣不够,还不能跟上 ...
- 运维工程师如果将web服务http专变为https
1:生成私钥 2:生成证书签署请求 3:在提供CA签署的web网站上,提交生成的证书签署请求 4:下载已经签署的CA证书 5:将证书的信息保留在web服务器中,且应用到提供web服务的 ...
- QSet使用及Qt自定义类型使用QHash等算法
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QSet使用及Qt自定义类型使用QHash等算法 本文地址:http://techie ...
- 【BioCode】将多个蛋白质序列分成单个的txt文档
代码说明: fasta格式的蛋白质序列,一个txt里面有很多蛋白质序列,计算ss.pssm或disorder score时候都需要单条计算,需要分开. 分割前: 分割后: show you the c ...
- oracle package pragma SERIALLY_REUSABLE(编译指示 告诉PL/SQL 的运行时引擎,在数据引用之时不要保持包级数据。)
当包第一次被动调用时,将进行初始化:比如将包从硬盘上调到内存中来,放到系统全局工作区的共享缓冲池中,包的运行状态则被放到用户全局区的会话中存储区中,因此可以保证每个调用包的会话都拥有包的运行副本,当会 ...