文章目录
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