您的位置:首页 > 编程语言 > Java开发

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

2018-03-15 21:56 666 查看

传统做法

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}



3. 创建dll项目(Visual Studio)

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


(2)新建dlltest.cpp实现test_Example.h的C++函数#include <iostream>
#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, "<init>", "([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<<s<<std::endl;
}

JNIEXPORT jstring JNICALL Java_test_Example_get
(JNIEnv* env, jobject obj) {
return charTojstring(env,s.c_str());
}(3)编译,生成dll文件

        


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<void*>(get)},
{"set", "(Ljava/lang/String;)V", reinterpret_cast<void*>(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文件中的注释复制就行。

字符javaC/C++类型
Vvoidvoid
Zjbooleanboolean
Ijintint
Jjlonglong
Djdoubledouble
Fjfloatfloat
Bjbytebyte
Cjcharchar
Sjshortshort
数组则以"["开始,用两个字符表示,比如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/216a41352fd8http://blog.csdn.net/qiuxiaolong007/article/details/7860610
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java native