Android C++和JAVA互相調用

Android C++和JAVA互相調用。

1. JNIEnv對象

對於本地函數

JNIEXPORT void JNICALL Java_video1_TestNative_sayHello(JNIEnv * env, jobject obj)

{

cout<<"Hello Native Test !"<

}

JNIEnv類型代表Java環境。通過這個JNIEnv*指針,就可以對Java端的代碼進行操作。如,創建Java類得對象,調用Java對象的方法,獲取Java對象的屬性等。

JNIEnv的指針會被JNI傳送到本地方法的實現函數中來對Java端的代碼進行操作

JNIEnv類中的函數:

NewObject/NewString/NewArray:new新對象

Get/SetField:獲取屬性

Get/SetStaticField:獲取靜態屬性

CallMethod/CallStaticMethod:調用方法

2. Java數據類型與C/C++數據類型的對應關系

可以參考jni.h文件:https://home.pacifier.com/~mmead/jni/cs510ajp/jni.h

Java類型別名  C++本地類型   字節(bit)

booleanjbooleanunsignedchar   8,unsigned

byte jbyte signedchar     8

char jchar unsignedshort   16,unsigned

short jshortshort    16

int jintlong     32

long jlong __int64    64

float jfloatfloat    32

double jdouble double    64

voidvoid     n/a

Object_jobject*jobject

3. 獲取jclass

為瞭能夠在C/C++使用Java類,jni.h頭文件中專門定義瞭jclass類型來表示Java中的Class類

jclass的取得:

JNIEnv類中有如下幾個簡單的函數可以取得jclass

jclass FindClass(const char* clsName) 根據類名來查找一個類,完整類名。

jclass GetObjectClass(jobject obj) 根據一個對象,獲取該對象的類

jclass GetSuperClass(jclass obj) 獲取一個類的父類

FindClass 會在classpath系統環境變量下尋找類,需要傳入完整的類名,註意包與包之間是用"/"而不是"."來分割

如:jclass cls_string= env->FindClass("java/lang/String");

獲取jclass又什麼用,比如你要調用類的靜態方法,靜態屬性就需要通過這個方法來獲取一個類。

4. 本地代碼訪問Java類中的屬性與方法

有瞭類和對象之後,如何才能訪問java中的對象的屬性和方法呢,這就需要用到以下這些方法瞭。

JNI在jni.h頭文件中定義瞭jfieldID,jmethodID類表示Java端的屬性和方法

如何獲取屬性: 在訪問或設置Java屬性的時候,首先就要現在本地代碼中取得代表Java屬性的jfieldID,然後才能在本地代碼中進行Java屬性操作。

如何調用java的方法:調用Java端的方法時,需要取得代表方法的jmethodID才能進行Java方法調用

JNIEnv獲取相應的fieldID和jmethodID的方法:

GetFieldID/GetMethodID

GetStaticFieldID/GetStaticMethodID

GetMethodID也可以取得構造函數的jmethodID。創建Java對象時調用指定的構造函數。

如:env->GetMethodID(data_Clazz,"method_name","()V")

(*jniEnv)->GetMethodID(jniEnv, Clazz,"", "()V");

這個比較特殊,這個是默認構造函數的方法,一般用這個來初始化對象,但是再實際過程中,為瞭快速生成一個實例,一般通過工廠方法類創建jobject

jni.h 對GetMethodID的定義:

jmethodID (JNICALL *GetMethodID)

(JNIEnv *env, jclass clazz, const char *name, const char *sig);

這就引入瞭一個新的問題,什麼是sig,我們後面再說,舉個例子說明

前提說明: JAVA類TestProvider,該類有2個方法分別為String getTime( ),void saysayHello( String str)

jclassTestProvider;

jobjectmTestProvider;

jmethodIDgetTime;

jmethodIDsayHello;

C 中映射類

TestProvider=(*jniEnv)->FindClass(jniEnv,"com/duicky/TestProvider");

C中新建對象

//默認構造函數,不傳參數

jmethodIDconstruction_id=(*jniEnv)->GetMethodID(jniEnv,TestProvider,"","()V");

//通過NewObject來創建對象

jobjectmTestProvider=(*jniEnv)->NewObject(jniEnv,TestProvider,construction_id);

C 中映射方法

靜態:

getTime=(*jniEnv)->GetStaticMethodID(jniEnv,TestProvider,"getTime","()Ljava/lang/String;");

非靜態:

sayHello=(*jniEnv)->GetMethodID(jniEnv,TestProvider,"sayHello","(Ljava/lang/String;)V");

C 中調用 Java的 方法

靜態:

(*jniEnv)->CallStaticObjectMethod(jniEnv,TestProvider,getTime);

非靜態:

(*jniEnv)->CallVoidMethod(jniEnv,mTestProvider,sayHello,jstrMSG);

註意GetXXXMethodID和CallXXXMethod 。

第一個XXX 表示的是映射方法的類型,如: 靜態 跟非靜態

第二個 XXX 表示 調用方法的返回值 ,如:Void,Object,等等。(調用靜態方法的時候Call後面要加Static)

5. sign簽名

對於jmethodID GetMethodID(jclass clazz, const char *name, const char *sign)

clazz代表該屬性所在的類,name表示方法名稱,sign是簽名

那什麼是簽名,簽名是對函數參數和返回值的描述,對同一個函數,在java中允許重載,這個時候就需要這個sign來進行區分瞭。

以下是java類型簽名的描述

用來表示要取得的屬性/方法的類型

類型 相應的簽名

booleanZ

byte B

char C

short S

int I

long J

float F

double D

void V

object L用/分隔包的完整類名:Ljava/lang/String;

Array [簽名[I[Ljava/lang/Object;

Method(參數1類型簽名參數2類型簽名···)返回值類型簽名

特別註意:Object後面一定有分號(;)結束的,多個對象參數中間也用分號(;)來分隔

例子:

方法簽名

voidf1() ()V

intf2(int,long) (IJ)I

booleanf3(int[]) ([I)B

doublef4(String,int) (Ljava/lang/String;I)D

voidf5(int,String[],char)(I[Ljava/lang/String;C)V

圖解簽名:

 

 

使用javap命令來產生簽名

javap -s -p [full class Name]

-s 表示輸出簽名信息

-p 同-private,輸出包括private訪問權限的成員信息

例子:

C:\E\java\workspaces\myeclipseblue\JNITest\bin>javap-s-privatevideo1.TestNative

Compiledfrom"TestNative.java"

publicclassvideo1.TestNativeextendsjava.lang.Object{

publicjava.lang.Stringname;

Signature:Ljava/lang/String;

publicvideo1.TestNative();

Signature:()V

publicintsignTest(int,java.util.Date,int[]);

Signature:(ILjava/util/Date;[I)I

publicnativevoidsayHello();

Signature:()V

publicstaticvoidmain(java.lang.String[]);

Signature:([Ljava/lang/String;)V

}

TestNative完整代碼:

packagevideo1;

importjava.util.Date;

publicclassTestNative{

publicStringname="Test";

publicintnumber=100;

publicintsignTest(inti,Datedate,int[]arr){

System.out.println("SignTest");

return0;

}

//native關鍵字修飾的方法,其內容是C/C++編寫的,java中不必為它編寫具體的實現

publicnativevoidsayHello();

publicstaticvoidmain(String[]args){

System.loadLibrary("NativeCode");

TestNativetn=newTestNative();

tn.sayHello();

}

}

C/C++代碼

#include"video1_TestNative.h"

#include

usingnamespacestd;

JNIEXPORTvoidJNICALLJava_video1_TestNative_sayHello(JNIEnv*env,jobjectobj){

cout<<"HelloNativeTest!"< //因為test不是靜態函數,所以傳進來的就是調用這個函數的對象

//否則就傳入一個jclass對象表示native()方法所在的類

jclassnative_clazz=env->GetObjectClass(obj);

//得到jfieldID

jfieldIDfieldID_prop=env->GetFieldID(native_clazz,"name","Ljava/lang/String;");

jfieldIDfieldID_num=env->GetFieldID(native_clazz,"number","I");

//得到jmethodID

jmethodIDmethodID_func=env->GetMethodID(native_clazz,"signTest","(ILjava/util/Date;[I)I");

//調用signTest方法

env->CallIntMethod(obj,methodID_func,1L,NULL,NULL);

//得到name屬性

jobjectname=env->GetObjectField(obj,fieldID_name);

//得到number屬性

jintnumber=env->GetIntField(obj,fieldID_num);

cout< //修改number屬性的值

env->SetIntField(obj,fieldID_num,18880L);

number=env->GetIntField(obj,fieldID_num);

cout< }

摘要:

1Java類生成c頭文件和庫文件

2 對於c/c++程序,啟動時先啟動jvm,然後獲得對應的java類的對象和方法。然後正常使用。

最近正在做一個C/C++調用java的程序,這裡說的調用java不是使用方式 exec(/path/to/java,…..),而是調用一個class文件中的一個特定的函數。

實踐後總結如下:

1. 安裝 jdk

2. 安裝gcc(Linux自帶有的就無需安裝瞭)

利用JNI(Javanative interface),來實現動態建立javaruntime environment.

第一,C/C++程序中包含頭文件"jni.h"

#include 一般在JAVA_HOME/include 目錄下。

調用jni.h中的方法建立runtime env 然後調用java 程序。

第二,編譯

g++ -o testjava testjava.cpp -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux-L${JRE_HOME}/lib/i386/client -ljvm

以上就是大致思路,現詳細說明過程如下:

#####################################################################################

一、安裝配置Java環境

我的linux是RedHat Enterprise linux 5, 內核版本2.6.18

在Linux系統中安裝Java比較簡單。可以訪問Java download網站或自由軟件庫等,選擇你所有安裝的操作系統類型(Linux,Linux AMD64,Solaris等)。一旦你已經選擇下載文件──要麼是自解壓縮執行文件,要麼是自解壓縮的RPM文件,你都可以安裝它。我下載的是jdk-1_5_0_06-linux-i586.bin:

# mkdir /usr/local/java

# cd /usr/local/java

# cp /home/soft/jdk-1_5_0_06-linux-i586.bin ./

# chmod u+x jdk-1_5_0_06-linux-i586.bin

# ./jdk-1_5_0_06-linux-i586.bin

運行完後生成jdk1.5.0_06目錄,jdk被安裝在/usr/local/java/jdk1.5.0_06/。運行以下執行代碼將得到一個測試結果:

# cd jdk1.5.0_06/bin

[root@localhost bin]# ./java -version

java version "1.5.0_06"

Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05)

Java HotSpot(TM) Client VM (build 1.5.0_06-b05, mixed mode, sharing)

為瞭能夠使用Java,需要設置如下環境變量:

JAVA_HOME=/usr/local/java/jdk1.5.0_06

PATH=$PATH:/usr/local/java/jre1.5.0_05/bin

export JAVA_HOME PATH

export JRE_HOME=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JRE_HOME/lib/i386:$JRE_HOME/lib/i386/client

註意JRE_HOME的配置,若機器上沒有jre環境,則安裝jre,安裝方法類似安裝jdk

設置完後可以查看變量的值

[root@localhost bin]# echo $JAVA_HOME

/usr/local/java/jdk1.5.0_06

[root@localhost bin]# echo $PATH

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/zhangp/bin:/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre/bin:/usr/local/java/jdk1.5.0_06/bin

二、編寫簡單的Java程序

package com.test;

public class MyTest {

public MyTest(){

super();

}

public static int add(int a,int b) {

return a+b;

}

public boolean judge(boolean bool) {

return !bool;

}

}

編譯Java程序:

#javac MyTest.java

編譯之後生成MyTest.class,將其放置於當前目錄的com/test目錄下,C++程序的JNI調用時會使用相關方法在com/test目錄下查找該class。

三、C++程序

#include

#include

#include

#include

#include

jstring stoJstring(JNIEnv* env, const char* pat)

{

jclass strClass = env->FindClass("java/lang/String");

jmethodID ctorID = env->GetMethodID(strClass, "", "([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);

}

char* jstringTostring(JNIEnv* env, jstring jstr)

{

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;

}

using namespace std;

int main()

{

JavaVMOption options[2];

JNIEnv *env;

JavaVM *jvm;

JavaVMInitArgs vm_args;

long status;

jclass cls;

jmethodID mid;

jint square;

jboolean jnot;

jobject jobj;

options[0].optionString = "-Djava.compiler=NONE";

options[1].optionString = "-Djava.class.path=.";

//options[2].optionString = "-verbose:jni"; //用於跟蹤運行時的信息

vm_args.version = JNI_VERSION_1_4; // JDK版本號

vm_args.nOptions = 2;

vm_args.options = options;

vm_args.ignoreUnrecognized = JNI_TRUE;

status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

if(status != JNI_ERR){

printf("create java jvm success\n");

cls = env->FindClass("com/test/MyTest");// 在這裡查找ava類

if(cls !=0){

printf("find java class success\n");

// 構造函數

mid = env->GetMethodID(cls,"","()V");

if(mid !=0){

jobj=env->NewObject(cls,mid);

std::cout << "init ok" << std::endl;

}

// 調用add函數

mid = env->GetStaticMethodID( cls, "add", "(II)I");

if(mid !=0){

square = env->CallStaticIntMethod( cls, mid, 5,5);

std::cout << square << std::endl;

}

// 調用judge函數

mid = env->GetMethodID( cls, "judge","(Z)Z");

if(mid !=0){

jnot = env->CallBooleanMethod(jobj, mid, 1);

if(!jnot) std::cout << "Boolean ok" << std::endl;

}

}

else{

fprintf(stderr, "FindClass failed\n");

}

jvm->DestroyJavaVM();

fprintf(stdout, "Java VM destory.\n");

return 0;

}

else{

printf("create java jvm fail\n");

return -1;

}

}

編譯該C++程序(前提:Java環境已設置好,即JAVA_HOME、PATH、JRE_HOME、LD_LIBRARY_PATH)

[root@localhost jni]# g++ -o testjava testjava.cpp -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -L${JRE_HOME}/lib/i386/client -ljvm

編譯好後可以用ldd testjava查看其使用的鏈接庫的正確性。

運行:

[root@localhost jni]# ./testjava

create java jvm success

find java class success

init ok

10

Boolean ok

Java VM destory.

JRE_HOME和LD_LIBRARY_PATH要設置好,編譯C++程序時要使用JRE_HOME下面的libjvm.so動態庫(一開始我使用網上說的使用JAVA_HOME目錄下的libjvm.so,結果出現下面錯誤

# An unexpected error has been detected by HotSpot Virtual Machine:

#

#SIGSEGV (0xb) at pc=0xb6d3dbe3, pid=14454, tid=2773482416

#

# Java VM: Java HotSpot(TM) Server VM (1.5.0_11-b03 mixed mode)

。。。。

You May Also Like