C 语言调用 JNI_CreateJavaVM

Posted 车斗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C 语言调用 JNI_CreateJavaVM相关的知识,希望对你有一定的参考价值。

C 语言调用 JNI_CreateJavaVM

环境:win10 + vs2015 + jdk-8u171-windows-x64.zip

假设我们已经存在了 C/C++ 语言的动态库clib.dll,如果想从 java 语言调用此动态库clib.dll,需要写个供 java 调用的C/C++的JNI动态库: clib_jniwrapper.dll,clib_jniwrapper.dll 桥接了从Java(JNIWrapper)对象到C/C++对象(clib.dll)的转换。 然后再写个 java 类如: JNIWrapper.java,在这个java JNIWrapper类里调用JNI方法提供者库clib_jniwrapper.dll。这就是 从 Java 调用 C 的整个过程:

[Java(JNIWrapper.java) ]===JNI Call===> [C/C++ (clib_jniwrapper.dll)] ===> [clib.dll]

clib_jniwrapper.dll 既然是一个 JNI 动态库,就要实现 Java(JNIWrapper)声明的 native 方法。首先是在 JNIWrapper.java 中加入要实现的 native 方法,然后用 javah命令自动创建出 jniwrapper 头文件:com_github_jni_JNIWrapper.h。然后需要手工编写 clib_jniwrapper.dll,实现这个头文件里声明的方法。

显然,在实际应用中,clib_jniwrapper.dll 被 Java 运行时所加载,然后在 Java 里调用 JNI 方法,间接通过 clib_jniwrapper.dll 最后调用到 clib.dll。

然而开发这样一个 clib_jniwrapper.dll 需要大量的调试和测试,因此需要写一个C语言的测试程序:test_clib_jniwrapper.exe。测试程序 test_clib_jniwrapper.exe 加载 jvm 运行时,然后创建 jni 对象,通过 jni 方法和对象调用 clib_jniwrapper.dll,这样就可以很方便地本地调试 C/C++ 的JNI 动态库 clib_jniwrapper.dll 了。

下面是 test_clib_jniwrapper.c 核心代码:


#include <com_github_jni_JNIWrapper.h>

static const char THIS_FILE[] = "test_clib_jniwrapper.c";

HINSTANCE jvmdll = NULL;
JavaVM * jvm = NULL;
void   * jenv = NULL;

static void appexit_cleanup(void)
{
    if (jvm) {
        (*jvm)->DestroyJavaVM(jvm);
    }

    if (jvmdll) {
        FreeLibrary(jvmdll);
    }
}


static HANDLE logon_user(const char *username, const char *domain, const char *password)
{
    HANDLE hLogon = NULL;
    BOOL result = LogonUserA(username, domain, password,
            LOGON32_LOGON_INTERACTIVE,
            LOGON32_PROVIDER_DEFAULT,
            &hLogon);
    if (!result) {
        exit(-1);
    }
    result = ImpersonateLoggedOnUser(hLogon);
    if (!result) {
        exit(-1);
    }
    return hLogon;
}


static void get_env_var(const char *var, const char *prefix, char *value, size_t valuesz)
{
    errno_t err;
    size_t bsize;

    int prelen = 0;
    if (prefix) {
        prelen = snprintf_chkd_V1(value, valuesz, prefix);
        valuesz -= prelen;
    }

    err = getenv_s(&bsize, NULL, 0, var);
    if (err || !bsize) {
        fprintf(stderr, "failed to get env: %s\\n", var);
        exit(-1);
    }

    if ((int)bsize >= (int)(valuesz)) {
        fprintf(stderr, "insufficent value buffer for: JAVA_HOME\\n");
        exit(-1);
    }

    err = getenv_s(&bsize, &value[prelen], valuesz, var);
    if (err || !bsize || (int)bsize >= (int)valuesz) {
        fprintf(stderr, "error to get env: %s\\n", var);
        exit(-1);
    }

    printf("${env:%s}=%.*s\\n", var, (int)bsize, &value[prelen]);
}


void createJVM()
{
    typedef jint(JNICALL *ProcCreateJavaVM)(JavaVM **, void**, void *);

    jint res;
    ProcCreateJavaVM jvm_CreateJavaVM;

    char javahome[200];
    char jvmdllpath[260];
    char classpath[1200];

    JavaVMInitArgs vm_args;
    JavaVMOption options[30];

    memset(&vm_args, 0, sizeof(vm_args));
    memset(&options, 0, sizeof(options));

    get_env_var("JAVA_HOME", NULL, javahome, sizeof(javahome));
    get_env_var("CLASSPATH", "-Djava.class.path=", classpath, sizeof(classpath));

    snprintf_chkd_V1(jvmdllpath, sizeof(jvmdllpath), "%s\\\\jre\\\\bin\\\\server\\\\jvm.dll", javahome);

    // 注意: 务必使用动态载入 jvm.dll 方式调用 JNI_CreateJavaVM
    jvmdll = LoadLibraryA(jvmdllpath);
    if (!jvmdll) {
        printf("failed to load: %s\\n", jvmdllpath);
        exit(-1);
    }
    printf("success load: %s\\n", jvmdllpath);

    jvm_CreateJavaVM = (ProcCreateJavaVM) GetProcAddress(jvmdll, "JNI_CreateJavaVM");
    if (!jvm_CreateJavaVM) {
        printf("failed to GetProcAddress: JNI_CreateJavaVM\\n");
        exit(-1);
    }

    options[0].optionString = classpath;

    vm_args.version = JNI_VERSION_1_6;
    vm_args.ignoreUnrecognized = JNI_TRUE;
    vm_args.options = options;
    vm_args.nOptions = 1;

    // 务必关闭异常: (VS2015: Ctrl+Alt+E -> Win32 Exceptioins -> 0xc0000005 Access violation)
    //   https://stackoverflow.com/questions/36250235/exception-0xc0000005-from-jni-createjavavm-jvm-dll
	res = jvm_CreateJavaVM(&jvm, &jenv, &vm_args);
	if (res == 0) {
		printf("successfully created JVM.\\n");
	} else {
		printf("failed to create JVM.\\n");
        exit(-1);
	}
}

int main(int argc, char *argv[])
{
    WINDOWS_CRTDBG_ON

    createJVM();
    jobject obj = NULL;

    atexit(appexit_cleanup);
    Java_com_github_jni_JNIWrapper_JNI_1clib_1lib_1version(jenv, obj);

    return 0;
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_github_jni_JNIWrapper */

#ifndef _Included_com_github_jni_JNIWrapper
#define _Included_com_github_jni_JNIWrapper
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_github_jni_JNIWrapper
 * Method:    JNI_clib_lib_version
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_github_jni_JNIWrapper_JNI_1clib_1lib_1version
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

以上需要注意的是,务必关闭异常: (VS2015: Ctrl+Alt+E -> Win32 Exceptioins -> 0xc0000005 Access violation)。

以上测试完全可用。完整的DEMO:

https://github.com/pepstack/clib-jni

以上是关于C 语言调用 JNI_CreateJavaVM的主要内容,如果未能解决你的问题,请参考以下文章

C 语言调用 JNI_CreateJavaVM

Android NDK编译之undefined reference to 'JNI_CreateJavaVM'

JNI_CreateJavaVM 上的段错误

java - 来自JNI_CreateJavaVM(jvm.dll)的异常0xC0000005

来自 JNI_CreateJavaVM (jvm.dll) 的异常 0xC0000005

引用了未解析的外部符号 __imp__JNI_CreateJavaVM@12