文章目錄
java調用c語言主要通過下面這三個步驟來實現的
- 加載c庫
- 找到對應的c函數,有對應的映射規則
- 調用函數
實際的例子
- TestJni.java 文件
- testJni.c 文件
TestJni.java 源代碼
public class TestJni {
static {//1. 使用靜態代碼塊加載 c庫
System.loadLibrary("testJni"); /* libtestJni.so */
}
public static void main (String args[]) {
// 2. java 源碼中 映射到需要調用的 c函數
//調用相應的函數
test_demo();
}
}
在java中調用 c 語言中的 test_demo 函數,有兩種方式
- native static 聲明
public native static void test_demo();
不加native 編譯會報錯
TestJni.java:7: error: missing method body, or declare abstract
public static void test_demo();
^
1 error
public class TestJni {
static {//1. 使用靜態代碼塊加載 c庫
System.loadLibrary("testJni"); /* libtestJni.so */
}
public native static void test_demo();
public static void main (String args[]) {
// 2. java 源碼中 映射到需要調用的 c函數
//調用相應的函數
test_demo();
}
}
- 如果不加 static 不能直接直接調用,需要使用 —》類.test_demo() 來調用
public class TestJni {
static {//1. 使用靜態代碼塊加載 c庫
System.loadLibrary("testJni"); /* libtestJni.so */
}
public native void test_demo();
public static void main (String args[]) {
TestJni t = new TestJni();
// 2. java 源碼中 映射到需要調用的 c函數
//調用相應的函數,如果加了static,直接使用test_demo()即可調用
t.test_demo();
}
}
testJni.c 源代碼
#inlcude <stdio.h>
void test_demo()
{
printf("func:%s, line:%d\n", __func__, __LINE__);
}
int main()
{
return 0;
}
那麼怎麼調用呢?
jni.pdf–>chapter 2 Getting Started
編譯java代碼的命令
javac -encoding gbk TestJni.java
生成c裏面對應的頭文件
javah -jni TestJni
會在目錄下面生成一個TestJni.h
這裏會寫c語言中函數名稱應該寫成什麼樣,其實是 類名+函數名
sgy@ubuntu:~/sgy/java_learn/jni_learn$ cat TestJni.
cat: TestJni.: No such file or directory
sgy@ubuntu:~/sgy/java_learn/jni_learn$ cat TestJni.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJni */
#ifndef _Included_TestJni
#define _Included_TestJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: TestJni
* Method: test_demo
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_TestJni_test_1demo
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
sgy@ubuntu:~/sgy/java_learn/jni_learn$
修改testJni.c文件
#include <jni.h>
#include <stdio.h>
#include "TestJni.h"
JNIEXPORT void JNICALL
Java_TestJni_test_1demo(JNIEnv *env, jobject obj)
{
printf("func:%s, line:%d\n", __func__, __LINE__);
return;
}
int main()
{
return 0;
}
編譯成動態庫
gcc -fPIC -shared -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/linux/ -o libtestJni.so testJni.c
告訴java 動態庫的路徑,需要設置環境變量
export LD_LIBRARY_PATH=.
運行java的命令
java TestJni
第二種方法, testJni.c 源碼
第一種方法,c語言的函數的名字比較固定,第二種可以比較方便的指定對應關係
jni.pdf—>8.4.1 The JNI_OnLoad Handler
void test()
{
printf("func:%s, line:%d\n", __func__, __LINE__);
}
static const JNINativeMethod methods[] = {
{"test_demo", "()V", test},
};
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "TestJni");
if (cls == NULL) {
return JNI_ERR;
}
if ((*env)->RegisterNatives(env, cls, methods, 1) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
- cls = (*env)->FindClass(env, “JNIDemo”); 先要找到對應的類,返回一個cls
- (*env)->RegisterNatives(env, cls, methods, 1) 註冊對應java函數和c函數的對應關係,對應關係存在methods結構體裏面
typedef struct {
char *name; /* Java裏調用的函數名 */
char *signature; /* JNI字段描述符, 用來表示Java裏調用的函數的參數和返回值類型 */
void *fnPtr; /* C語言實現的本地函數 */
} JNINativeMethod;
static const JNINativeMethod methods[] = {
{"test_demo", "()V", test},
};
最後的源代碼
#include <jni.h>
#include <stdio.h>
#include "TestJni.h"
JNIEXPORT void JNICALL
Java_TestJni_test_1demo(JNIEnv *env, jobject obj)
{
printf("func:%s, line:%d\n", __func__, __LINE__);
return;
}
void test()
{
printf("func:%s, line:%d\n", __func__, __LINE__);
}
static const JNINativeMethod methods[] = {
{"test_demo", "()V", test},
};
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "TestJni");
if (cls == NULL) {
return JNI_ERR;
}
if ((*env)->RegisterNatives(env, cls, methods, 1) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
int main()
{
return 0;
}
signature的格式和介紹
jni的字段描述符
對於參數是String類, signature應該寫成 Ljava/lang/String;
對於其他類,統一寫成 Ljava/lang/Object;
java函數怎麼傳遞參數到c函數裏面
1. 基本數據類型,直接使用,直接返回
TestJni.java 裏面的函數改成下面這個樣子
public native static int test_demo(int java_var);
System.out.println(test_demo(1));
testJni.c
jint test(JNIEnv *env, jclass cls, jint java_var)
{
printf("func:%s, line:%d, java_var:%d\n", __func__, __LINE__, java_var);
return 2;
}
執行結果
func:test, line:13, java_var:1
2
2. String類型參數,返回值是String類型
jni.pdf—>P39
TestJni.java 裏面的函數改成下面這個樣子
public native static String test_demo(String java_str);
System.out.println(test_demo("java_str"));
testJni.c
jstring test(JNIEnv *env, jclass cls, jstring java_str)
{
const jbyte *str;
str = (*env)->GetStringUTFChars(env, java_str, NULL);
if (str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
printf("func:%s, line:%d, java_str:%s\n", __func__, __LINE__, str);
return (*env)->NewStringUTF(env, "c_str");;
}
static const JNINativeMethod methods[] = {
{"test_demo", "(Ljava/lang/String;)Ljava/lang/String;", test},
};
執行結果
func:test, line:20, java_str:java_str
c_str
3. 數組類型參數,返回值是int類型
jni.pdf—>P49
TestJni.java 裏面的函數改成下面這個樣子
public native static int test_demo(int array[]);
int array[] = {1, 2, 3};
System.out.println(test_demo(array));
testJni.c
jint test(JNIEnv *env, jclass cls, jintArray java_array)
{
jint i = 0;
jint sum = 0;
jint *cArray;
cArray = (*env)->GetIntArrayElements(env, java_array, NULL);
if (cArray == NULL) {
return 0; /* exception occurred */
}
for (i = 0; i < (*env)->GetArrayLength(env, java_array); i++) {
sum += cArray[i];
}
(*env)->ReleaseIntArrayElements(env, java_array, cArray, 0);
return sum;
}
static const JNINativeMethod methods[] = {
{"test_demo", "([I)I", test},
};
執行結果
6
jni關於數組提供的一些函數接口
PS:疑問
- 如果jni的字段描述符 java函數和C語言函數不匹配執行的時候會有什麼效果?
執行的時候會報錯
Exception in thread "main" java.lang.NoSuchMethodError: Method TestJni.test_demo()V name or signature does not match