java JNI 实现原理 (二) Linux 下如何 load JNILibrary

在博客java JNI (一)虚拟机中classloader的JNILibrary 中讨论了java中的Library 是由classloader 来load的,那我们来看看 classloader是如何去load 一个library的

ClassLoader.c  

JNIEXPORT void JNICALL 
Java_java_lang_ClassLoader_00024NativeLibrary_load
  (JNIEnv *env, jobject this, jstring name)
{
    const char *cname;
    jint jniVersion;
    jthrowable cause;
    void * handle;

    if (!initIDs(env))
        return;

    cname = JNU_GetStringPlatformChars(env, name, 0);
    if (cname == 0)
        return;
    handle = JVM_LoadLibrary(cname);
    if (handle) {
        const char *onLoadSymbols[] = JNI_ONLOAD_SYMBOLS;
        JNI_OnLoad_t JNI_OnLoad;
	int i;
	for (i = 0; i < sizeof(onLoadSymbols) / sizeof(char *); i++) {
	    JNI_OnLoad = (JNI_OnLoad_t) 
	        JVM_FindLibraryEntry(handle, onLoadSymbols[i]);
	    if (JNI_OnLoad) {
	        break;
	    }
	}
	if (JNI_OnLoad) {
	    JavaVM *jvm;
	    (*env)->GetJavaVM(env, &jvm);
	    jniVersion = (*JNI_OnLoad)(jvm, NULL);
	} else {
	    jniVersion = 0x00010001;
	}

	cause = (*env)->ExceptionOccurred(env);
	if (cause) {
 	    (*env)->ExceptionClear(env);
	    (*env)->Throw(env, cause);
	    JVM_UnloadLibrary(handle);
	    goto done;
	}
   
	if (!JVM_IsSupportedJNIVersion(jniVersion)) {
	    char msg[256];
	    jio_snprintf(msg, sizeof(msg),
			 "unsupported JNI version 0x%08X required by %s",
			 jniVersion, cname);
	    JNU_ThrowByName(env, "java/lang/UnsatisfiedLinkError", msg);
	    JVM_UnloadLibrary(handle);
	    goto done;
	}
	(*env)->SetIntField(env, this, jniVersionID, jniVersion);
    } else {
	cause = (*env)->ExceptionOccurred(env);
	if (cause) {
	    (*env)->ExceptionClear(env);
	    (*env)->SetLongField(env, this, handleID, (jlong)NULL);
	    (*env)->Throw(env, cause);
	}
	goto done;
    }
    (*env)->SetLongField(env, this, handleID, ptr_to_jlong(handle));

 done:
    JNU_ReleaseStringPlatformChars(env, name, cname);
}

1. JVM_LoadLibrary 

jvm中load library 核心函数,实现也非常简单,在linux下调用了系统函数dlopen去打开库文件,详细可参考方法

void * os::dll_load(const char *filename, char *ebuf, int ebuflen)

2. JVM_FindLibraryEntry 

JVM在加载库文件时候,会去尝试查找库中的JNI_ONLOAD方法的地址,而在Linux中调用了dlsym函数通过前面的dlopen加载库的指针去获取方法的地址,而dlsym在glibc2.0是非线程安全的,需要锁的保护,虽然在java中加载库已经有锁的保护,但只是针对同一个classloader对象的细粒度锁。

void* os::dll_lookup(void* handle, const char* name) {
  pthread_mutex_lock(&dl_mutex);
  void* res = dlsym(handle, name);
  pthread_mutex_unlock(&dl_mutex);
  return res;
}

3. 方法JNI_OnLoad

JVM提供了一种方式允许你在加载库文件的时候做一些你想做的事情,也就是JNI_OnLoad方法

在2中提到过在加载动态链接库,JVM会去尝试查找JNI_OnLoad方法,同时也会调用该函数,这样你个人可以在函数里做一些初始化的事情,比如register native方法。

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{}
JNI_OnLoad中返回的是JNI 的version,在1.6版本的情况下支持如下 

jboolean Threads::is_supported_jni_version(jint version) {
  if (version == JNI_VERSION_1_2) return JNI_TRUE;
  if (version == JNI_VERSION_1_4) return JNI_TRUE;
  if (version == JNI_VERSION_1_6) return JNI_TRUE;
  return JNI_FALSE;
}

完整的加载过程就是

首先先加载动态链接库,尝试查找JNI_OnLoad方法,并且运行方法,对我们来说从而实现可以自定义的初始化方法。



准备工作: 1.编译JnativeCpp 2.将编译出来的libJNativeCpp.so,拷贝到/usr/lib/,同时执行chmod 555 libJNativeCpp.so 测试过程简介 1.c测试库libtest.so 环境:ubuntu10.4下 语言:c 编译库名称为:libtest.so 涉及文件:so_test.h test_a.c test_b.c test_c.c 终端执行命令:$ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so 将编译得到到libtest.so拷贝到/usr/lib/,同时执行chmod 555 libtest.so 2.qt测试库libmylib.so 环境:ubuntu10.4下 语言:c 编译工具:qt Creator 编译库名称为:libmylib.so 将编译得到到libmylib.so拷贝到/usr/lib/,同时执行chmod 555 libmylib.so 3.编译环境安装 a.安装jdk 1.6 b.安装netBeans 6.8 c.创建javaApp工程 d.将JNative.jar添加到工程中 e.参考如下代码,编写后编译执行,并运行 import org.xvolks.jnative.JNative; import org.xvolks.jnative.Type; import org.xvolks.jnative.exceptions.NativeException; public class Main { public static void main(String[] args) throws NativeException, IllegalAccessException{ //纯c写到动态库 JNative clib = new JNative("libtest.so", "test_a"); //调用libtest.so下到test_a函数 clib.setRetVal(Type.STRING); //设置此函数的返回值 clib.invoke(); //函数执行 System.out.println(clib.getRetVal());//输出函数返回结果 //qt写到动态库 //以下部分使用qt编译到so,注意在函数声明前加 extern "C" //如extern "C" const char* getLocalHost(); JNative getstring = new JNative("libmylib.so", "getstring"); getstring.setRetVal(Type.STRING); getstring.invoke(); System.out.println(getstring.getRetVal()); } } 4.输出结果 this is in test_a... getstring hello .....
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值