java native:Java本地方法调用(jni方式)

传统做法

1.  编写包含本地方法的Java类(Eclipse)

package test;

public class Example {
	public native void set(String info);
	public native String get();
}

2. 生成javah文件(Eclipse)

(1)点击eclipse工具栏外部工具按钮,打开配置外部工具;

(2)选中program右键点击添加;

(3)配置如下信息后点击run,生成的test_Example.h文件在项目的jni目录下。

name框里输入命令的名字:javah

location框里输入javah.exe的绝对路径: D:\jdk1.8\bin\javah.exe

Working Directory框里输入:${project_loc}

Arguments框里输入: -v-classpath "${project_loc}/bin" -d "${project_loc}/jni"-jni ${java_type_name}

java native:Java本地方法调用(jni方式)_第1张图片

3. 创建dll项目(Visual Studio)

(1)vs下新建DLL项目,将test_Example.h和jni.h(包含JAVA_HOME/include/jni.h和JAVA_HOME/include/win32/jni_md.h)添加进工程。

java native:Java本地方法调用(jni方式)_第2张图片

(2)新建dlltest.cpp实现test_Example.h的C++函数

#include 
#include "opencv2/highgui/highgui.hpp"
#include "test_Example.h"

std::string s = "hello dll";

std::string jstring2str(JNIEnv* env, jstring jstr) {   
    char* rtn = NULL;   
    jclass clsstring = env->FindClass("java/lang/String");   
    jstring strencode = env->NewStringUTF("GB2312");   
    jmethodID mid = env->GetMethodID(clsstring,   "getBytes",   "(Ljava/lang/String;)[B");   
    jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr,mid,strencode);   
    jsize alen = env->GetArrayLength(barr);   
    jbyte* ba = env->GetByteArrayElements(barr,JNI_FALSE);   
    if(alen > 0)   
    {   
        rtn = (char*)malloc(alen+1);         
        memcpy(rtn,ba,alen);   
        rtn[alen]=0;   
    }   
    env->ReleaseByteArrayElements(barr,ba,0);   
    std::string stemp(rtn);
    free(rtn);
    return stemp;   
}

jstring charTojstring(JNIEnv* env, const char* pat) {
    //定义java String类 strClass
    jclass strClass = (env)->FindClass("Ljava/lang/String;");
    //获取String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
    jmethodID ctorID = (env)->GetMethodID(strClass, "", "([BLjava/lang/String;)V");
    //建立byte数组
    jbyteArray bytes = (env)->NewByteArray(strlen(pat));
    //将char* 转换为byte数组
    (env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*) pat);
    // 设置String, 保存语言类型,用于byte数组转换至String时的参数
    jstring encoding = (env)->NewStringUTF("GB2312");
    //将byte数组转换为java String,并输出
    return (jstring) (env)->NewObject(strClass, ctorID, bytes, encoding);
}

JNIEXPORT void JNICALL Java_test_Example_set
	(JNIEnv* env, jobject obj, jstring str) {
	s = jstring2str(env,str);
	std::cout<

(3)编译,生成dll文件

java native:Java本地方法调用(jni方式)_第3张图片        java native:Java本地方法调用(jni方式)_第4张图片


4. 在java中使用调用本地方法(Eclipse)

编写测试类,并将dlltest.dll文件放在classpath下。

package test;

public class Test {

	static {
		System.load("E:\\workspace\\test\\src\\dlltest.dll");
	}
	public static void main(String[] args) {
		Example e = new Example();
		System.out.println(e.get());
		e.set("hello java");
	}
}

运行结果:

hello dll

hello java

5.  常见错误

(1)找不到对应的dll,原因很可能是不在classpath下,使用绝对路径试试;

(2)dll的64位版本和Eclipse的32位版本的不一致错误;

(3)web下找不到dll可参考:http://blog.csdn.net/l1028386804/article/details/53903557

模仿Object中的registerNatives()方法

除了使用传统方法实现JNI外,也可以使用RegisterNatives实现JNI。和传统方法相比,使用RegisterNatives的好处有三点:
(1)C++中函数命名自由,不必像javah自动生成的函数声明那样,拘泥特定的命名方式;
(2)效率高。传统方式下,Java类调用本地函数时,通常是依靠VM去动态寻找.so中的本地函数(因此它们才需要特定规则的命名格式),而使用RegisterNatives将本地函数向VM进行登记,可以让其更有效率的找到函数
(3)运行时动态调整本地函数与Java函数值之间的映射关系,只需要多次调用RegisterNatives()方法,并传入不同的映射表参数即可。

为了使用RegisterNatives,我们需要了解JNI_OnLoad和JNI_OnUnload函数。JNI_OnLoad()函数在VM执行System.loadLibrary(xxx)函数时被调用,它有两个重要的作用:(1)指定JNI版本:告诉VM该组件使用那一个JNI版本(若未提供JNI_OnLoad()函数,VM会默认该使用最老的JNI 1.1版),如果要使用新版本的JNI,例如JNI 1.4版,则必须由JNI_OnLoad()函数返回常量JNI_VERSION_1_4(该常量定义在jni.h中) 来告知VM。(2)初始化设定,当VM执行到System.loadLibrary()函数时,会立即先呼叫JNI_OnLoad()方法,因此在该方法中进行各种资源的初始化操作最为恰当,RegisterNatives也在这里进行。JNI_OnUnload()当VM释放该组件时被调用,JNI_OnUnload()函数的作用与JNI_OnLoad()对应,因此在该方法中进行善后清理,资源释放的动作最为合适。

相比传统方式,vs项目下有所不同。只需在dll项目下声明本地方法,并重载jni.h中的JNI_ONLOAD方法来注册本地方法

1. 声明本地方法

static JNINativeMethod methods[] = {  
	{"get", "()Ljava/lang/String;", reinterpret_cast(get)},
	{"set", "(Ljava/lang/String;)V", reinterpret_cast(set)}
}; 

其中JNINativeMethod是jni.h中定义的结构体

/*
 * used in RegisterNatives to describe native method name, signature,
 * and function pointer.
 */
typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;

name是java中定义的native函数的名字,fnPtr是函数指针,也就是C++中java native函数的实现。signature是java native函数的签名,可以认为是参数和返回值。native函数签名参考下表,其实也可对照生成的javah文件中的注释复制就行。

字符 java C/C++类型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
数组则以"["开始,用两个字符表示,比如int数组表示为[I,以此类推。
如果参数是Java类,则以"L"开头,以";"结尾,中间是用"/"隔开包及类名,例如Ljava/lang/String;,而其对应的C++函数的参数为jobject,一个例外是String类,它对应C++类型jstring。

为了与JNINativeMethod的函数名对应,简化native方法名,同时应修改javah的函数声明:

JNIEXPORT void JNICALL set(JNIEnv *, jobject, jstring);
JNIEXPORT jstring JNICALL get (JNIEnv *, jobject);

2. 重载jni.h中的JNI_Onload方法,注册native方法

jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)  
{  
 JNIEnv* env = NULL;  
   if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)  
   {  
      return JNI_ERR;  
   }  
  
 jclass cls = env->FindClass("test/Example");  
   if (cls == NULL)  
   {  
      return JNI_ERR;  
   }  
  
   int len = sizeof(methods) / sizeof(methods[0]);  
   if (env->RegisterNatives(cls, methods, len) < 0)  
   {  
      return JNI_ERR;  
   }  
  
   return JNI_VERSION_1_4;  
}

3.  编译dll项目,生成dll文件

生成dll后与前面的用法完全一样。


更多内容请参考:https://www.jianshu.com/p/216a41352fd8

http://blog.csdn.net/qiuxiaolong007/article/details/7860610

你可能感兴趣的:(java native:Java本地方法调用(jni方式))