Fenrier Lab

Java 本地调用(jni)编程基础

首先说明一下,根据经验来看,得益于高性能的 Java 虚拟机,使用纯 Java 实现的代码不一定就比 c 语言的慢。但是在大量的 c 语言库中还藏有不少珍宝,或许是没必要重复造轮子,或许是没有信心写出比那些经过多年考验的代码更健壮的程序,使用 Java 代码调用 C/C++ 仍然具有很高的价值。

一个 hello world 例子

本地方法的定义类似于接口函数,因为都还没实现嘛,不同的地方在于需要在返回值前面加上 native 关键字,例如下面的 Hello 类定义了 本地方法 say()

//Hello.java
public class Hello{
	public native void say(String something);
}

这段程序需要 c 语言实现,首先使用 javac 工具在当前目录下生成头文件(以前有专门的 javah 工具,但是如果在新版本的java中使用,会警告说此工具已经准备被移除)

javac Hello.java -h .\

头文件内容如下

//Hello.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */

#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Hello
 * Method:    say
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_Hello_say
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

需要注意的是,这段代码由 javac 工具自动生成,最好不要修改任何内容。其函数命名也极具规律,遵循 Java_类名_函数名 这种格式。

然后是新建一个 Hello.c 文件实现头文件函数定义

#include <jni.h>

#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Hello
 * Method:    say
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_Hello_say
  (JNIEnv * env, jobject jobj, jstring jstr){
	  const char *c_str = NULL;
	  c_str = (*env) -> GetStringUTFChars(env, jstr, NULL);
	  printf(c_str);
	  (*env) -> ReleaseStringUTFChars(env, jstr, c_str);
	  return;
  }

#ifdef __cplusplus
}
#endif
#endif

最后再编译并生成动态链接库

gcc -c Hello.c -I "%JAVA_HOME%\include" -I "%JAVA_HOME%\include\win32"
gcc -shared -o Hello.dll Hello.o

使用下述代码调用

//Main.java
public class Main{
	public static void main(String[] args ) {
		System.loadLibrary("Hello");
		new Hello().say("hello jni world");
	}
	
}

本文遵守 CC-BY-NC-4.0 许可协议。

Creative Commons License

欢迎转载,转载需注明出处,且禁止用于商业目的。