c 通过jni调用java_使用c通过jni调用java
編譯環(huán)境:
fedora16
gcc (GCC) 4.6.3 20120306 (Red Hat 4.6.3-2)
java version "1.6.0_31"
Java(TM) SE Runtime Environment (build 1.6.0_31-b04)
Java HotSpot(TM) Server VM (build 20.6-b01, mixed mode)
準備工作:
首先需要安裝jdk和gcc(或者其他c編譯器也可以)并配置相應(yīng)的環(huán)境變量(可自行在網(wǎng)上搜索,這個資料很多的),配好之后在命令行運行g(shù)cc --version和java -version出現(xiàn)版本號就代表安裝配置成功了:
[roysong@roysong c]$ gcc --version
gcc (GCC) 4.6.3 20120306 (Red Hat 4.6.3-2)
Copyright ? 2011 Free Software Foundation, Inc.
本程序是自由軟件;請參看源代碼的版權(quán)聲明。本軟件沒有任何擔保;
包括沒有適銷性和某一專用目的下的適用性擔保。
[roysong@roysong c]$ java -version
java version "1.6.0_31"
Java(TM) SE Runtime Environment (build 1.6.0_31-b04)
Java HotSpot(TM) Server VM (build 20.6-b01, mixed mode)
然后需要將jdk中的虛擬機庫配置到環(huán)境路徑中,我是在/etc/ld.so.conf.d/下面新建了一個jni-i386.conf文件,內(nèi)容如下:
/usr/java/jdk1.6.0_31/jre/lib/i386/client
然后運行一下ldconfig –v,讓配置文件起作用,也可以直接編輯/etc/ld.so.conf文件,將這個路徑加入進去.這個路徑的含義就是$JAVA_HOME/jre/lib/i386/client,其中
$JAVA_HOME換成你自己的jdk安裝路徑就可以了.這是為了找到libjvm.so.因為我是32位的機器,所以路徑中是i386,
我估計64位的機器這個目錄是不一樣的,根據(jù)libjvm.so文件的路徑自行替換即可.
程序編寫和編譯:
程序就分成了c的部分和java的部分,首先我們先看看java的部分,我是把在同一目錄下新建了兩個文件夾分別叫c和java,
作為c程序的路徑和java的classpath,然后在java文件夾下面新建包com.test.base64,java程序的目錄結(jié)構(gòu)即:
java/com/test/base64.接著,我們在base64目錄下面新建一個Hello.java:
package com.test.base64;
public class Hello{
public String hello (String name){
return "hello,"+name;
}
}
結(jié)構(gòu)非常簡單,不過有參數(shù)有返回值就可以代表大部分情況了.然后我們使用javac對這個文件進行編譯以產(chǎn)生一個class
文件:
[roysong@roysong java]$ cd com/test/base64/
[roysong@roysong base64]$ javac Hello.java
[roysong@roysong base64]$ ls
Hello.class Hello.java
編譯成功后,在base64目錄下會出現(xiàn)Hello.class這個文件.
然后我們回到c文件夾,新建一個c源文件,我的是cfjIns.c,開始編寫c的程序:
#include
#include
/**
*初始化jvm
*/
JNIEnv* create_vm() {
JavaVM* jvm;
JNIEnv* env;
JavaVMInitArgs args;
JavaVMOption options[1];
args.version = JNI_VERSION_1_6;
args.nOptions = 1;
options[0].optionString = "-Djava.class.path=../java";
args.options = options;
args.ignoreUnrecognized = JNI_FALSE;
JNI_CreateJavaVM(&jvm, (void **)&env, &args);
return env;
}
這是cfjIns.c的第一部分,頭文件聲明
#include
非常重要,這是引用的$JAVA_HOME/include/jni.h,一會兒我們在編譯時會加上這個路徑.
其次就是
args.version = JNI_VERSION_1_6;
這是jni的版本信息,需要跟你自己的jdk中jni版本對應(yīng),jdk1.6和jdk1.7的jni版本都是上面這個.
options[0].optionString = "-Djava.class.path=../java";
這個參數(shù)是指明你自己java程序的類路徑(classpath),因為我的c文件夾和java文件夾在同一目錄下,所以我用了一個
相對路徑來指明classpath.
cfjIns.c中獲取類定義的函數(shù):
/**
* 根據(jù)全限定類名來獲取類的定義
*/
jclass create_class(JNIEnv* env,char *className){
jclass cls = (*env)->FindClass(env,className);
if(cls == 0){
printf("class-[%s] find error\n",className);
return;
}
return cls;
}
這個函數(shù)有兩個參數(shù),第一個就是我們上面通過create_vm函數(shù)創(chuàng)建的jvm環(huán)境,第二個是全限定的類名字符串,
比如:"com/test/base64/Hello",返回值即對應(yīng)的類.
cfjIns.c中獲取類實例的函數(shù):
/**
*通過無參構(gòu)造函數(shù)來獲取對應(yīng)類的實例
*/
jobject getInstance(JNIEnv* env, jclass obj_class)
{
jmethodID construction_id = (*env)->GetMethodID(env,obj_class, "", "()V");
jobject obj = (*env)->NewObject(env,obj_class, construction_id);
if(obj == 0){
printf("java class instance failed\n");
return;
}
return obj;
}
這個函數(shù)的第一個參數(shù)是jvm環(huán)境,第二個是通過上面的create_class函數(shù)獲取的類定義.
cfjIns.c中獲取方法定義的函數(shù):
/**
* 根據(jù)類\方法名\返回值\參數(shù)獲取類中對應(yīng)的方法定義
*/
jmethodID get_method(JNIEnv* env,jclass cls,char *methodName,char *key){
jmethodID mid = (*env)->GetMethodID(env,cls,methodName,key);
if(mid == 0){
printf("method-%s is not found\n",methodName);
return;
}
return mid;
}
這兒我們需要注意的是最后一個參數(shù)字符串key,這個是方法簽名,用于指明方法的參數(shù)和返回值類型,格式是
"(參數(shù)類型聲明)返回值類型聲明".類型聲明的值參照下面的對應(yīng)表:
Java類型
對應(yīng)的簽名
boolean
Z
byte
B
char
C
shrot
S
int
I
long
L
float
F
double
D
void
V
Object
L用/分割包的完整類名;? Ljava/lang/String;
Array
[簽名?? ? ? [I ? ? ? [Ljava/lang/String;
舉個例子,如果方法的參數(shù)是int,返回值是void,那么方法的簽名就是"(I)V";如果方法的參數(shù)是String,返回值
也是String,則方法的簽名就是"(Ljava/lang/String;)Ljava/lang/String;",注意,如果是引用類型,類名后面必須
帶有一個分號.
我們也可以通過javap來查看類中方法參數(shù)和返回值的簽名:
[roysong@roysong c]$ cd ../java/com/test/base64/
[roysong@roysong base64]$ javap -s -private Hello
Compiled from "Hello.java"
public class com.test.base64.Hello extends java.lang.Object{
public com.test.base64.Hello();
Signature: ()V //構(gòu)造函數(shù)的簽名
public java.lang.String hello(java.lang.String);
Signature: (Ljava/lang/String;)Ljava/lang/String; //hello方法的簽名
}
然后是一個將c語言中的字符串轉(zhuǎn)換為java中String的工具函數(shù):
/**
* 轉(zhuǎn)換c中的字符串為java.lang.String,這個方法是從網(wǎng)上找到的,感謝原作者天末涼風(fēng)
*/
jstring stoJstring(JNIEnv* env, const char* pat)
{
jclass strClass = (*env)->FindClass(env,"Ljava/lang/String;");
jmethodID ctorID = (*env)->GetMethodID(env,strClass, "", "([BLjava/lang/String;)V");
jbyteArray bytes = (*env)->NewByteArray(env,strlen(pat));
(*env)->SetByteArrayRegion(env,bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = (*env)->NewStringUTF(env,"utf-8");
return (jstring)(*env)->NewObject(env,strClass, ctorID, bytes, encoding);
}
有了上面的例子,這個函數(shù)就很好理解了,首先獲取到j(luò)ava.lang.String的類定義,然后獲取構(gòu)造函數(shù),然后將c中的字符串
轉(zhuǎn)化為字節(jié)流并設(shè)置編碼格式,最后產(chǎn)生一個新的java.lang.String對象并返回.
這下我們就準備齊全了,開始編寫調(diào)用函數(shù):
void invoke(){
JNIEnv* env = create_vm(); //初始化java虛擬機
jclass cls = create_class(env,"com/test/base64/Hello");//根據(jù)類名找到對應(yīng)的類
jobject obj = getInstance(env,cls);//然后根據(jù)類獲取對應(yīng)的實例
jmethodID hello = get_method(env,cls,"hello","(Ljava/lang/String;)Ljava/lang/String;");//根據(jù)類\方法名和簽名獲取到對應(yīng)的方法
jstring name_str = (*env)->CallObjectMethod(env,obj,hello,stoJstring(env,"a"));//傳入?yún)?shù)調(diào)用方法
const char* pname = (*env)->GetStringUTFChars(env,name_str, NULL);//將返回的java字符串轉(zhuǎn)換為c字符串
printf("the result is:%s\n",pname);//打印出調(diào)用的結(jié)果
}
調(diào)用函數(shù)編寫完成后,用一個main函數(shù)來運行一下查看結(jié)果:
int main(int argc, char **argv) {
invoke();
}
至此,c的源代碼cfjIns.c就編寫完成了,我們開始編譯cfjIns.c:
[roysong@roysong c]$ gcc -o cfj cfj.c -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -L$JAVA_HOME/jre/lib/i386/client -ljvm
[roysong@roysong c]$ ls
cfjIns cfjIns.c
編譯命令中我們使用-I選項加入了兩個頭文件路徑,$JAVA_HOME/include這個是為了引用到j(luò)ni.h,而
$JAVA_HOME/include/linux這個為了引用到j(luò)ni_md.h,因為jni.h中有對jni_md.h的引用.我的操作系統(tǒng)是fedora,所以
jni_md.h在$JAVA_HOME/include的linux文件夾下面,其他操作系統(tǒng)可能路徑不同,找到j(luò)ni_md.h文件的路徑進行替換
即可.使用-L選項是為了引用到j(luò)ava虛擬機的庫文件libjvm.so,-l選項(注意,這是小寫的L,而不是大寫的i)是聲明具體引用
的庫jvm.如果libjvm.so文件的路徑與我的不同,找到libjvm.so文件的路徑進行替換即可.
編譯成功后,目錄下面會出現(xiàn)一個可運行的cfj文件,如果一切順利,我們運行它就可以得到預(yù)期的結(jié)果:
[roysong@roysong c]$ ./cfjIns
hello,a
顯示正常,調(diào)用完成
總結(jié)
以上是生活随笔為你收集整理的c 通过jni调用java_使用c通过jni调用java的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (递归3)最大公约数
- 下一篇: (STL,map,queue)团体队列