JNI使用总结
说明:
这部分是写在JNI反射调用之后,因为普通的JNI在很早以前就使用过。现在做JNI内容的总结,整理了一下思路,发现基本的使用主要就两种:
- JAVA程序正向调用JNI,JNI内对C/C++代码功能进行调用。
- 需要集成在JNI接口生成的库中,在C/C++的代码中反射调用JAVA代码。
第一种其实就是常用的一些使用方式,后面会详细讲述,第二种用到的情况倒是不多,相当于在JNI组成的库中需要在C/C++代码的逻辑中反向调用。
这篇文章主要对第一种做详细阐述。
开发测试平台是win10。因为我本身做C/C++对JAVA的不太熟悉,所以JAVA的都是简化IDE,用简单的测试demo的使用。如果是用linux平台其实基本过程也差不多,就是生成的是.so库。
1. JNI介绍
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/intro.html
This chapter introduces the Java Native Interface (JNI). The JNI is a native programming interface. It allows Java code that runs inside a Java Virtual Machine (VM) to interoperate with applications and libraries written in other programming languages, such as C, C++, and assembly.
2. 工具
这部分网上的参考例子还蛮多的。
先说下我这边的工具:
JAVA IDE: IntelliJ IDEA
JAVA IDE应该也可以不装,直接装JDK,JRE的安装包就可以了。IDE安装了他会默认把java,javac之类的命令装起来,所以不管是用命令行还是IDE界面生成,本质还是一样的。
C++开发IDE:visual studio
3. 一个最简单demo
首先根据需要的接口先创建个java文件,然后定义类如下:
public class jniDemo { public native void hello(); public native void demoInterface1(String str); public native boolean demoInterface2(int num); ... public static void main(String[] args) { System.loadLibrary("myDll"); jniDemo demo = new jniDemo(); demo.demoInterface1("Hello"); if (demo.demoInterface2(10)) System.out.println("大于5"); else System.out.println("小等于5"); } }
其中native关键字说明这个是个 native方法。也就是后面会导入到头文件中的接口函数。
直接用装好的javah命令可以生成jni头文件
javah -jni com.aaa.bbb.ccc.ddd.jniDemo
然后在当前目录自动生成了头文件
用Visual stido创建一个dll工程
因为最后是以dll的形式(windows),提供给JAVA调用的。
将前面生成的的头文件加入工程,接着对上述接口进行实现
#include "stdafx.h"
#include <string>
#include <iostream>
#include "jni.h"
#include "com_aaa_bbb_ccc_ddd_jniDemo.h"
using namespace std;
JNIEXPORT void JNICALL Java_com_aaa_bbb_ccc_ddd_jniDemo_hello
(JNIEnv *, jobject)
{
cout << "hello" << endl;
}
JNIEXPORT void JNICALL Java_com_aaa_bbb_ccc_ddd_jniDemo_demoInterface1
(JNIEnv *env, jobject, jstring str)
{
const char *t_tmp = env->GetStringUTFChars(str, JNI_FALSE);
string tmp_str = t_tmp;
env->ReleaseStringUTFChars(str, t_tmp);
cout << tmp_str << endl;
}
JNIEXPORT jboolean JNICALL Java_com_aaa_bbb_ccc_ddd_jniDemo_demoInterface2
(JNIEnv *, jobject, jint num)
{
bool ret = false;
if (num > 5)
ret = true;
cout << "get num: " << num << endl;
return ret;
}
进行编译生成dll文件。
运行测试java代码调用
将dll放入到工程中(也可以放入java.exe所在的目录做临时测试使用)。
java的测试代码在
public static void main(String[] args) {
System.loadLibrary("myDll");
jniDemo demo = new jniDemo();
demo.hello();
demo.demoInterface1("Hello");
if (demo.demoInterface2(10))
System.out.println("大于5");
else
System.out.println("小等于5");
}
直接编译运行得到结果。
如果用命令行的话编译和运行时分开的两条命令。
编译命令:
javac -encoding UTF-8 jniDemo.java
执行命令:
java com.aaa.bbb.ccc.ddd.jniDemo
运行结果如下:
到这里说明整个JNI调用成功
——————————————————————————————————————————————
4. 传递数组
这部分在3的demo上继续进行
- 在java文件中加入一个传递参数是数组
public native void sendString(String []strArr);
- 生成头文件
/*
* Class: com_aaa_bbb_ccc_ddd_jniDemo
* Method: sendString
* Signature: ([Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_aaa_bbb_ccc_ddd_jniDemo_sendString
(JNIEnv *, jobject, jobjectArray);
可以看到参数多了一个jobjectArray。
- 在C/C++代码中对其进行实现
JNIEXPORT void JNICALL Java_com_aaa_bbb_ccc_ddd_jniDemo_sendString
(JNIEnv * env , jobject, jobjectArray stringArray)
{
int count = env->GetArrayLength(stringArray);
for (int i = 0; i < count; i++) {
jobject item = env->GetObjectArrayElement(stringArray, i);
const char *str = env->GetStringUTFChars((jstring)item, 0);
cout << "array [" << i << "]:" << str << endl;
env->ReleaseStringUTFChars((jstring)item, str);
}
}
- 修改增加java测试代码
System.out.println("-------------------------");
String []strArr = {"111111", "22222", "33333"};
demo.sendString(strArr);
然后重新编译接着运行
运行结果如下:
————————————————————————————————————————
5. 传递对象
这部分在3的demo上继续进行
- 在java文件中定义一个简单的类
package com.aaa.bbb.ccc.ddd;
public class TestStruct {
private int m_no;
private String m_name;
private String m_discribe;
public TestStruct(int i, String name, String disc)
{
m_no = i;
m_name = name;
m_discribe = disc;
}
};
- 在java文件中加入一个传递参数是数组
public native void sendStruct(String []strArr);
- 在C/C++代码中对其进行实现
JNIEXPORT void JNICALL Java_com_aaa_bbb_ccc_ddd_jniDemo_sendStruct
(JNIEnv *env, jobject, jobject testObj)
{
jclass jcs;
try {
jcs = env->FindClass("com/aaa/bbb/ccc/ddd/TestStruct");
}
catch (...)
{
cout << "catch find class error!" << endl;
return;
}
if (jcs == 0)
{
cout << "find class error!" << endl;
return;
}
jfieldID noId = env->GetFieldID(jcs, "m_no", "I");
jfieldID nameId = env->GetFieldID(jcs, "m_name", "Ljava/lang/String;");
jfieldID discId = env->GetFieldID(jcs, "m_discribe", "Ljava/lang/String;");
cout << "no:" << env->GetIntField(testObj, noId) << endl;
jstring nameStr = (jstring)env->GetObjectField(testObj, nameId);
const char *t_nameStr = env->GetStringUTFChars(nameStr, 0);
cout << "name:" << t_nameStr << endl;
env->ReleaseStringUTFChars((jstring)nameStr, t_nameStr);
jstring discStr = (jstring)env->GetObjectField(testObj, discId);
const char *t_discStr = env->GetStringUTFChars(discStr, 0);
cout << "disc:" << t_discStr << endl;
env->ReleaseStringUTFChars((jstring)discStr, t_discStr);
}
- 修改增加java测试代码
System.out.println("-------------------------");
TestStruct test = new TestStruct(5, "abc", "efg");
demo.sendStruct(test);
编译运行
——————————————————————————————————————————————————————
6. 总结
6.1. JNI函数名及类型
头文件内容
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#ifndef _Included_com_aaa_bbb_ccc_ddd_jniDemo
#define _Included_com_aaa_bbb_ccc_ddd_jniDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_aaa_bbb_ccc_ddd_jniDemo
* Method: hello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_aaa_bbb_ccc_ddd_jniDemo_hello
(JNIEnv *, jobject);
/*
* Class: com_aaa_bbb_ccc_ddd_jniDemo
* Method: demoInterface1
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_aaa_bbb_ccc_ddd_jniDemo_demoInterface1
(JNIEnv *, jobject, jstring);
/*
* Class: com_aaa_bbb_ccc_ddd_jniDemo
* Method: demoInterface2
* Signature: (I)Z
*/
JNIEXPORT jboolean JNICALL Java_com_aaa_bbb_ccc_ddd_jniDemo_demoInterface2
(JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
参考:
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html
- 类型
基础类型:
和C/C++中的类型就是一一对应的,看头文件就是直接typedef
Reference 类型:
JNI中的类型和Java对象中的类型的对应及结构 图:
关于引用类型有这么一段描述:
Referencing Java Objects
Primitive types, such as integers, characters, and so on, are copied between Java and native code. Arbitrary Java objects, on the other hand, are passed by reference. The VM must keep track of all objects that have been passed to the native code, so that these objects are not freed by the garbage collector. The native code, in turn, must have a way to inform the VM that it no longer needs the objects. In addition, the garbage collector must be able to move an object referred to by the native code.
正常类型可以正常使用,但对于java对象需要通过引用传递,需要我们手动释放他们。
引用类型也有局部和全局之分,局部就是jni方法作用域内的结果。JNI允许程序员从本地引用创建全局引用,同时在任何时候手动删除本地引用
如何使用本地引用:
To implement local references, the Java VM creates a registry for each transition of control from Java to a native method. A registry maps nonmovable local references to Java objects, and keeps the objects from being garbage collected. All Java objects passed to the native method (including those that are returned as the results of JNI function calls) are automatically added to the registry. The registry is deleted after the native method returns, allowing all of its entries to be garbage collected.
VM每次从JAVA到本地方法的转换都会创建一个注册表。注册表映射固定的本地引用映射到Java对象,并防止对象被垃圾收集。传递给本地调用的所有Java对象(包括那些作为JNI函数调用的结果返回的对象)都会自动添加到注册表中。在本机方法返回后,注册表将被删除,从而允许对其所有项进行垃圾回收。
基础数组类型:
First, we provide a set of functions to copy primitive array elements between a segment of a Java array and a native memory buffer. Use these functions if a native method needs access to only a small number of elements in a large array.
Second, programmers can use another set of functions to retrieve a pinned-down version of array elements. Keep in mind that these functions may require the Java VM to perform storage allocation and copying. Whether these functions in fact copy the array depends on the VM implementation, as follows:
Lastly, the interface provides functions to inform the VM that the native code no longer needs to access the array elements. When you call these functions, the system either unpins the array, or it reconciles the original array with its non-movable copy and frees the copy.
JNI提供了一套函数用于在JAVA数组段和本地内存缓冲之间拷贝基本对象数组。如果本地方法需要访问少量元素在一个大数组中这个方法就会很有用。
其次,程序员可以使用另一套函数接口来检索数组元素的固定版本。注意这些函数可能需要Java VM执行存储分配和复制。这些函数是否实际复制数组取决于VM实现。
最后如果不需要了可以释放了
- 函数名
- 签名
JNI使用Java VM的类型签名表示。
头文件的说明里能看到函数的签名。
6.2.JNI操作接口
这篇的操作写的挺详细的可供参考:
https://blog.csdn.net/yingshukun/article/details/79080462
注意:其实对这些接口进行操作很大的一个毛病就是有的对象获得之后需要释放资源,这个需要特别注意。
下一篇: Angular中$compile源码分析