JNI總結(1)

說明:

這部分是寫在JNI反射調用之後,因爲普通的JNI在很早以前就使用過。現在做JNI內容的總結,整理了一下思路,發現基本的使用主要就兩種:

  1. JAVA程序正向調用JNI,JNI內對C/C++代碼功能進行調用。
  2. 需要集成在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

 

注意:其實對這些接口進行操作很大的一個毛病就是有的對象獲得之後需要釋放資源,這個需要特別注意。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章