小试JNI——轻松了解windows和linux动态加载动态库的用法和差异+完整示例代码
小试jni: 轻松了解windows和linux动态加载动态库的用法和差异+完整示例代码
1. 编写java代码
注意使用jni的代码,修改包名时,需要重新生成头文件并同步修改dll库相关代码,否则会导致无法链接错误
package org.example.testjni;
import java.io.File;
/**
* 注意: 使用jni的代码,修改包名时,需要重新生成头文件并同步修改dll库相关代码,否则会导致无法链接错误
*/
public class Decoder {
private static String dllPath = null;
private static long handle = 0L;
private static Decoder decoder = null;
public native String version();
public String encode(String serial, int synctime) {
return this.encode(handle, serial, synctime);
}
public String decode(String data) {
return this.decode(handle, data);
}
private native long init(String dllPath);
private native void kill(long handle);
private native String encode(long handle, String serial, int synctime);
private native String decode(long handle, String data);
private Decoder() {
}
public void close() {
if (handle != 0L) {
decoder.kill(handle);
handle = 0L;
}
decoder = null;
}
public static Decoder getDecoder() {
if (decoder == null) {
synchronized(Decoder.class) {
decoder = new Decoder();
handle = decoder.init(dllPath);
}
}
return decoder;
}
/**
*
* @param proxyDllPath 代理dll的目录地址,要求两个dll放置在同一目录
*/
public static void loadDll(String proxyDllPath) {
String systemType = System.getProperties().getProperty("os.name");
System.err.println("加载lib之前打点");
String proxyFullPath = null;
if (systemType.indexOf("Windows") != -1) {
Decoder.dllPath = proxyDllPath + File.separator + "testdll.dll";
proxyFullPath = proxyDllPath + File.separator + "testjni.dll";
} else {
Decoder.dllPath = proxyDllPath + File.separator + "testdll.so";
proxyFullPath = proxyDllPath + File.separator + "testjni.so";
}
System.load(proxyFullPath);
}
public static void main(String[] args) {
// String property = System.getProperty("java.library.path");
// System.setProperty("java.library.path", property + File.pathSeparator + "E:\\test\\lib");
// System.err.println(System.getProperty("java.library.path"));
String systemType = System.getProperties().getProperty("os.name");
System.err.println("加载lib之前打点");
String proxyDllPath = null;
if (systemType.indexOf("Windows") != -1) {
proxyDllPath = "E:\\codes\\testjni\\temp";
} else {
proxyDllPath = "/root/code/cpp/testjni";
}
Decoder.loadDll(proxyDllPath);
Decoder d = getDecoder();
System.out.println("testdll version: " + d.version());
String s = "encode_testdata";
System.out.println(d.decode(s));
s = "encode_testdata_2";
System.out.println(d.decode(s));
System.out.println(d.encode("testdata", 0));
System.out.println(d.encode("testdata", 1));
}
}
1.1. 生成jni的头文件
找到java源代码的位置,就是源代码根目录,或者说包名的根目录
cd C:\leidian\JNI-test1\src\main\java
执行下列命令会编译java类并生成头文件
jdk9及以下(需要到编译后class的根包目录执行,执行对象是class文件,此命令在jdk14被移除)
javah -jni -v org.example.testjni.Decoder
jdk9及以上(不指定则默认使用系统编码, 通常是ANSI编码,比如win下中文版本一般是GBK)
javac -encoding UTF-8 -h . org/example/testjni/Decoder.java
2. windows下编译jni动态库
在vs向导页面创建一个dll库项目,并按如下设置,附加包含目录里面的目录路径是头文件路径

此外要注意这个不使用预编译头,如果你使用了windows相关的api,但是没有正确的引用pch.h, 会报错,这个时候就需要设置不使用预编译头

2.1. 目录结构
"jdk8_jni_heade"就是jdk根目录的include文件夹复制过来的,其实可以不用复制,直接在选项中指定目录位置就可以了
|--- include
| |--- org_example_testjni_Decoder.h (这是步骤1.1生成的头文件)
| |--- jdk8_jni_header(省略此文件夹内容,)
|--- testjni.h
|--- testjni.cpp
2.2. 本地库代码
这里假定我们jni调用的dll是testjni.dll,在testjni中加载并调用的另外一个dll是testdll.dll
org_example_testjni_Decoder.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_example_testjni_Decoder */
#ifndef _Included_org_example_testjni_Decoder
#define _Included_org_example_testjni_Decoder
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_example_testjni_Decoder
* Method: version
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_version
(JNIEnv*, jobject);
/*
* Class: org_example_testjni_Decoder
* Method: init
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT jlong JNICALL Java_org_example_testjni_Decoder_init
(JNIEnv*, jobject, jstring);
/*
* Class: org_example_testjni_Decoder
* Method: kill
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_org_example_testjni_Decoder_kill
(JNIEnv*, jobject, jlong);
/*
* Class: org_example_testjni_Decoder
* Method: encode
* Signature: (JLjava/lang/String;I)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_encode
(JNIEnv*, jobject, jlong, jstring, jint);
/*
* Class: org_example_testjni_Decoder
* Method: decode
* Signature: (JLjava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_decode
(JNIEnv*, jobject, jlong, jstring);
#ifdef __cplusplus
}
#endif
#endif
testjni.h
#pragma once
#ifndef TESTJNI_H
#define TESTJNI_H
#include "include/org_example_testjni_Decoder.h"
#include <windows.h>
#include <string>
#endif //TESTJNI_H
testjni.cpp
#include "testjni.h"
namespace {
// 只能在本 .cpp 文件中访问
static HMODULE hDll = NULL;
using Version = jstring(*)(JNIEnv*, jobject); // 定义函数指针类型
static Version version = NULL;
using Init = jlong(*)(JNIEnv*, jobject); // 定义函数指针类型
static Init init = NULL;
using Kill = void(*)(JNIEnv*, jobject, jlong); // 定义函数指针类型
static Kill kill = NULL;
using Encode = jstring(*)(JNIEnv*, jobject, jlong, jstring, jint); // 定义函数指针类型
static Encode encode = NULL;
using Decode = jstring(*)(JNIEnv*, jobject, jlong, jstring); // 定义函数指针类型
static Decode decode = NULL;
/*
* 释放加载的dll
*/
void free_dll() {
FreeLibrary(hDll);
init = NULL;
kill = NULL;
version = NULL;
encode = NULL;
decode = NULL;
hDll = NULL;
}
}
/*
* Class: org_example_testjni_Decoder
* Method: version
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_version
(JNIEnv* j_env, jobject j_obj) {
if (version != NULL) {
return version(j_env, j_obj);
}
// 获取异常类
jclass cls_run_excep = j_env->FindClass("java/lang/RuntimeException");
j_env->ThrowNew(cls_run_excep, "version method is null!");
return NULL;
}
/*
* Class: org_example_testjni_Decoder
* Method: init
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_org_example_testjni_Decoder_init
(JNIEnv* j_env, jobject j_obj, jstring j_dll_full_path) {
// 获取异常类
jclass cls_run_excep = j_env->FindClass("java/lang/RuntimeException");
if (hDll == NULL) {
// 获取java字符串
const char* dll_full_path = j_env->GetStringUTFChars(j_dll_full_path, nullptr);
// 不使用绝对路径的直接使用testdll.dll,则会从系统库路径加载,比如项目根目录
hDll = GetModuleHandleA(dll_full_path);
if (NULL == hDll) {
hDll = LoadLibraryA(dll_full_path);
}
// 释放字符串
j_env->ReleaseStringUTFChars(j_dll_full_path, dll_full_path);
if (NULL == hDll) {
j_env->ThrowNew(cls_run_excep, "load testdll.dll fail!");
// 错误处理
return NULL;
}
}
if (version == NULL) {
version = (Version)GetProcAddress(hDll, "Java_org_example_testdll_Decoder_version");
if (!version) {
free_dll();
j_env->ThrowNew(cls_run_excep, "Java_org_example_testdll_Decoder_version is not found!");
return NULL;
}
}
if (init == NULL) {
init = (Init)GetProcAddress(hDll, "Java_org_example_testdll_Decoder_init");
if (!init) {
free_dll();
j_env->ThrowNew(cls_run_excep, "Java_org_example_testdll_Decoder_init is not found!");
return NULL;
}
}
if (kill == NULL) {
kill = (Kill)GetProcAddress(hDll, "Java_org_example_testdll_Decoder_kill");
if (!kill) {
free_dll();
j_env->ThrowNew(cls_run_excep, "Java_org_example_testdll_Decoder_kill is not found!");
return NULL;
}
}
if (encode == NULL) {
encode = (Encode)GetProcAddress(hDll, "Java_org_example_testdll_Decoder_encode");
if (!kill) {
free_dll();
j_env->ThrowNew(cls_run_excep, "Java_org_example_testdll_Decoder_encode is not found!");
return NULL;
}
}
if (decode == NULL) {
decode = (Decode)GetProcAddress(hDll, "Java_org_example_testdll_Decoder_decode");
if (!decode) {
free_dll();
j_env->ThrowNew(cls_run_excep, "Java_org_example_testdll_Decoder_decode is not found!");
return NULL;
}
}
return init(j_env, j_obj);
}
/*
* Class: org_example_testjni_Decoder
* Method: kill
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_org_example_testjni_Decoder_kill
(JNIEnv* j_env, jobject j_obj, jlong j_handle) {
if (kill != NULL) {
kill(j_env, j_obj, j_handle);
free_dll();
}
// 获取异常类
jclass cls_run_excep = j_env->FindClass("java/lang/RuntimeException");
j_env->ThrowNew(cls_run_excep, "kill method is null!");
}
/*
* Class: org_example_testjni_Decoder
* Method: encode
* Signature: (JLjava/lang/String;I)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_encode
(JNIEnv* j_env, jobject j_obj, jlong j_handle, jstring j_serial, jint j_synctime) {
if (encode != NULL) {
return encode(j_env, j_obj, j_handle, j_serial, j_synctime);
}
// 获取异常类
jclass cls_run_excep = j_env->FindClass("java/lang/RuntimeException");
j_env->ThrowNew(cls_run_excep, "encode method is null!");
return NULL;
}
/*
* Class: org_example_testjni_Decoder
* Method: decode
* Signature: (JLjava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_decode
(JNIEnv* j_env, jobject j_obj, jlong j_handle, jstring j_data) {
if (decode != NULL) {
return decode(j_env, j_obj, j_handle, j_data);
}
// 获取异常类
jclass cls_run_excep = j_env->FindClass("java/lang/RuntimeException");
j_env->ThrowNew(cls_run_excep, "decode method is null!");
return NULL;
}
2.3 编译生成dll文件
直接点生成解决方案,就可以在项目根目录的相关目录找到
3. linux下编译jni动态库
3.2. 目录结构
"jdk8_jni_heade"就是jdk根目录的include文件夹复制过来的,其实可以不用复制,直接在选项中指定目录位置就可以了
|--- include
| |--- org_example_testjni_Decoder.h(这是步骤1.1生成的头文件)
| |--- jdk8_jni_header(省略此文件夹内容,)
|--- testjni.h
|--- testjni.cpp
3.2. 本地库代码
这里假定我们jni调用的dll是testjni.dll,在testjni中加载并调用的另外一个dll是testdll.dll
org_example_testjni_Decoder.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_example_testjni_Decoder */
#ifndef _Included_org_example_testjni_Decoder
#define _Included_org_example_testjni_Decoder
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_example_testjni_Decoder
* Method: version
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_version
(JNIEnv *, jobject);
/*
* Class: org_example_testjni_Decoder
* Method: init
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT jlong JNICALL Java_org_example_testjni_Decoder_init
(JNIEnv *, jobject, jstring);
/*
* Class: org_example_testjni_Decoder
* Method: kill
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_org_example_testjni_Decoder_kill
(JNIEnv *, jobject, jlong);
/*
* Class: org_example_testjni_Decoder
* Method: encode
* Signature: (JLjava/lang/String;I)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_encode
(JNIEnv *, jobject, jlong, jstring, jint);
/*
* Class: org_example_testjni_Decoder
* Method: decode
* Signature: (JLjava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_decode
(JNIEnv *, jobject, jlong, jstring);
#ifdef __cplusplus
}
#endif
#endif
testjni.h
#ifndef TESTJNI_H
#define TESTJNI_H
#include "include/org_example_testjni_Decoder.h"
#include <dlfcn.h>
#include <string>
#endif //TESTJNI_H
testjni.cpp
#include "testjni.h"
// 匿名命名空间
namespace {
// 只能在本 .cpp 文件中访问
static void* hDll = NULL;
using Version = jstring(*)(JNIEnv*, jobject); // 定义函数指针类型
static Version version = NULL;
using Init = jlong(*)(JNIEnv*, jobject); // 定义函数指针类型
static Init init = NULL;
using Kill = void(*)(JNIEnv*, jobject, jlong); // 定义函数指针类型
static Kill kill = NULL;
using Encode = jstring(*)(JNIEnv*, jobject, jlong, jstring, jint); // 定义函数指针类型
static Encode encode = NULL;
using Decode = jstring(*)(JNIEnv*, jobject, jlong, jstring); // 定义函数指针类型
static Decode decode = NULL;
/*
* 释放加载的dll
*/
void free_dll() {
dlclose(hDll);
init = NULL;
kill = NULL;
version = NULL;
encode = NULL;
decode = NULL;
hDll = NULL;
}
}
/*
* Class: org_example_testjni_Decoder
* Method: version
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_version
(JNIEnv* j_env, jobject j_obj) {
if (version != NULL) {
return version(j_env, j_obj);
}
// 获取异常类
jclass cls_run_excep = j_env->FindClass("java/lang/RuntimeException");
j_env->ThrowNew(cls_run_excep, "version method is null!");
return NULL;
}
/*
* Class: org_example_testjni_Decoder
* Method: init
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_org_example_testjni_Decoder_init
(JNIEnv* j_env, jobject j_obj, jstring j_dll_full_path) {
// 获取异常类
jclass cls_run_excep = j_env->FindClass("java/lang/RuntimeException");
if (hDll == NULL) {
// 获取java字符串
const char* dll_full_path = j_env->GetStringUTFChars(j_dll_full_path, nullptr);
// testdll.dll
hDll = dlopen(dll_full_path,RTLD_LAZY);
// 释放字符串
j_env->ReleaseStringUTFChars(j_dll_full_path, dll_full_path);
if (NULL == hDll) {
fprintf(stderr,"%s ", dlerror());
j_env->ThrowNew(cls_run_excep, "load testdll.so fail!");
// 错误处理
return 0;
}
}
if (version == NULL) {
version = (Version)dlsym(hDll, "Java_org_example_testdll_Decoder_version");
if (!version) {
free_dll();
j_env->ThrowNew(cls_run_excep, "Java_org_example_testdll_Decoder_version is not found!");
return 0;
}
}
if (init == NULL) {
init = (Init)dlsym(hDll, "Java_org_example_testdll_Decoder_init");
if (!init) {
free_dll();
j_env->ThrowNew(cls_run_excep, "Java_org_example_testdll_Decoder_init is not found!");
return 0;
}
}
if (kill == NULL) {
kill = (Kill)dlsym(hDll, "Java_org_example_testdll_Decoder_kill");
if (!kill) {
free_dll();
j_env->ThrowNew(cls_run_excep, "Java_org_example_testdll_Decoder_kill is not found!");
return 0;
}
}
if (encode == NULL) {
encode = (Encode)dlsym(hDll, "Java_org_example_testdll_Decoder_encode");
if (!kill) {
free_dll();
j_env->ThrowNew(cls_run_excep, "Java_org_example_testdll_Decoder_encode is not found!");
return 0;
}
}
if (decode == NULL) {
decode = (Decode)dlsym(hDll, "Java_org_example_testdll_Decoder_decode");
if (!decode) {
free_dll();
j_env->ThrowNew(cls_run_excep, "Java_org_example_testdll_Decoder_decode is not found!");
return 0;
}
}
return init(j_env, j_obj);
}
/*
* Class: org_example_testjni_Decoder
* Method: kill
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_org_example_testjni_Decoder_kill
(JNIEnv* j_env, jobject j_obj, jlong j_handle) {
if (kill != NULL) {
kill(j_env, j_obj, j_handle);
free_dll();
}
// 获取异常类
jclass cls_run_excep = j_env->FindClass("java/lang/RuntimeException");
j_env->ThrowNew(cls_run_excep, "kill method is null!");
}
/*
* Class: org_example_testjni_Decoder
* Method: encode
* Signature: (JLjava/lang/String;I)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_encode
(JNIEnv* j_env, jobject j_obj, jlong j_handle, jstring j_serial, jint j_synctime) {
if (encode != NULL) {
return encode(j_env, j_obj, j_handle, j_serial, j_synctime);
}
// 获取异常类
jclass cls_run_excep = j_env->FindClass("java/lang/RuntimeException");
j_env->ThrowNew(cls_run_excep, "encode method is null!");
return NULL;
}
/*
* Class: org_example_testjni_Decoder
* Method: decode
* Signature: (JLjava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_example_testjni_Decoder_decode
(JNIEnv* j_env, jobject j_obj, jlong j_handle, jstring j_data) {
if (decode != NULL) {
return decode(j_env, j_obj, j_handle, j_data);
}
// 获取异常类
jclass cls_run_excep = j_env->FindClass("java/lang/RuntimeException");
j_env->ThrowNew(cls_run_excep, "decode method is null!");
return NULL;
}
3.3. 使用g++编译生成so动态库
g++ testjni.cpp -std=c++14 -I ./include -I ./include/jdk8_jni_header/ -I ./include/jdk8_jni_header/linux/ -fPIC -shared -o testjni.so
-I 指定头文件目录
-fPIC 生成位置无关的代码
-shared 生成共享动态库
4.总结linux和windows下的动态库的动态加载差异
| 功能 | windows | linux | 附加说明 |
|---|---|---|---|
| 动态库后缀名 | 以.dll作为后缀,是PE文件格式 | 以lib开头,以.so作为后缀 | 存在差异 |
| 定义函数指针 | using Kill = void()(JNIEnv, jobject, jlong); | using Kill = void()(JNIEnv, jobject, jlong); | 完全一样 |
| 使用函数指针 | kill(j_env, j_obj, j_handle) | kill(j_env, j_obj, j_handle) | 完全一样 |
| 加载动态库api | HMODULE hDll= GetModuleHandleA(dll_full_path);/HMODULE hDll= LoadLibraryA(dll_full_path); | void* hDll = dlopen(dll_full_path,RTLD_LAZY); | 存在差异 |
| 加载动态库函数api | decode = (Decode)GetProcAddress(hDll, "Java_org_example_testdll_Decoder_decode"); | decode = (Decode)dlsym(hDll, "Java_org_example_testdll_Decoder_decode"); | 存在差异 |
| 释放动态库函数api | FreeLibrary(hDll); | dlclose(hDll); | 存在差异 |

浙公网安备 33010602011771号