小试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库项目,并按如下设置,附加包含目录里面的目录路径是头文件路径
clip

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

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); 存在差异
posted @ 2025-11-20 14:51  zeromi  阅读(7)  评论(0)    收藏  举报