JNI 是用来让Java跟别种语言沟通的函式库,如果我们举C/C++ 为例,便分为C call Java与Java call C。
Java call C
1. 建立一个 Java class (HelloWord.java)。在这个class裡宣告一个native method (print),然后在 main() 里呼叫这个 method。此时,我们唿叫 printf() 时,它的实体是用 C/C++ 所写的。
class HelloWorld {
private native void print();
public static void main(String[] args) {
new HelloWorld().print();
}
static {
System.loadLibrary("HelloWorld");
}
}
上例中的System.loadLibrary("HelloWorld")会去找你程式目录下的HelloWorld.dll或HelloWorld.so,这之后会提到。
2. Compile HelloWorld.java,用javac指令。
3. 产生一个 native method 的 header file。
指令: javah -jni HelloWorld
这里,我们用javah可以产生 .h 档,接着就是实作这个 .h 档的 .cpp,然后就可以 compile 成 .dll 或 .so 了。cpp 的范例如下:
#include
#include
#include "HelloWorld.h"
JNIEXPORT void JNICALL
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
printf("Hello World!\n");
return;
}
记得专案的设定中需指定好 jni.h 的目录。如果是用VS系列的话,专案新增时选DLL版本,compile过后就会输出HelloWorld.dll。
4. 最后,将HelloWorld.class跟HelloWorld.dll放在一起后,执行 java HelloWorld 就可以看到结果啰。
註: HelloWorld.dll 其实也不一定要跟 HelloWorld.class 放在一起,有个环境变数叫LD_LIBRARY_PATH,它便是用来设定 native library path。还有一种方式,就是利用java指令来指定路径。下方是将路径设为当前目录。
java -Djava.library.path=. HelloWorld
C call Java
首先,我们用java写一个 class Prog,请他印出些字,然后用javac去compile出 .class。
public class Prog {
public static void main(String[] args) {
System.out.println("Hello World " + args[0]);
}
}
基本上这段代码是可以直接用java指令去执行的,但我们的目标是要产生一个 .c 档来呼叫它,其流程大致如下:
唤醒 Java VM
载入指定class path下的所有 .class 们
找到你想要执行的class及其 method ID。
呼叫 method。
对照第七章的范例来看的话,步骤一加二的程式如下。旧版的JNI 有提供JNI_GetDefaultJavaVMInitArgs,新版的还是请大家自己乖乖指定好 class path 跟其他资讯。JNI_CreateJavaVM 会提供两个重要的指标,jvm 是指向一个新开的JavaVM,env则是待会让你用来对java class做怪怪事的界面。
JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString =
"-Djava.class.path=" USER_CLASSPATH;
vm_args.version = 0x00010002;
vm_args.options = options;
vm_args.nOptions = 1;
vm_args.ignoreUnrecognized = JNI_TRUE;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
步骤三+四如下。
cls = (*env)->FindClass(env, "Prog");
if (cls == NULL) {
goto destroy;
}
mid = (*env)->GetStaticMethodID(env, cls, "main",
"([Ljava/lang/String;)V");
if (mid == NULL) {
goto destroy;
}
jstr = (*env)->NewStringUTF(env, " from C!");
if (jstr == NULL) {
goto destroy;
}
stringClass = (*env)->FindClass(env, "java/lang/String");
args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
if (args == NULL) {
goto destroy;
}
(*env)->CallStaticVoidMethod(env, cls, mid, args);
比较可能看不懂的地方是在GetStaticMethodID的第四个参数,这种诡异的写法称为JNI signature,当我们唿叫 Java method 时需要写一些 signature,用来检查你指定的型态是否与该 method 一样。举些例子好了。
Java constructor:
String s
对应到的signature为:
(Ljava/lang/String;)V
Java method:
String toString()
对应到的signature为:
()Ljava/lang/String;
Java method:
long myMethod(int n, String s, int[] arr)
对应到的signature为:
(ILjava/lang/String;[I)J
看了那么多一定还看不懂对吧?基本上,一个函式会有参数值与回传值,signature 的规则便是先用 () 描述参数值型态,后面再接回传值型态。比较特别的,是如果有用到某个 package,除了要打出 package path 外还要加个分号!
下面列出其对应的所有规则,比较一下就可以知道了。
B=byte
C=char
D=double
F=float
I=int
J=long
S=short
V=void
Z=boolean
Lfully-qualified-class=fully qualified class
[type=array of type>
(argument types)return type=method type. If no arguments, use empty argument types: (). If return type is void (or constructor) use (argument types)V.
如果 method signature 不对的话,还会出现类似以下的错误讯息。
#
# An unexpected error has been detected by Java Runtime Environment:
#
# Internal Error (sharedRuntime.cpp:552), pid=7304, tid=2492
# Error: guarantee(cb != 0,"exception happened outside interpreter, nmethods and vtable stubs (1)")
#
# Java VM: Java HotSpot(TM) Client VM (10.0-b19 mixed mode, sharing windows-x86)
# If you would like to submit a bug report, please visit:
# http://java.sun.com/webapps/bugreport/crash.jsp
#
最后,你可能会想问:我要如何知道一个 Java class 里的所有 method 到底需要哪几种 signature ? 除了自己一个个看以外,J2SDK 有提供神奇指令来帮助我们!
javap -s -p classname
- 评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)
-
