Java编程教程之 JNI(Java Native Interface) 基础知识,原型数据

3、 JNI基础知识

JNI在本机系统中定义了与Java类型对应的以下JNI类型:

  1. Java Primitives: jint , jbyte , jshort , jlong , jfloat , jdouble , jchar , jboolean                                           对应的Java Primitive   int , byte , short , long , float , double , charboolean 。
  2. Java引用类型: java.lang.Object jobject 。 它还定义了以下子类型 :
    1. java.lang.Class jclass 。
    2. java.lang.String jstring 。
    3. jthrowable for java.lang.Throwable 。
    4. jarray for Java array。 Java数组是一个具有八个基本数组和一个Object数组的引用类型。 因此,有八个基元数组jintArray , jbyteArray , jshortArray , jlongArray , jfloatArray , jdoubleArray , jcharArrayjbooleanArray ; 和一个对象数组jobjectArray 。

本机函数接收上述JNI类型中的参数,并返回JNI类型中的值(例如jstring , jintArray )。 但是,本机函数在它们自己的本机类型上运行(例如C-string,C的int[] )。 因此,需要在JNI类型和本机类型之间进行转换(或转换)。

本机程序:

  1. 接收JNI类型的参数(由Java程序传递)。
  2. 对于参考JNI类型,将参数转换或复制到本地本机类型,例如,将jstring转换为C字符串,将jintArray为C的int[] ,依此类推。 原始JNI类型(如jintjdouble不需要转换,可以直接操作。
  3. 以本地本机类型执行其操作。
  4. 以JNI类型创建返回的对象,并将结果复制到返回的对象中。
  5. 返回。

JNI编程中最令人困惑和具有挑战性的任务是JNI 引用类型(如jstring , jobject , jintArray , jobjectArray )和本机类型( C-string , int[] )之间的转换(或转换)。 JNI Environment接口提供了许多功能来进行转换。

JNI是一个C接口,它不是面向对象的。 它并没有真正传递对象。

[C ++面向对象的界面?!]

4、 在Java和本机程序之间传递参数和结果

4.1   传递原语

传递Java原语很简单。 jxxx类型在本机系统中定义,即。 jint , jbyte , jshort , jlong , jfloat , jdouble , jcharjboolean用于每个Java的基元int , byte , short , long , float , double , charboolean 。

Java JNI Program: TestJNIPrimitive.java

public class TestJNIPrimitive {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
 
   // Declare a native method average() that receives two ints and return a double containing the average
   private native double average(int n1, int n2);
 
   // Test Driver
   public static void main(String args[]) {
      System.out.println("In Java, the average is " + new TestJNIPrimitive().average(3, 2));
   }
}

此JNI程序加载共享库myjni.dll (Windows)或libmyjni.so ( libmyjni.so )。 它声明了一个接收两个intnative方法average() 并返回一个包含两个int的平均值的double 。 main()方法调用average() 。

将Java程序编译为“ TestJNIPrimitive.class ”并生成C / C ++头文件“ TestJNIPrimitive.h ”:

javac -h . TestJNIPrimitive.java

C实现 - TestJNIPrimitive.c

头文件TestJNIPrimitive.h包含一个函数声明Java_TestJNIPrimitive_average() ,它接受一个JNIEnv* (用于访问JNI环境接口),一个jobject (用于引用该object ),两个jint (Java本机方法的两个参数)并返回一个jdouble (Java本机方法的返回类型)。

 JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average (JNIEnv *,jobject,jint,jint); Java_TestJNIPrimitive_average (JNIEnv *,jobject,jint,jint); 

JNI类型jintjdouble对应于Java的intdouble类型。

“ jni.h ”和“ win32\jni_mh.h ”(与平台相关)包含八个JNI原语的typedef语句和一个额外的jsize 。

有趣的是, jint被映射到C的long (至少是32位),而不是C的int (可能是16位)。 因此,在C程序中使用jint非常重要,而不是简单地使用int 。 Cygwin不支持__int64 。

 //在“win \ jni_mh.h”中 - 与机器相关的机器标题
 typedef long jint;
 typedef __int64 jlong​​;
 typedef signed char jbyte;
 
 //在“jni.h”中
 typedef unsigned char jboolean;
 typedef unsigned short jchar;
 typedef short jshort;
 typedef float jfloat;
 typedef double jdouble;
 typedef jint jsize; 
//在“win \ jni_mh.h”中 - 与机器相关的机器标题
 typedef long jint;
 typedef __int64 jlong​​;
 typedef signed char jbyte;
 
 //在“jni.h”中
 typedef unsigned char jboolean;
 typedef unsigned short jchar;
 typedef short jshort;
 typedef float jfloat;
 typedef double jdouble;
 typedef jint jsize; 

 TestJNIPrimitive.c的实现如下:

#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitive.h"
 
JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
          (JNIEnv *env, jobject thisObj, jint n1, jint n2) {
   jdouble result;
   printf("In C, the numbers are %d and %d\n", n1, n2);
   result = ((jdouble)n1 + n2) / 2.0;
   // jint is mapped to int, jdouble is mapped to double
   return result;
}

将C程序编译为共享库( jni.dll )。

gcc -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o myjni.dll TestJNIPrimitive.c

运行Java程序:

java -Djava.library.path=. TestJNIPrimitive

C ++实现 - TestJNIPrimitive.cpp

#include <jni.h>
#include <iostream>
#include "TestJNIPrimitive.h"
using namespace std;
 
JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
          (JNIEnv *env, jobject obj, jint n1, jint n2) {
   jdouble result;
   cout << "In C++, the numbers are " << n1 << " and " << n2 << endl;
   result = ((jdouble)n1 + n2) / 2.0;
   // jint is mapped to int, jdouble is mapped to double
   return result;
}

编译C ++程序:

 g ++ -I“%JAVA_HOME%\ include”-I“%JAVA_HOME%\ include \ win32”-shared -o myjni.dll TestJNIPrimitive.cpp 

4.2   传递字符串

Java JNI程序: TestJNIString.java

public class TestJNIString {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
   // Native method that receives a Java String and return a Java String
   private native String sayHello(String msg);
 
   public static void main(String args[]) {
      String result = new TestJNIString().sayHello("Hello from Java");
      System.out.println("In Java, the returned string is: " + result);
   }
}

这个JNI程序声明了一个native方法sayHello() ,它接收一个Java String并返回一个Java String 。 main()方法调用sayHello() 。

编译Java程序并生成C / C ++头文件“ TestJNIString.h ”:

javac -h . TestJNIString.java

C实现 - TestJNIString.c

头文件TestJNIString.h包含此函数声明:

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *, jobject, jstring);

JNI定义了一个jstring类型来表示Java String 。 最后一个参数(JNI类型为jstring )是传递给C程序的Java String 。 返回类型也是jstring 。

传递字符串比传递基元更复杂,因为Java的String是一个对象(引用类型),而C-string是一个以NULL结尾的char数组。 您需要在Java String (表示为JNI jstring )和C-string( char* )之间进行转换。

JNI环境(通过参数JNIEnv*访问)提供转换功能:

  1. 要从JNI字符串( jstring )获取C字符串( char* ),请调用方法const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*) 。
  2. 要从C字符串( char* )获取JNI字符串( jstring ),请调用方法jstring NewStringUTF(JNIEnv*, char*) 。

C实现TestJNIString.c如下。

  1. 它接收JNI字符串( jstring ),通过GetStringUTFChars()转换为C字符串( char* GetStringUTFChars() 。
  2. 然后它执行其预期的操作 - 显示收到的字符串并提示用户返回另一个字符串。
  3. 它通过NewStringUTF()将返回的C字符串( char* )转换为JNI字符串( jstring NewStringUTF() ,并返回jstring 。

 

#include <jni.h>
#include <stdio.h>
#include "TestJNIString.h"
 
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
   // Step 1: Convert the JNI String (jstring) into C-String (char*)
   const char *inCStr = (*env)->GetStringUTFChars(env, inJNIStr, NULL);
   if (NULL == inCSt) return NULL;
 
   // Step 2: Perform its intended operations
   printf("In C, the received string is: %s\n", inCStr);
   (*env)->ReleaseStringUTFChars(env, inJNIStr, inCStr);  // release resources
 
   // Prompt user for a C-string
   char outCStr[128];
   printf("Enter a String: ");
   scanf("%s", outCStr);    // not more than 127 characters
 
   // Step 3: Convert the C-string (char*) into JNI String (jstring) and return
   return (*env)->NewStringUTF(env, outCStr);
}

将C程序编译为共享库。

gcc -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o myjni.dll TestJNIString.c

现在,运行Java程序:

java -Djava.library.path=. TestJNIString
In C, the received string is: Hello from Java
Enter a String: test
In Java, the returned string is: test

JNI本机字符串函数

JNI支持Unicode(16位字符)和UTF-8(1-3字节编码)字符串的转换。 UTF-8字符串的作用类似于以null结尾的C字符串( char数组),应该在C / C ++程序中使用。

JNI字符串( jstring )函数是:

 // UTF-8字符串(编码为1-3字节,向后兼容7位ASCII) 
  //可以映射到以null结尾的char-array C-string

const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
    //返回一个指向字节数组的指针,该字节数组表示修改后的UTF-8编码中的字符串。
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
   //通知VM本机代码不再需要访问utf。
jstring NewStringUTF(JNIEnv *env, const char *bytes);
  //从修改后的UTF-8编码中的字符数组构造一个新的java.lang.String对象。
jsize GetStringUTFLength(JNIEnv *env, jstring string);
 //返回字符串的修改后的UTF-8表示的字节长度。
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);
    //将从offset start开始的len个Unicode字符转换为修改后的UTF-8编码  
     //并将结果放在给定的缓冲区buf中。
  
 // Unicode字符串(16位字符)
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
    //返回指向Unicode字符数组的指针
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
    //通知VM本机代码不再需要访问字符。
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);
    //从Unicode字符数组构造一个新的java.lang.String对象。
jsize GetStringLength(JNIEnv *env, jstring string);
    //返回Java字符串的长度(Unicode字符数)。
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);
    //将从offset start开始的len个Unicode字符数复制到给定的缓冲区buf 

UTF-8字符串或C字符串

GetStringUTFChars()函数可用于从给定的Java的jstring创建新的C字符串( char* )。 如果无法分配内存,则该函数返回NULL 。 检查NULL是一个好习惯。

第三个参数isCopy (of jboolean* ),它是一个“in-out”参数,如果返回的字符串是原始java.lang.String实例的副本,则将设置为JNI_TRUE 。 如果返回的字符串是指向原始String实例的直接指针,则它将设置为JNI_FALSE - 在这种情况下,本机代码不应修改返回的字符串的内容。 如果可能,JNI运行时将尝试返回直接指针; 否则,它返回一份副本。 尽管如此,我们很少对修改底层字符串感兴趣,并且经常传递NULL指针。

每当您不需要返回的GetStringUTFChars()字符串来释放内存和引用以便可以对其进行垃圾回收时,始终调用ReleaseStringUTFChars() 。

NewStringUTF()函数使用给定的C字符串创建一个新的JNI字符串( jstring )。

JDK 1.2引入了GetStringUTFRegion() ,它将jstring (或从length start的一部分GetStringUTFRegion()复制到“ 预分配”的 C的char数组中。 可以使用它们代替GetStringUTFChars() 。 由于预先分配了C的数组,因此不需要isCopy 。

JDK 1.2还引入了Get/ReleaseStringCritical()函数。 与GetStringUTFChars()类似,如果可能,它返回一个直接指针; 否则,它返回一份副本。 本机方法不应阻止(对于IO或其他)一对GetStringCritical()ReleaseStringCritical()调用。

Unicode字符串

它使用jchar*来存储Unicode字符,而不是char* 。

#include <jni.h>
#include <iostream>
#include <string>
#include "TestJNIString.h"
using namespace std;
 
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
   // Step 1: Convert the JNI String (jstring) into C-String (char*)
   const char *inCStr = env->GetStringUTFChars(inJNIStr, NULL);
   if (NULL == inCStr) return NULL;
 
   // Step 2: Perform its intended operations
   cout << "In C++, the received string is: " << inCStr << endl;
   env->ReleaseStringUTFChars(inJNIStr, inCStr);  // release resources
 
   // Prompt user for a C++ string
   string outCppStr;
   cout << "Enter a String: ";
   cin >> outCppStr;
 
   // Step 3: Convert the C++ string to C-string, then to JNI String (jstring) and return
   return env->NewStringUTF(outCppStr.c_str());
}

编译:

g++ -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o myjni.dll TestJNIString.cpp

请注意,C ++本机字符串函数与C语法不同。在C ++中,我们可以使用“env->”而不是“(env *) - >”。 此外,C ++函数中不需要JNIEnv *参数。

另请注意,C ++支持一个字符串类(在标题<string>下面,它更加用户友好,以及传统的C字符串(char数组))。

[TODO]是否直接支持C ++字符串类?

 

4.3传递基元数组

JNI计划 - TestJNIPrimitiveArray.java

 1
 2
 3
 4
五
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17 
 2
 3
 4
五
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17 
公共类TestJNIPrimitiveArray {
   静态的 {
      的System.loadLibrary( “myjni”); // myjni.dll(Windows)或libmyjni.so(Unixes)
    }
 
   //声明一个本机方法sumAndAverage()接收一个int []和
   //返回一个double [2]数组,其中[0]为sum,[1]为平均值
   private native double [] sumAndAverage(int [] numbers);
 
   //测试驱动程序
   public static void main(String args []){
      int [] numbers = {22,33,33};
      double [] results = new TestJNIPrimitiveArray()。sumAndAverage(numbers);
      System.out.println(“在Java中,总和是”+结果[0]);
      System.out.println(“在Java中,平均值是”+结果[1]);
    }
 } 

C实现 - TestJNIPrimitiveArray.c

标题“ TestJNIPrimitiveArray.h”包含以下函数声明:

<span style="color:#000000">JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_average(JNIEnv *,jobject,jintArray); </span>

在Java中,数组是一种引用类型,类似于类。有9种类型的Java数组,八​​个基元中的每一个和一个数组java.lang.Object。 JNI定义了类型为每个八个Java原始阵列,即jintArrayjbyteArrayjshortArrayjlongArrayjfloatArrayjdoubleArrayjcharArrayjbooleanArray用于Java的原始阵列intbyteshortlongfloatdoublecharboolean分别。它还定义了一个jobjectArrayfor Java的数组Object(稍后讨论)。

同样,你需要JNI阵列和本地阵列,例如之间的转换,之间jintArray和C的jint[],或jdoubleArray和C的jdouble[]。JNI Environment接口为转换提供了一组函数:

  1. 要从jint[]JNI 获取C本机jintArray,请调用jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)
  2. 要从jintArrayC本机获取JNI jint[],首先,调用jintArray NewIntArray(JNIEnv *env, jsize len)分配,然后使用void SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf)从中复制jint[]jintArray

上面有8组函数,一组用于八个Java原语中的每一个。

本机程序需要:

  1. 接收传入的JNI阵列(例如jintArray),转换为C的本机阵列(例如jint[])。
  2. 执行其预期的操作。
  3. 将返回C的本机数组(例如jdouble[])转换为JNI数组(例如jdoubleArray),并返回JNI数组。

C实现“ TestJNIPrimitiveArray.c”是:

 1
 2
 3
 4
五
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28 
 2
 3
 4
五
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28 
#include <jni.h>
 #include <stdio.h>
#include“TestJNIPrimitiveArray.h”
 
JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage
          (JNIEnv * env,jobject thisObj,jintArray inJNIArray){
   //第1步:将传入的JNI jintarray转换为C的jint []
   jint * inCArray =(* env) - > GetIntArrayElements(env,inJNIArray,NULL);
   if(NULL == inCArray)返回NULL;
   jsize length =(* env) - > GetArrayLength(env,inJNIArray);
 
   //第2步:执行预期的操作
   jint sum = 0;
    int i;
   for(i = 0; i <length; i ++){
      sum + = inCArray [i];
    }
   jdouble average =(jdouble)sum / length;
   (* env) - > ReleaseIntArrayElements(env,inJNIArray,inCArray,0); //释放资源
 
   jdouble outCArray [] = {sum,average};
 
   //步骤3:将C的Native jdouble []转换为JNI jdoublearray,并返回 
   jdoubleArray outJNIArray =(* env) - > NewDoubleArray(env,2); //分配
   if(NULL == outJNIArray)返回NULL;
   (* env) - > SetDoubleArrayRegion(env,outJNIArray,0,2,outCArray); //复制
   退出JJIArray;
 } 

JNI原始数组函数

所述JNI原始阵列(jintArrayjbyteArrayjshortArrayjlongArrayjfloatArrayjdoubleArrayjcharArrayjbooleanArray)的功能是:

<span style="color:#000000"><span style="color:#009900">// <em>ArrayType</em>:jintArray,jbyteArray,jshortArray,jlong​​Array,jfloatArray,jdoubleArray,jcharArray,jbooleanArray
// <em>PrimitiveType</em>:int,byte,short,long,float,double,char,boolean
// <em>NativeType</em>:jint,jbyte,jshort,jlong​​,jfloat,jdouble,jchar,jboolean </span>
<em>NativeType</em> * <strong>Get < <em>PrimitiveType</em> > ArrayElements</strong>(JNIEnv * env,<em>ArrayType</em> array,jboolean * isCopy);
void <strong>Release < <em>PrimitiveType</em> > ArrayElements</strong>(JNIEnv * env,<em>ArrayType</em>数组,<em>NativeType</em> * elems,jint模式);
void <strong>Get < <em>PrimitiveType</em> > ArrayRegion</strong>(JNIEnv * env,<em>ArrayType</em>数组,jsize start,jsize length,<em>NativeType</em> * buffer);
void <strong>Set < <em>PrimitiveType</em> > ArrayRegion</strong>(JNIEnv * env,<em>ArrayType</em>数组,jsize start,jsize length,const <em>NativeType</em> * buffer);
<em>ArrayType </em> <strong>New < <em>PrimitiveType</em> > Array</strong>(JNIEnv * env,jsize length);
void * <strong>GetPrimitiveArrayCritical</strong>(JNIEnv * env,jarray数组,jboolean * isCopy);
void <strong>ReleasePrimitiveArrayCritical</strong>(JNIEnv * env,jarray数组,void * carray,jint模式);</span>

该可用于创建一个新的C的本地数组从给定的Java 。可用于复制(或从部分的),并从预先分配的 C本机阵列。GET|Release< PrimitiveType >ArrayElements()jxxx[]jxxxArrayGET|Set<PrimitiveType >ArrayRegion()jxxxArraystartlengthjxxx[]

New<PrimitiveType>Array()可用于分配一个新的jxxxArray给定尺寸的。然后,您可以使用该函数从本机数组中填充其内容。Set< PrimitiveType >ArrayRegion()jxxx[]

这些Get|ReleasePrimitiveArrayCritical()函数不允许在get和release之间阻塞调用。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道格拉斯范朋克

播种花生牛奶自留田

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值