记于:2024-04-14 地点:浙江省·温州市·家里 天气:阴雨
背景 因为一个项目需要Java JNI调用C++ SDK,只有编译好的Windows DLL,没有对应的源码。
测试 简单demo JniTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package com.yeshimin.test.jni;import java.lang.reflect.Field;import java.util.Arrays;public class JniTest { static { try { String path = "C:\\Users\\ysd\\IdeaProjects\\test\\dll" ; addLibraryPath(path); } catch (Exception e) { throw new RuntimeException (e); } System.loadLibrary("jnitest" ); } public static void addLibraryPath (String pathToAdd) throws Exception { final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths" ); usrPathsField.setAccessible(true ); final String[] paths = (String[]) usrPathsField.get(null ); for (String path : paths) { if (path.equals(pathToAdd)) { return ; } } final String[] newPaths = Arrays.copyOf(paths, paths.length + 1 ); newPaths[newPaths.length - 1 ] = pathToAdd; usrPathsField.set(null , newPaths); } private static native String sayHello (String name) ; public static void main (String[] args) { String result = sayHello("ysm" ); System.out.println("result: " + result); } }
生成本地方法对应的C++代码的头部文件; 可以使用javah或javac(jdk版本>=9)命令; com_yeshimin_test_jni_JniTest.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <jni.h> #ifndef _Included_com_yeshimin_test_jni_JniTest #define _Included_com_yeshimin_test_jni_JniTest #ifdef __cplusplus extern "C" {#endif JNIEXPORT jstring JNICALL Java_com_yeshimin_test_jni_JniTest_sayHello (JNIEnv *, jclass, jstring) ; #ifdef __cplusplus } #endif #endif
接下需要根据头部文件中函数定义进行实现 JniTest.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include "com_yeshimin_test_jni_JniTest.h" JNIEXPORT jstring JNICALL Java_com_yeshimin_test_jni_JniTest_sayHello (JNIEnv *env, jclass cls, jstring j_str) { const char *c_str = NULL ; char buff[128 ] = {0 }; c_str = (*env)->GetStringUTFChars(env, j_str, NULL ); if (c_str == NULL ) { printf ("out of memory\n" ); return NULL ; } sprintf (buff, "Hello, %s\n" , c_str); (*env)->ReleaseStringUTFChars(env, j_str, c_str); return (*env)->NewStringUTF(env, buff); }
编译
1 2 3 4 5 6 # 编译JniTest.c,输出jnitest.dll,并将其放到自定义dll目录下 # 我的自定义dll目录为C:\Users\ysd\IdeaProjects\test \dll gcc -m64 -shared -o jnitest.dll -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include" -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include\win32" .jni\JniTest.c # jni需要jni.h相关头部,这里需要指定头部所在目录;具体位置因系统和jdk版本而异 # -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include" -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include\win32"
运行
不同包下 场景为,调用点所在的包(和类)与目标本地代码的声明不一致时,调用会报错; !!!重要前提,场景假设为,目标代码是编译好的dll;直接源码编译到一起的不算~
注意,JniTest.java和JniTestInternal.c是在不同包下,JniTestInternal是目标代码,JniTest是调用类
JniTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.yeshimin.test.jni;import java.lang.reflect.Field;import java.util.Arrays;public class JniTest { static { System.loadLibrary("jnitestinternal" ); } private static native String sayHelloInternal (String name) ; public static void main (String[] args) { String result = sayHelloInternal("ysm" ); System.out.println("result: " + result); } }
org_sample_JniTestInternal.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <jni.h> #ifndef _Included_org_sample_JniTestInternal #define _Included_org_sample_JniTestInternal #ifdef __cplusplus extern "C" {#endif JNIEXPORT jstring JNICALL Java_org_sample_JniTestInternal_sayHelloInternal (JNIEnv *, jclass, jstring) ; #ifdef __cplusplus } #endif #endif
JniTestInternal.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include "org_sample_JniTestInternal.h" JNIEXPORT jstring JNICALL Java_org_sample_JniTestInternal_sayHelloInternal (JNIEnv *env, jclass cls, jstring j_str) { const char *c_str = NULL ; char buff[128 ] = {0 }; c_str = (*env)->GetStringUTFChars(env, j_str, NULL ); if (c_str == NULL ) { printf ("out of memory\n" ); return NULL ; } sprintf (buff, "Hello, %s\n" , c_str); (*env)->ReleaseStringUTFChars(env, j_str, c_str); return (*env)->NewStringUTF(env, buff); }
编译
1 2 # 编译JniTestInternal.c,输出jnitestinternal.dll,并将其放到自定义dll目录下 gcc -m64 -shared -o jnitestinternal.dll -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include" -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include\win32" .jni\JniTestInternal.c
运行
如果将类名修改,使得调用点所在包(和类名)与目标定义不一致,比如将类名修改为”OtherName”,编译运行后,将会报错:
1 2 3 Exception in thread "main" java.lang.UnsatisfiedLinkError: org.sample.OtherName.sayHelloInternal(Ljava/lang/String;)Ljava/lang/String; at org.sample.OtherName.sayHelloInternal(Native Method) at org.sample.OtherName.main(OtherName.java:60)
解决方案 使用“代理”的方式调用目标方法;即在同包名(和类名)对应的本地实现中包含目标头部,并且封装一个方法进行目标调用;具体见下面代码
JniTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.yeshimin.test.jni;import java.lang.reflect.Field;import java.util.Arrays;public class JniTest { static { System.loadLibrary("jnitest" ); System.loadLibrary("jnitestinternal" ); } private static native String sayHello (String name) ; private static native String sayHelloByProxy (String name) ; private static native String sayHelloInternal (String name) ; public static void main (String[] args) { String result = sayHello("ysm" ); System.out.println("result: " + result); String result2 = sayHelloByProxy("ysm" ); System.out.println("result2: " + result2); String result3 = sayHelloInternal("ysm" ); System.out.println("result3: " + result3); } }
com_yeshimin_test_jni_JniTest.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <jni.h> #ifndef _Included_com_yeshimin_test_jni_JniTest #define _Included_com_yeshimin_test_jni_JniTest #ifdef __cplusplus extern "C" {#endif JNIEXPORT jstring JNICALL Java_com_yeshimin_test_jni_JniTest_sayHello (JNIEnv *, jclass, jstring) ; JNIEXPORT jstring JNICALL Java_com_yeshimin_test_jni_JniTest_sayHelloByProxy (JNIEnv *, jclass, jstring) ; #ifdef __cplusplus } #endif #endif
JniTest.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include "com_yeshimin_test_jni_JniTest.h" #include "org_sample_JniTestInternal.h" JNIEXPORT jstring JNICALL Java_com_yeshimin_test_jni_JniTest_sayHello (JNIEnv *env, jclass cls, jstring j_str) { const char *c_str = NULL ; char buff[128 ] = {0 }; c_str = (*env)->GetStringUTFChars(env, j_str, NULL ); if (c_str == NULL ) { printf ("out of memory\n" ); return NULL ; } sprintf (buff, "Hello, %s\n" , c_str); (*env)->ReleaseStringUTFChars(env, j_str, c_str); return (*env)->NewStringUTF(env, buff); } JNIEXPORT jstring JNICALL Java_com_yeshimin_test_jni_JniTest_sayHelloByProxy (JNIEnv *env, jclass cls, jstring j_str) { return Java_org_sample_JniTestInternal_sayHelloInternal(env, cls, j_str); }
org_sample_JniTestInternal.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <jni.h> #ifndef _Included_org_sample_JniTestInternal #define _Included_org_sample_JniTestInternal #ifdef __cplusplus extern "C" {#endif JNIEXPORT jstring JNICALL Java_org_sample_JniTestInternal_sayHelloInternal (JNIEnv *, jclass, jstring) ; #ifdef __cplusplus } #endif #endif
JniTestInternal.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include "org_sample_JniTestInternal.h" JNIEXPORT jstring JNICALL Java_org_sample_JniTestInternal_sayHelloInternal (JNIEnv *env, jclass cls, jstring j_str) { const char *c_str = NULL ; char buff[128 ] = {0 }; c_str = (*env)->GetStringUTFChars(env, j_str, NULL ); if (c_str == NULL ) { printf ("out of memory\n" ); return NULL ; } sprintf (buff, "Hello, %s\n" , c_str); (*env)->ReleaseStringUTFChars(env, j_str, c_str); return (*env)->NewStringUTF(env, buff); }
编译
1 2 3 4 5 6 7 # 编译JniTestInternal.c,输出jnitestinternal.dll,并将其放到自定义dll目录下 gcc -m64 -shared -o jnitestinternal.dll -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include" -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include\win32" .jni\JniTestInternal.c # 编译JniTest.c,输出jnitest.dll,并将其放到自定义dll目录下 gcc -m64 -shared -o jnitest.dll -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include" -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include\win32" -L"C:\Users\ysd\IdeaProjects\test\dll" -l"jnitestinternal" .jni\JniTest.c # -L"C:\Users\ysd\IdeaProjects\test\dll" -l"jnitestinternal" // 用于指定依赖的dll
运行
1 2 3 4 5 6 7 result: Hello, ysm result2: Hello, ysm Exception in thread "main" java.lang.UnsatisfiedLinkError: com.yeshimin.test.jni.JniTest.sayHelloInternal(Ljava/lang/String;)Ljava/lang/String; at com.yeshimin.test.jni.JniTest.sayHelloInternal(Native Method) at com.yeshimin.test.jni.JniTest.main(JniTest.java:69)
调用dll 场景跟上面【不同包下】类似,区别为目标是第三方sdk中的dll文件,且没有头部文件
JniTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.yeshimin.test.jni;import java.lang.reflect.Field;import java.util.Arrays;public class JniTest { static { System.loadLibrary("jnitest" ); System.loadLibrary("jnitestinternal" ); } private static native int dynamicInvoke () ; public static void main (String[] args) { int intResult = dynamicInvoke(); System.out.println("intResult: " + intResult); } }
com_yeshimin_test_jni_JniTest.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <jni.h> #ifndef _Included_com_yeshimin_test_jni_JniTest #define _Included_com_yeshimin_test_jni_JniTest #ifdef __cplusplus extern "C" {#endif JNIEXPORT jint JNICALL Java_com_yeshimin_test_jni_JniTest_dynamicInvoke (JNIEnv *, jclass) ; #ifdef __cplusplus } #endif #endif
JniTest.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <windows.h> #include <stdbool.h> #include "com_yeshimin_test_jni_JniTest.h" #include "org_sample_JniTestInternal.h" JNIEXPORT jint JNICALL Java_com_yeshimin_test_jni_JniTest_dynamicInvoke (JNIEnv *env, jclass cls) { typedef int (__stdcall *TargetMethodPtr) () ; TargetMethodPtr targetMethod; HMODULE hModule = LoadLibrary(TEXT("Log_MD_VC120_v3_1.dll" )); if (hModule != NULL ) { targetMethod = (TargetMethodPtr)GetProcAddress(hModule, "?ConfigureFromEnvironment@CLog@GenICam_3_1@@SA_NXZ" ); if (targetMethod != NULL ) { printf ("function found" ); bool result = targetMethod(); FreeLibrary(hModule); return result ? 1 : 0 ; } else { printf ("function not found" ); } FreeLibrary(hModule); } else { DWORD dwError = GetLastError(); printf ("Error code: %lu\n" , dwError); } return -1 ; }
编译
1 2 3 4 5 6 7 # 编译JniTestInternal.c,输出jnitestinternal.dll,并将其放到自定义dll目录下 gcc -m64 -shared -o jnitestinternal.dll -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include" -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include\win32" .jni\JniTestInternal.c # 编译JniTest.c,输出jnitest.dll,并将其放到自定义dll目录下 gcc -m64 -shared -o jnitest.dll -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include" -I"C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot\include\win32" -L"C:\Users\ysd\IdeaProjects\test\dll" -l"jnitestinternal" .jni\JniTest.c # -L"C:\Users\ysd\IdeaProjects\test\dll" -l"jnitestinternal" // 用于指定依赖的dll
运行 将上面两个dll以及用到的第三方dll及其依赖dll放到项目根目录下才行正常运行,原因未知,见【遇到的问题】第3点
遇到的问题 1.提示32位代码在64位架构下跑不通;是下载错了mingw架构,应该下载64位的 2.指定自定义dll目录无效;解决方法见上面【简单demo】代码 3.在【调用dll】测试过程中,dll文件放到自定义dll目录下,链接不到,放到项目根目录下就可以,暂时搞不清楚 4.在【调用dll】测试过程中,一直获取不到目标函数; 开始是通过dllexp工具查看的函数函数名称为 public: static bool __cdecl GenICam_3_1::CLog::ConfigureFromEnvironment(void), 也试过 GenICam_3_1::CLog::ConfigureFromEnvironment(void) 和 GenICam_3_1::CLog::ConfigureFromEnvironment,都不行 后来是通过visual studio的dumpbin工具查看到dll的正确函数名 ?ConfigureFromEnvironment@CLog@GenICam_3_1@@SA_NXZ
参考资料