一、 JNI基本概念
JNI全称 JavaNative Interface,指java本地接口,使java程序可以调用本地应用/库,也可以被其他程序调用。
本地程序一般是由其他语言(c/c++)编写,并且被编译为基于本地硬件和操作系统的程序。
二、JNI工作原理
在JNI中,本地函数是通过一个独立的.c或.cpp文件来实现的(C++为JNI提供的界面会更简洁一些)。
当JVM调用该函数时,它传递了一个JNIEnv指针、一个jobject指针和通过Java方法定义的Java参数,
JNI函数的形式如下:
JNIEXPORT void JNICALLJava_ClassName_MethodName
(JNIEnv *env, jobject obj)
{
//Method native implemenation
}
env指针是一个包含了JVM接口的结构,它包含了与JVM进行交互以及与Java对象协同工作所必需的函数,
示例中的JNI函数可以在本地数组和Java数组类型之间、本地字符串和Java字符串类型之间进行转换,
其功能还包括对象的实例化、抛出异常等。
本地代码通过调用JNI的函数来访问JVM,这是通过一个界面指针实现的(界面指针实际上是指向指针的指针),
该指针指向一个指针数组,数组中的每个指针都指向了一个界面函数,而每个界面函数都是在数组中预先定义过的。
本地方法将JNI界面指针当作一个参数,如果在同一个Java线程中,出现对该本地方法的多重调用,
JVM则保证传递相同的界面指针到本地方法。不过,一个本地方法可以被不同的Java线程调用,
因而也可能会收到不同的JNI界面指针。
java中调用本地方法是通过System.loadLibrarry方法加载的,在以下的例子中,类的初始化方法加载了一个指定平台的
本地类库,该类库中定义了本地方法:
packagepkg;
class Cls {
native double f(inti, String s);
static {
System.loadLibrary("*.dll");
}
}
数据类型映射
基本数据类型比如整型、字符等等,是在Java和本地代码间进行拷贝的,而其他的自定义Java对象则是通过引用来传递的。
三、JNI怎样实现与c++的交互的
1、编写一个Java文件,对于需要C/C++实现的方法,声明为native(本地方法)
例如:publicnative String Call_C();
2、通过javac编译成class,再通过javah生成供c/c++使用的.h头文件(注意:编译javac时要加上-encoding utf-8参数)
3、c/c++对其头文件的实现,例如:
#include"com_xcl_jini_XclJini.h"
#include <string.h>
#include "ConvertJini.h"
/*
*Class: com_xcl_jini_XclJini
*Method: GetVersion
*Signature: ()I
*/
JNIEXPORT jint JNICALLJava_com_xcl_jini_XclJini_GetVersion
(JNIEnv *, jobject)
{
printf("C++: GetVersion() Version 1.1\n");
return 0;
}
/*
*Class: com_xcl_jini_XclJini
*Method: GetStatus
*Signature: ()I
*/
JNIEXPORT jint JNICALLJava_com_xcl_jini_XclJini_GetStatus
(JNIEnv *, jobject)
{
printf("C++: GetStatus()\n");
printf("C++: Running.....\n");
printf("C++: GetStatus() end.\n");
return 1;
}
/*
*Class: com_xcl_jini_XclJini
*Method: GetMsg
*Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALLJava_com_xcl_jini_XclJini_GetMsg
(JNIEnv * env, jobject jobj)
{
printf("C++: GetMsg()\n");
char *ret = "C++ Message.";
ConvertJini cj ;
jstring jret = cj.stoJstring(env,ret);
printf("C++: GetMsg() end.\n");
return jret;
}
/*
*Class: com_xcl_jini_XclJini
*Method: SendMsg
*Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALLJava_com_xcl_jini_XclJini_SendMsg
(JNIEnv * env, jobject jobj, jstring msg)
{
printf("C++: SendMsg()\n");
jboolean b = true;
char s[80];
memset(s, 0, sizeof(s));
strcpy_s(s ,(char*)env->GetStringUTFChars(msg, &b));
printf("C++: Java Message:%s\n", s);
env->ReleaseStringUTFChars(msg , NULL);
printf("C++: SendMsg() end.\n");
return0;
}
四、怎样实现静态对静态,动态对动态
java调用c++代码的最完美方法
JNI层是以C方式实现的,逻辑上讲还属于Java类的。
C与C++的语法是通用的,因此从理论上讲可以将JNI(C层)代码和C++层代码可以放在相同的文档中。
一、保持JNI层稳定的原则:“静态对静态,动态对动态”
JNI层既可以创建Java层对象,也可以C++层对象。需要特别注意的是:JNI层(C层)的全局或静态(static)变量只适合存储静态的数据,例如methodID或fieldID等。把动态的Java或C++对象引用储存于JNI(C层)的全局变量,会导致JNI层(C层)的不稳定性。所以:“静态对静态”的原则是:JNI层的全局变量或静态变量只能存储Java层或C++层的静态数据。
“动态对动态”的原则是:JNI层动态创建的对象只能存储在Java层或C++层中动态创建的对象中。
以下例子展示了如何在Java层存储JNI层动态创建的C++对象:
二、c++层代码
#pragma once
class CFood
{
private:
char* name;
double price;
public:
CFood(char* name, double price)
{
this->name = name;
this->price = price;
}
~CFood()
{
if(name != NULL)
{
free(name);
name = NULL;
}
}
const char* getName()
{
return this->name;
}
double getPrice()
{
return this->price;
}
};
三、JAVA层代码
Java层为了使用上述代码,引入一个新的类Food,如下:
publicclass Food {
static {
System.loadLibrary("jniFood");
}
// 用于存储C++层的对象指针
private int mObject;
public Food(String name, double price){
setFoodParam(name, price);
}
public native void setFoodParam(String name,double price);
public native String getName();
public native double getPrice();
protected native void finalize();
public static void main(String[] args){
Food f1 = new Food("面包",1.99);
Food f2 = new Food("牛奶",3.99);
System.out.println(String.format("食物:%s, 单价:%f",f1.getName(), f1.getPrice()));
System.out.println(String.format("食物:%s, 单价:%f",f2.getName(), f2.getPrice()));
}
}
其中,声明了本地方法,需要注意的是创建一个int型字段用来存放C++层对象的指针。另外需要注意的是通过本地方法
finalize(),来析构c++对象。
四、JNI层代码
头文件:
#include <jni.h>
/* Header for class test2_Food */
#ifndef _Included_test2_Food
#define _Included_test2_Food
#ifdef __cplusplus
extern "C" {
#endif
/*
*Class: test2_Food
*Method: setFoodParam
*Signature: (Ljava/lang/String;D)V
*/
JNIEXPORT void JNICALLJava_test2_Food_setFoodParam
(JNIEnv *, jobject, jstring, jdouble);
/*
*Class: test2_Food
*Method: getName
*Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALLJava_test2_Food_getName
(JNIEnv *, jobject);
/*
*Class: test2_Food
*Method: getPrice
*Signature: ()D
*/
JNIEXPORT jdouble JNICALLJava_test2_Food_getPrice
(JNIEnv *, jobject);
/*
*Class: test2_Food
*Method: finalize
*Signature: ()V
*/
JNIEXPORT void JNICALLJava_test2_Food_finalize
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
源文件:
#include "stdafx.h"
#include <stdlib.h>
#include "test2_Food.h"
#include "Food.h"
char* jstring2string(JNIEnv* env, jstringjstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes","(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid,strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
jstring char2Jstring(JNIEnv* env, constchar* pat)
{
jclass strClass =env->FindClass("Ljava/lang/String;");
jmethodID ctorID = env->GetMethodID(strClass,"<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = env->NewByteArray(strlen(pat));
env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, bytes,encoding);
}
CFood* getCFood(JNIEnv *env, jobjectthiz)
{
jclass clazz = env->GetObjectClass(thiz);
jfieldID fid = env->GetFieldID(clazz, "mObject","I");
jint p = env->GetIntField(thiz, fid);
return (CFood*)p;
}
void setFood(JNIEnv *env, jobject thiz,const CFood* pFood)
{
jclass clazz = env->GetObjectClass(thiz);
jfieldID fid = env->GetFieldID(clazz, "mObject","I");
env->SetIntField(thiz, fid, (jint)pFood);
}
JNIEXPORT void JNICALLJava_test2_Food_setFoodParam
(JNIEnv *env, jobject thiz, jstring name, jdouble price)
{
//const char* tempName = env->GetStringUTFChars(name, 0);
char* tempName = jstring2string(env, name);
double tempPrice = price;
CFood* pFood = new CFood(tempName, tempPrice);
setFood(env, thiz, pFood);
}
JNIEXPORT jstring JNICALLJava_test2_Food_getName
(JNIEnv *env, jobject thiz)
{
CFood* pFood = getCFood(env, thiz);
const char* name = pFood->getName();
return char2Jstring(env, name);
}
JNIEXPORT jdouble JNICALLJava_test2_Food_getPrice
(JNIEnv *env, jobject thiz)
{
CFood* pFood = getCFood(env, thiz);
return pFood->getPrice();
}
JNIEXPORT void JNICALLJava_test2_Food_finalize
(JNIEnv *env, jobject thiz)
{
CFood* pFood = getCFood(env, thiz);
if (pFood != NULL)
{
delete pFood;
pFood = NULL;
setFood(env, thiz, pFood);
}
}
五、c++中回调JAVA方法
如上所属在java中保存c++对象堪称完美,不会有任何副作用。但是在c++中保存java对象就比较麻烦,jobject并不可靠,
用它在c++中保存Java对象有很大隐患,jobject的值会不断变化,不能保存在c++代码中,可能与Java的GC有关,JVM不断
整理内存,导致Java对象的内存移动。
建议始终将JNI的接口参数JNIEnv *和 jobject 一起传参使用。以下是一份实现代码:
package test1;
class MyPrint {
public void onPrint(String text) {
System.out.println(text);
}
}
public class MyFile {
private MyPrint myPrint = null;
static {
System.loadLibrary("jniTest5");
}
private int mObject;
public MyFile() {
init();
}
public void onPrint(String text) {
myPrint.onPrint(text);
}
public void setPrint(MyPrint myPrint) {
this.myPrint = myPrint;
}
public void setMyPrint(MyPrint myPrint) {
setPrint(myPrint);
this.registerPrint(myPrint);
}
public void myPrint(String text) {
this.doPrint(text);
}
public native void init();
public native void registerPrint(MyPrint myPrint);
public native void doPrint(String text);
protected native void finalize();
public static void main(String[] args) {
MyFile myFile = new MyFile();
MyPrint myPrint = new MyPrint();
myFile.setMyPrint(myPrint);
myFile.doPrint("hello world!");
myFile.doPrint("again!");
myFile.doPrint("next!");
}
}
JNI接口层:
头文件:test1_MyFile.h
#include <jni.h>
/* Header for class test1_MyFile */
#ifndef _Included_test1_MyFile
#define _Included_test1_MyFile
#ifdef __cplusplus
extern "C" {
#endif
/*
*Class: test1_MyFile
*Method: init
*Signature: ()V
*/
JNIEXPORT void JNICALLJava_test1_MyFile_init
(JNIEnv *, jobject);
/*
*Class: test1_MyFile
*Method: registerPrint
*Signature: (Ltest1/MyPrint;)V
*/
JNIEXPORT void JNICALLJava_test1_MyFile_registerPrint
(JNIEnv *, jobject, jobject);
/*
*Class: test1_MyFile
*Method: doPrint
*Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_test1_MyFile_doPrint
(JNIEnv *, jobject, jstring);
/*
*Class: test1_MyFile
*Method: finalize
*Signature: ()V
*/
JNIEXPORT void JNICALLJava_test1_MyFile_finalize
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
源文件:
#include "stdafx.h"
#include <jni.h>
#include "MyFile.h"
#include "test1_MyFile.h"
char* jstring2string(JNIEnv* env, jstringjstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode =env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes","(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid,strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
CMyFile* getMyFile(JNIEnv *env, jobjectthiz)
{
jclass clazz = env->GetObjectClass(thiz);
jfieldID fid = env->GetFieldID(clazz, "mObject","I");
jint p = env->GetIntField(thiz, fid);
return (CMyFile*)p;
}
void setMyFile(JNIEnv *env, jobject thiz,CMyFile* pFile)
{
jclass clazz = env->GetObjectClass(thiz);
jfieldID fid = env->GetFieldID(clazz, "mObject","I");
env->SetIntField(thiz, fid, (jint)pFile);
}
JNIEXPORT void JNICALLJava_test1_MyFile_init
(JNIEnv *env, jobject thiz)
{
CMyFile* pFile = new CMyFile();
setMyFile(env, thiz, pFile);
}
JNIEXPORT void JNICALLJava_test1_MyFile_registerPrint
(JNIEnv *env, jobject thiz, jobject jobj)
{
CMyPrint* pPrint = new CMyPrint();
CMyFile* pFile = getMyFile(env, thiz);
pFile->registerPrint(pPrint);
}
JNIEXPORT void JNICALLJava_test1_MyFile_doPrint
(JNIEnv *env, jobject thiz, jstring strText)
{
CMyFile* pFile = getMyFile(env, thiz);
char* pText = jstring2string(env, strText);
pFile->doPrint(env, thiz, pText);
if (pText != NULL)
{
free(pText);
pText = NULL;
}
}
JNIEXPORT void JNICALL Java_test1_MyFile_finalize
(JNIEnv *env, jobject thiz)
{
CMyFile* pFile = getMyFile(env, thiz);
if (pFile != NULL)
{
delete pFile;
pFile = NULL;
setMyFile(env, thiz, pFile);
}
}
c++层:
MyPrint.h
#include "stdafx.h"
#include <jni.h>
#include <stdlib.h>
class CMyPrint
{
public:
jstring char2Jstring(JNIEnv* env, const char* pat)
{
jclass strClass =env->FindClass("Ljava/lang/String;");
jmethodID ctorID = env->GetMethodID(strClass,"<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = env->NewByteArray(strlen(pat));
env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, bytes,encoding);
}
// 如下传递JNIEnv* 和 jobject来获取Java对象,然后回调
void onPrint(JNIEnv *env, jobject thiz, char* text)
{
jclass clazz = env->GetObjectClass(thiz);
jmethodID methodID = env->GetMethodID(clazz, "onPrint","(Ljava/lang/String;)V");
jstring strText = char2Jstring(env, text);
env->CallVoidMethod(thiz, methodID, strText);
}
};
MyFile.h
#pragma once
#include "MyPrint.h"
class CMyFile
{
private:
CMyPrint* pPrint;
public:
~CMyFile()
{
if (pPrint != NULL)
{
delete pPrint;
pPrint = NULL;
}
}
void registerPrint(CMyPrint* pPrint)
{
this->pPrint = pPrint;
}
void doPrint(JNIEnv *env1, jobject thiz, char* text)
{
pPrint->onPrint(env1, thiz, text);
}
};
五、遇到的问题以及处理方法
1、问题:在eclipse中可以正常运行java程序,而在cmd命令行中使用java命令运行会出现无法加载主类的问题。
解决方法:需要在*.java所在目录使用javac进行编译,然后在com所在目录下使用java命令运行。
2、在进行行列两层for循环赋值的时候一定要注意理清逻辑关系。
3、问题:给结构体数组赋值的时候出现只赋值给前三个元素,后三个元素的值为随机值
解决方法:在结构体定义的时候元素的类型值需要和赋值函数的类型一致,如:(env)->SetIntField(objectNewEng, iR,tmp[i].R);
4、问题:找不到java中的实例类
解决方法:jclassobjectClass = (env)->FindClass("com/jni/demo/H8Color");需要从com找起。
5、问题:运行java程序报错提示:can'tfind depent library
解决方法:需要将所使用到的dll都显示调用,如jni_test.dll调用了libpng.dll,需要在Java代码中
System.loadlibrary(libpng.dll);System.loadLibrary(jni_test.dll);