声明:迁移自本人CSDN博客https://blog.csdn.net/u013365635

孔乙己说,茴香豆的茴有四种写法,今天谈谈JNI的第2种写法:本地方法注册。

这种写法的好处是不需要使用javah生成一个C++头文件,也不必使用javah自动生成的长长的C++函数名,往往在native函数很多的情况下,扩展比较灵活。
之前的笔者写的文章中介绍的是函数名映射的方法,今天介绍的是采用注册本地方法的方式 。本质都是建立起Java层native函数和C层函数的映射关系。
C++代码部分做了一些额外的测试操作,这些测试操作意在表明Java堆和C本地堆之间的关系。

测试类

package com.testjnitype2;

public class TestJNIType2
{
    private static NativeUtils nativeUtils = new NativeUtils();

    static
    {
        System.load("D:\\test\\src\\com\\testjnitype2\\lib_testjni_Type2_amd64.dll");
    }

    public static void main(String[] args)
    {
        byte[] dataSharedByJavaC = new byte[10];
        System.out.println("JNIType2 before-->dataSharedByJavaC=" + dataSharedByJavaC);
        for (int i = 0; i < 10; i++)
        {
            System.out.print(dataSharedByJavaC[i] + " ");
        }
        System.out.println("\n----------");
        nativeUtils.tranferBytes(dataSharedByJavaC);
        System.out.println("JNIType2 after-->dataSharedByJavaC=" + dataSharedByJavaC);
        for (int i = 0; i < 10; i++)
        {
            System.out.print(dataSharedByJavaC[i] + " ");
        }
    }
}

java native方法定义类

package com.testjnitype2;

public class NativeUtils
{
    public native void tranferBytes(byte[] dataSharedByJavaC);
}

C++实现类

#include "jni.h"
#include "stdio.h"

static const char *classPath = "com/testjnitype2/NativeUtils";

int FAILURE = -1;

int SUCESS = 0;

JNIEXPORT void JNICALL tranfer_bytes(JNIEnv *jniEnv, jobject obj, jbyteArray dataSharedByJavaC);


//step1-1:建立java方法与native 方法的映射关系
//JNINativeMethod定义在jni.h中,定义如下
/**
typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;
*/
static const JNINativeMethod methods[] =
{
    /*
    tranferBytes为java层定义的方法,tranfer_bytes为native层定义的方法,此处就不需要写成Java_com_testjnitype2_NativeUtils_tranferBytes这种格式了
    */
    {"tranferBytes", "([B)V", (void*)tranfer_bytes}
};

//step 1-2:实现与java方法映射到的本地方法
JNIEXPORT void JNICALL tranfer_bytes(JNIEnv *jniEnv, jobject obj, jbyteArray dataSharedByJavaC)
{
    printf("\n----------");
    //data在native层的地址
	printf("\ndataSharedByJavaC addr = %x, dataSharedByJavaC and dataSharedByJavaC addr transfered from java parsed by native is random:\n", dataSharedByJavaC);
	for (int i=0; i<10; i++)
	{
		printf("%d ", dataSharedByJavaC[i]);
	}

    //data在native层解析后指向java data后的地址
	jbyte* p = jniEnv->GetByteArrayElements(dataSharedByJavaC, NULL);
	printf("\n----------");
	printf("\np addr = %x, make native p point to java heap, now native data pointed by p is definitized, in other wolds, it's java heap data:\n", p);
    for (int i=0; i<10; i++)
    {
    	printf("%d ", p[i]);
    }

    //重新赋值,也就是更改Java heap中的数据
	for (int i = 0; i < 10; i++)
	{
	    p[i] = i * i + 1;
	}

	jniEnv->ReleaseByteArrayElements(dataSharedByJavaC, p, 0);
    return;
}

//step2:在jvm中注册映射关系
int registerNativeMethods(JNIEnv *env, const char *classPath)
{
    printf("begin register native methods");
    jclass clazz = env->FindClass(classPath);
    if (!clazz)
    {
        return FAILURE;
    }
    if (env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof(JNINativeMethod)) != JNI_OK)
    {
        return FAILURE;
    }
    return SUCESS;
}

/**
step3:实现jni.h中声明的方法JNI_OnLoad,并调用step 2中的注册方法
该方法在程序启动时,Java加载本地库的时被调用,比如System.load("xxx");
*/
JNIEXPORT jint JNI_OnLoad(JavaVM *jvm, void* reserved)
{
    printf("JNI OnLoad");
    JNIEnv *env;
    if (jvm->GetEnv((void**)&env, JNI_VERSION_1_8) != JNI_OK)
    {
        printf("get env error");
        return -1;
    }
    registerNativeMethods(env, classPath);
    return JNI_VERSION_1_8;
}

/**
实现jni.h中声明的方法
*/
JNIEXPORT void JNI_OnUnload(JavaVM* jvm, void* reserved)
{
    JNIEnv *env;
    if (jvm->GetEnv((void**)&env, JNI_VERSION_1_8) != JNI_OK)
    {
        printf("get env error");
    }
}

C代码编译方法
d:\VS2010\Microsoft Visual Studio 10.0\VC>cl /LD D:\test\src\com\testjnitype2\testjnitype2.cpp -o D:\test\src\com\testjnitype2\lib_testjni_Type2_amd64.dll

编译完后目录大概如下图。
这里写图片描述
执行结果如下

JNIType2 before-->dataSharedByJavaC=[B@78308db1
0 0 0 0 0 0 0 0 0 0 
----------
JNIType2 after-->dataSharedByJavaC=[B@78308db1
1 2 5 10 17 26 37 50 65 82 
----------
JNI OnLoad
----------
begin register native methods
----------
dataSharedByJavaC addr = 2c5f470, dataSharedByJavaC and dataSharedByJavaC addr transfered from java parsed by native is random:
240 96 213 106 7 0 0 0 88 89 
----------
p addr = 1dd95e70, make native p point to java heap, now native data pointed by p is definitized, in other wolds, it's java heap data:
0 0 0 0 0 0 0 0 0 0

执行结果暗含了很多信息,比如,输出的顺序、打印的数组值为什么会是上面的样子?这个会做个专题进行讨论。
再说说jni的使用方法吧,其实,本地方法注册法和函数名映射法可以混合使用的。
顺便说一句,netty集成平台动态库的方法就是用的这种。
以上。