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.htest.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);
}

这里 jdoublejclass 等类型是 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));
    }
}

这样就大功告成了!

5. 参考资料

评论