- 概述
- 库和版本管理
- Invocation API
- 示例
#1. 概述
Invocation API的作用在于如何把JVM嵌入到本地程序中。
下述代码展示了如何使用Invocation API,在C++程序中创建JVM然后执行Java程序。
#include <jni.h> /* where everything is defined */
...
JavaVM *jvm; /* denotes a Java VM */
JNIEnv *env; /* pointer to native method interface */
JavaVMInitArgs vm_args; /* JDK/JRE 10 VM initialization arguments */
JavaVMOption* options = new JavaVMOption[1];
options[0].optionString = "-Djava.class.path=/usr/lib/java";
vm_args.version = JNI_VERSION_10;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
/* load and initialize a Java VM, return a JNI interface
* pointer in env */
JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
delete options;
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("Main");
jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
env->CallStaticVoidMethod(cls, mid, 100);
/* We are done. */
jvm->DestroyJavaVM();
1.1 Creating the VM
JNI_CreateJavaVM函数负责加载和初始化Java VM,参数pvm返回JavaVM接口指针,参数penv返回JNI接口指针。
JNI_CreateJavaVM
_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);
1.2 Attaching to the VM
JNI接口指针只在当前线程有效。其他线程只有调用了AttachCurrentThread获取属于当前线程的JNI接口指针才能够访问Java VM。一旦attach成功,native线程就相当于是跑在本地方法中的Java线程。Native线程会一直处理attached状态除非调用DetachCurrentThread。
AttachCurrentThread
jint AttachCurrentThread(void **penv, void *args) {
return functions->AttachCurrentThread(this, penv, args);
}
1.3 Detaching from the VM
处于Attached状态的本地线程在退出前必须要调用DetachCurrentThread。当前线程在其调用栈上的函数尚未执行完毕前不能进行detach操作。
DetachCurrentThread
jint DetachCurrentThread() {
return functions->DetachCurrentThread(this);
}
1.4 Unloading the VM
JNI_DestroyJavaVM函数负责卸载创建好的Java VM。
DestroyJavaVM
jint DestroyJavaVM() {
return functions->DestroyJavaVM(this);
}
#2. 库和版本管理
VM所能加载的Native库分为两类:动态链接库(.so或者.dll),静态链接库(.a或者.lib)。
Native库一旦加载成功,对所有的类加载器都是可见的。因此存在同时两个类加载器去加载同一个Native库的情况,这种case会带来两个问题:
- 类可能会错误的链接上已经被其他类加载器所加载的库。
- Native的方法中会混淆来自不同类加载器的类,这会打破由类加载器隔绝的命名中间限制,带来类型安全问题。
每一个类加载器负责管理自己加载的本地库,同一个Native库不能同时被两个类加载所加载。如果存在不同的类加载器加载同一个Native库会触发UnsatisfiedLinkError。
2.1 JNI_OnLoad
当动态链接库被Java VM加载后(例如,通过System.loadLibrary),VM回调JNI_OnLoad函数。JNI_OnLoad接受JNI_VERSION作为返回值以决定使用哪个版本的JNI_API。
譬如Native库中需要使用AttachCurrentThreadAsDaemon,那么JNI版本必须是1.4以上,即必须返回JNI_VERSION_1_4或以上版本。如果Native库中没有导出JNI_OnLoad函数,VM会选择默认的JNI函数版本JNI_VERSION_1_1。如果返回的JNI版本号是未定义的,VM会自动卸载掉所加载的Native库。
jint JNI_OnLoad(JavaVM *vm, void *reserved);
JNI Version
#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006
示例
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
#ifdef ANDROID_ENV_FIPS_MODE
// Init FIPS mode.
if (FIPS_mode() != 0) {
LOGD("Already in FIPS mode\n");
} else {
int ret = FIPS_mode_set(1);
if (ret == 0) {
LOGE("FIPS_mode_set() returns 0\n");
unsigned long err;
while ((err = ERR_get_error()) != 0) {
LOGE("\tmsg = %lu, %s\n", err, ERR_error_string(err, nullptr));
}
}
}
#endif
return JNI_VERSION_1_6;
}
2.2 JNI_OnUnload
Java VM会在加载动态链接库的class loader被虚拟机回收后回调JNI_OnUnload。
JNI_OnUnload函数可以进行一些清理工作。
void JNI_OnUnload(JavaVM *vm, void *reserved);
2.3 JNI_OnLoad_L
静态链接库中必须要定义的函数。L为静态链接库的名字。JNI_OnLoad_L的作用和JNI_OnLoad是一样的,JNI_OnLoad_L是在JNI_VERSION_1_8以后支持的。
jint JNI_Onload_<L>(JavaVM *vm, void *reserved);
2.4 JNI_OnUnload_L
Java VM会在加载静态链接库的class loader被虚拟机回收后回调JNI_OnUnload_L。
void JNI_OnUnload_<L>(JavaVM *vm, void *reserved);
#3. Invocation API
JNI函数中提供了一系列的Invocation API,包括JNI_CreateJavaVM,
JNI_GetDefaultJavaVMInitArgs,JNI_GetCreatedJavaVMs等。
3.1 JavaVM
JavaVM是Invocation API函数指针。
struct JavaVM_ {
const struct JNIInvokeInterface_ *functions;
#ifdef __cplusplus
jint DestroyJavaVM() {
return functions->DestroyJavaVM(this);
}
jint AttachCurrentThread(void **penv, void *args) {
return functions->AttachCurrentThread(this, penv, args);
}
jint DetachCurrentThread() {
return functions->DetachCurrentThread(this);
}
jint GetEnv(void **penv, jint version) {
return functions->GetEnv(this, penv, version);
}
jint AttachCurrentThreadAsDaemon(void **penv, void *args) {
return functions->AttachCurrentThreadAsDaemon(this, penv, args);
}
#endif
};
3.2 JNI_GetDefaultJavaVMInitArgs
获取VM的默认配置。在调用该函数前,必须指定vm_args->version(告诉VM需要支持的JNI版本号)。
jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
3.3 JNI_GetCreatedJavaVMs
获取已经创建的JavaVM集合。
jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
3.4 JNI_CreateJavaVM
加载并且初始化Java VM。调用JNI_CreateJavaVM的线程变为主线程。p_env参数返回当前线程的JNI接口指针。vm_args参数用来设置VM的启动参数。
jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);
JavaVMInitArgs
JavaVM启动参数。
typedef struct JavaVMInitArgs {
jint version;
jint nOptions;
JavaVMOption *options;
jboolean ignoreUnrecognized;
} JavaVMInitArgs;
JavaVMOption
typedef struct JavaVMOption {
char *optionString; /* the option as a string in the default platform encoding */
void *extraInfo;
} JavaVMOption;
optionString | meaning |
---|---|
-D<name>=<value> | Set a system property |
-verbose[:class|gc|jni] | Enable verbose output. The options can be followed by a comma-separated list of names indicating what kind of messages will be printed by the VM. For example, "-verbose:gc,class" instructs the VM to print GC and class loading related messages. Standard names include: gc, class, and jni. All nonstandard (VM-specific) names must begin with "X". |
vfprintf | extraInfo is a pointer to the vfprintf hook. |
exit | extraInfo is a pointer to the exit hook. |
abort | extraInfo is a pointer to the abort hook. |
示例
JavaVMInitArgs vm_args;
JavaVMOption options[4];
options[0].optionString = "-Djava.compiler=NONE"; /* disable JIT */
options[1].optionString = "-Djava.class.path=c:\myclasses"; /* user classes */
options[2].optionString = "-Djava.library.path=c:\mylibs"; /* set native library path */
options[3].optionString = "-verbose:jni"; /* print JNI-related messages */
vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = TRUE;
/* Note that in the JDK/JRE, there is no longer any need to call
* JNI_GetDefaultJavaVMInitArgs.
*/
res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);
if (res < 0) ...
3.5 DestroyJavaVM
卸载Java VM。
jint DestroyJavaVM(JavaVM *vm);
#4. 示例
以下代码展示了如何在Native程序中嵌入JVM虚拟机,C++程序调用Java程序的过程。
4.1 Prog.java
public class Prog {
public static void main(String[] args) {
System.out.println("Hello World " + args[0]);
}
}
4.2 Invoke.cpp
#include <stdio.h>
#include <Windows.h>
#include <process.h>
#include <jni.h>
#define PATH_SEPARATOR ';'
#define USER_CLASSPATH "."
JavaVM *jvm; /*The virtual machine instance*/
void *JNU_FindCreateJavaVM(char *vmlibpath)
{
HMODULE hVM = LoadLibrary(vmlibpath);
if (hVM == NULL) {
return NULL;
}
return GetProcAddress(hVM, "JNI_CreateJavaVM");
}
void thread_fun(void *arg) {
jint res;
jclass cls;
jmethodID mid;
jstring jstr;
jclass stringClass;
jobjectArray args;
JNIEnv *env;
char buf[100];
int threadNum = (int)arg;
#ifdef JNI_VERSION_1_6
res = jvm->AttachCurrentThread((void **)&env, NULL);
#else
res = jvm->AttachCurrentThread((void **)&env, NULL);
#endif // JNI_VERSION_1_6
if (res < 0) {
fprintf(stderr, "Attach failed\n");
return;
}
cls = env->FindClass("Prog");
if (cls == NULL) {
goto detach;
}
mid = env->GetStaticMethodID(cls, "main", "([Ljava/lang/String;)V");
if (mid == NULL) {
goto detach;
}
sprintf_s(buf, sizeof(buf), "from Thread %d", threadNum);
jstr = env->NewStringUTF(buf);
if (jstr == NULL) {
goto detach;
}
stringClass = env->FindClass("java/lang/String");
args = env->NewObjectArray(1, stringClass, jstr);
if (args == NULL) {
goto detach;
}
env->CallStaticVoidMethod(cls, mid, args);
detach:
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
}
jvm->DetachCurrentThread();
}
void main() {
JNIEnv *env;
jint res;
const char *msg = " from C!";
const char *path = "C:\\Program Files\\Java\\jdk1.8.0_91\\jre\\bin\\server\\jvm.dll";
char classPath[1024];
char vmLibPath[1024];
typedef jint(*JNI_CreateJavaVM_Fn)(JavaVM**, void**, void*);
#ifdef JNI_VERSION_1_6
JavaVMInitArgs vm_args;
JavaVMOption options[1];
sprintf_s(classPath, sizeof(classPath), "%s%s", "-Djava.class.path=", USER_CLASSPATH);
options[0].optionString = classPath;
options[0].extraInfo = classPath;
vm_args.version = JNI_VERSION_1_6;
vm_args.options = options;
vm_args.nOptions = 1;
vm_args.ignoreUnrecognized = JNI_TRUE;
/*Create the Java VM*/
sprintf_s(vmLibPath, sizeof(vmLibPath), "%s", path);
void *addr = JNU_FindCreateJavaVM(vmLibPath);
if (addr == NULL) {
return;
}
JNI_CreateJavaVM_Fn jni_create_vm = reinterpret_cast<JNI_CreateJavaVM_Fn>(addr);
res = (*jni_create_vm)(&jvm, (void**)&env, &vm_args);
#else
JDK1_1InitArgs vm_args;
char classpath[1024];
vm_args.version = 0x00010001;
JNI_GetDefaultJavaVMInitArgs(&vm_args);
/* Append USER_CLASSPATH to the default system class path */
sprintf(classpath, "%s%c%s",
vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
vm_args.classpath = classpath;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
#endif /* JNI_VERSION_1_6 */
if (res < 0) {
fprintf(stderr, "Can't create Java VM.\n");
exit(0);
}
for (int i = 0; i < 5; i++) {
_beginthread(thread_fun, 0, (void *)i);
}
Sleep(1000);
jvm->DestroyJavaVM();
system("pause");
}

网友评论