JNI 与C++ 的那些事儿

一、  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);

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ym影子

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值