简单介绍下 Java 和 C 程序的相互调用。
Java 调用 C
Java 可以通过 JNI 调用 C 程序,这里通过一个 HelloWorld
的 Java 程序调用 helloFromC
函数,一个保存在 ctest 的共享库中函数。
/* HelloWorld.java */
public class HelloWorld {
native void helloFromC(); /* (1) */
static {
System.loadLibrary("ctest"); /* (2) */
}
static public void main(String argv[]) {
HelloWorld helloWorld = new HelloWorld();
helloWorld.helloFromC(); /* (3) */
}
}
简单介绍下上述的步骤:
- 通知 JVM 存在
helloFromC()
函数; - 加载 ctest 动态库,该库中定义了上述的函数;
- 直接调用上述的定义的函数。
即是现在还没有实现动态库,实际上仍可以编译 Java 程序,因为默认会在加载时可以找到相应的函数;只是在真正执行函数时会报错。
$ javac HelloWorld.java
$ java HelloWorld
Exception in thread "main" java.lang.UnsatisfiedLinkError: no ctest in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at HelloWorld.<clinit>(HelloWorld.java:5)
接下来,就需要通过 C 创建一个 ctest 库。首先,通过上面生成的 .class
文件创建相应的 C 语言头文件。
$ javah HelloWorld
该命令会生成 HelloWorld.h
头文件,包含了如下内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: helloFromC
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_helloFromC
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
直接从上述的文件中复制函数的声明,并生成如下的文件。
/* ctest.c */
#include <jni.h>
#include <stdio.h>
JNIEXPORT void JNICALL Java_HelloWorld_helloFromC
(JNIEnv * env, jobject jobj)
{
printf("Hello from C!\n");
}
接着就是生成动态库了,不同的平台对应 jni.h 文件位置有所区别,例如 CentOS 中可以通过如下命令查看头文件所在路径,并编译。
$ rpm -ql `rpm -qa | grep "java.*-openjdk-devel"` | grep 'jni.h'
$ gcc -o libctest.so -shared -I/path/to/jdk/headers ctest.c -lc
其中在 Mac 中要将 .so
文件替换为 .dylib
,在 Windows 中替换为 .dll
文件,编译完成后,就可以通过如下方式运行:
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD; java HelloWorld
C 调用 Java
书写 C 文件,模拟 JDK 中自带的 Java 命令。
#include <jni.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char*argv[])
{
JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options[1];
jobjectArray applicationArgs;
jstring appArg;
/* Setting VM arguments */
vm_args.version = JNI_VERSION_1_2;
vm_args.ignoreUnrecognized = JNI_TRUE;
vm_args.nOptions = 0;
/* Setting classpath */
char classpath[1024] = "-Djava.class.path=";
char *env_classpath = getenv("CLASSPATH");
int mainclass_index = 1;
if (argc >= 3 && !strcmp("-classpath", argv[1])) {
options[0].optionString = strcat(classpath, argv[2]);
vm_args.nOptions++;
mainclass_index += 2;
} else if (env_classpath) {
options[0].optionString = strcat(classpath, env_classpath);
vm_args.nOptions++;
}
if (vm_args.nOptions > 0) {
vm_args.options = options;
}
if (mainclass_index >= argc) {
printf("Main class not found, please specify it\n");
return 0;
}
jint res = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
if (res < 0) {
printf("Create VM error, code = %d\n", res);
return -1;
}
jclass cls = (*env)->FindClass(env, argv[mainclass_index]);
if (!cls) {
printf("Class %s not found\n", argv[mainclass_index]);
return -1;
}
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
if (!mid) {
printf("Method %s of Class %s not found\n", "main", argv[mainclass_index]);
return -1;
}
applicationArgs = (*env)->NewObjectArray(env, argc - mainclass_index - 1,
(*env)->FindClass(env, "java/lang/String"),
NULL);
int i = 0;
for (i = mainclass_index + 1; i < argc; i ++) {
appArg = (*env)->NewStringUTF(env, argv[i]);
(*env)->SetObjectArrayElement(env, applicationArgs, i - mainclass_index - 1, appArg);
}
(*env)->CallStaticVoidMethod(env, cls, mid, applicationArgs);
printf("before destroy\n");
/*
* Destroy the JVM.
* This is necessary, otherwise if the called method exits,
* this program will return immediately.
*/
(*jvm)->DestroyJavaVM(jvm);
printf("after destroy\n");
return 0;
}
接着需要编写 Makefile 文件,注意要链接 JDK 中所自带的 libjvm.so
库文件,在 CentOS 中可以通过 java-N.N.N-openjdk-headless
包安装。
设置环境变量 LD_LIBRARY_PATH
,也即 libjvm.so
所在的路径。
export LD_LIBRARY_PATH=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-3.b12.el7_3.x86_64/jre/lib/amd64/server/
然后运行命令 ./jvm -classpath . HelloWorld
。