OS X 上在 Java 项目中通过 JNI 调用 C/C++ 动态链接库
1. 创建 Java 代理类
首先在 Java 项目中创建一个 DemoProxy
类,作为 Java 调用 C/C++ 动态库的代理类,内容如下:
package com.demo;
public class DemoProxy {
static {
System.loadLibrary("demo");
}
public static native double square(double a);
}
这段代码首先在静态代码块中加载动态库 demo
,然后声明一个静态方法 double square(double a)
并用 native
关键字修饰,表明是一个 native 函数,在动态库中实现。
接着在终端中进入项目主目录,执行:
javac src/com/demo/DemoProxy.java -d ./bin
编译生成的 class
文件被放在了主目录下的 bin
目录(若没有需先创建)。
然后执行:
javah -classpath ./bin com.demo.DemoProxy
classpath
后的 ./bin
指定了命令在 bin
目录下寻找类文件,注意 com.demo.DemoProxy
的包名不能少。这会在项目主目录生成一个 com_demo_DemoProxy.h
文件,可以看到内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_demo_DemoProxy */
#ifndef _Included_com_demo_DemoProxy
#define _Included_com_demo_DemoProxy
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_demo_DemoProxy
* Method: square
* Signature: (D)D
*/
JNIEXPORT jdouble JNICALL Java_com_demo_DemoProxy_square
(JNIEnv *, jclass, jdouble);
#ifdef __cplusplus
}
#endif
#endif
这里的 JNIEXPORT jdouble JNICALL Java_com_demo_DemoProxy_square
(JNIEnv *, jclass, jdouble);
也就是我们将来所需调用的函数的声明。
最顶部 #include <jni.h>
包括了 jni.h
头文件,但是在 OS X 系统上,改成 #include <JavaVM/jni.h>
比较好,后面编译的时候就不需要指定头文件目录了。
2. 实现 C/C++ 函数
这里简便起见,创建 test.h
和 test.c
两个文件,都放在项目的 src
目录,分别如下:
//test.h
#ifndef __TEST__
#define __TEST__
double square(double a);
#endif
//test.c
#include "test.h"
double square(double a) {
return a * a;
}
实现了一个计算平方的函数。
3. 实现 Java 和 C/C++ 的连接
JNI 事实上是无法直接调用之前已经写好的 C/C++ 函数的,需要另外实现函数来调用现存的 C/C++ 函数,也就是需要实现先前自动生成的 .h
头文件中声明的函数。这一步中实现的函数起到连接 Java 代码和现存的 C/C++ 代码的作用。
创建一个 demo_proxy.c
文件,放在 src
目录中,包括一下之前生成的头文件,并实现相应函数,代码如下:
#include "../com_demo_DemoProxy.h"
#include "test.h"
JNIEXPORT jdouble JNICALL Java_com_demo_DemoProxy_square (JNIEnv *env, jclass cls, jdouble d) {
return square(d);
}
这里 jdouble
和 jclass
等类型是 Java 数据类型在 C 的对应的映射,具体看这里:JNI 数据类型与 Java 数据类型的映射关系。
然后编译所有的 C 文件,在 src
目录中执行:
gcc -dynamiclib -o ~/Library/Java/Extensions/libdemo.jnilib *.c
如果是 C++ 文件就用 g++
命令。这里的命令将刚才写的 C 函数封装到了一个动态链接库中,注意生成的动态库的名字和后缀:libdemo.jnilib
,OS X 下 JNI 调用的动态库后缀应该是 jnilib
,动态库名字最前面要加 lib
。
4. Java 中调用代理类的函数
这时候不出意外的话,一开始的 Java 项目中就可以通过 DemoProxy 类的静态方法来调用 test.c
中的 C 函数了,如下:
package com.demo;
public class Main {
public static void main(String[] args) {
System.out.println(DemoProxy.square(3.0));
}
}
这样就大功告成了!