赞
踩
今天有同事要用java调用c++写的dll. 以前已经在博客上做好笔记,并上传了demo工程。
从自己的资源中,下载了那个dmeo, 给他用。我直接写好的bat,去调用class中调用dll的方法是好使的。
但是他移植到自己工程中,就报错如下:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no TestDLL in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1857)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1119)
at com.user.usr1.TestDLL.<clinit>(TestDLL.java:17)
这个错误,一般是DLL的路径,或java中载入DLL时不对,或c++的DLL中的方法名和报名对不上。
demo中原来写的简单,就在com包下有个测试的class.
同事用的环境是在com.x.y下的.java生成的class.
因为dll是已经编译好的,只是我开发机上的JDK是32bits的,他开发机上是64bits的。我就将dll没改,只是编译成64bits, 然后用他给的JDK配好环境,在命令行下用java调用生成好的class, 这时是好使的。
然后,我将包路径从com 改成. com/user/usr1,模拟他那的包路径为多个名称的情况。果真,也出现了 java.lang.UnsatisfiedLinkError 的报错。
这时,已经可以知道是包名称引起的问题。那只要将工程中,所有和DLL相关的包名称都改成多个名字拼接的情况就O了。
package com.hjk.driver; // 这个包名必须和同事给的相同,如果他用的包名不是com.hjk.driver,调用接口就会失败。 public class TestDLL { //从指定地址读数据 // com.hjk.driver public native String readData(); static { System.out.println(System.getProperty("java.library.path")); //如果执行环境是linux这里加载的是SO文件,如果是windows环境这里加载的是dll文件 // System.loadLibrary("TestDLL"); // D:\ls\local_svn_checkout\src\for_user\rz_jikai\src\java_call_c++_dll\com // System.load("D:\\ls\\local_svn_checkout\\src\\for_user\\rz_jikai\\src\\java_call_c++_dll\\com\\TestDll.dll"); // System.load("D:\\TestDll.dll"); System.load("D:\\ls\\local_svn_checkout\\src\\for_user\\rz_jikai\\src\\java_call_c++_dll\\com\\hjk\\driver\\TestDll.dll"); // 将c++ dll 拷贝到 com目录 // 执行 genheader.bat, 编译好class 之后,在com同级目录执行class java -cp . com/TestDLL } public static void main(String[] args) { TestDLL td = new TestDLL(); System.out.println(td.readData()); } }
如果在java端调用的位置(包名)变了,还调用原来没改的DLL, 方法名自然就找不到了。调用时,就会引起 java.lang.UnsatisfiedLinkError 报错。
以前做的笔记管用,虽然简单,但是做后续实验很方便。
这次,帮同事解决完这个报错(就是包名变了,DLL没有重新编译),改过的代码贴一下,下次用。
// TestDll.cpp : Defines the entry point for the DLL application. // #include "stdafx.h" #include <stdio.h> #include <string> #include "TestDll.h" #include "com_hjk_driver_TestDLL.h" // 这个接口文件,是用java工具从调用jni_dll的.java中自动生成的,如果包名变了,这个.h名称变了,dll提供的接口函数也变了。 jstring pChar2JString(JNIEnv* env, const char* pat); char* JString2pChar(JNIEnv* env, jstring jstr); BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } // This is an example of an exported variable TESTDLL_API int nTestDll=0; // This is an example of an exported function. TESTDLL_API int fnTestDll(void) { return 42; } // This is the constructor of a class that has been exported. // see TestDll.h for the class definition CTestDll::CTestDll() { return; } jstring pChar2JString(JNIEnv* env, const char* pat) { jclass strClass = env->FindClass("Ljava/lang/String;"); jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V"); jbyteArray bytes = env->NewByteArray((jsize)strlen(pat)); env->SetByteArrayRegion(bytes, 0, (jsize)strlen(pat), (jbyte*)pat); jstring encoding = env->NewStringUTF("utf-8"); return (jstring)env->NewObject(strClass, ctorID, bytes, encoding); } char* JString2pChar(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = env->FindClass("java/lang/String"); jstring strencode = env->NewStringUTF("utf-8"); 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); return rtn; } // 包名不一样,接口函数的全程也不一样。虽然都是叫 readData() JNIEXPORT jstring JNICALL Java_com_hjk_driver_TestDLL_readData(JNIEnv * env, jobject javaboj) { char str[16]; memset(str, 0, 16); sprintf(str, "%s", "ahha"); return pChar2JString(env, str); }
jni将接口改完后,要编译成和java运行环境一样的平台类型(32bits or 64bits)
package com.hjk.driver; // 这个包名生成的方法和其他包名生成的方法不同,java同事如果自己写不来c++接口DLL, 要将这个包名和接口定义(入参,出参)用java生成好为一个.h, 告诉c++同事去做jni的DLL. // 类名必须和DLL名称一致 // 类所在的文件名必须和DLL名称一致 public class TestDLL { //从指定地址读数据 // com.hjk.driver public native String readData(); static { System.out.println(System.getProperty("java.library.path")); //如果执行环境是linux这里加载的是SO文件,如果是windows环境这里加载的是dll文件 // System.loadLibrary("TestDLL"); // D:\ls\local_svn_checkout\src\for_user\rz_jikai\src\java_call_c++_dll\com // System.load("D:\\ls\\local_svn_checkout\\src\\for_user\\rz_jikai\\src\\java_call_c++_dll\\com\\TestDll.dll"); // System.load("D:\\TestDll.dll"); System.load("D:\\ls\\local_svn_checkout\\src\\for_user\\rz_jikai\\src\\java_call_c++_dll\\com\\hjk\\driver\\TestDll.dll"); // 将c++ dll 拷贝到 com目录 // 执行 genheader.bat, 编译好class 之后,在com同级目录执行class java -cp . com/TestDLL } public static void main(String[] args) { TestDLL td = new TestDLL(); System.out.println(td.readData()); } }
/genheader.bat
echo on
rem 环境变量设在Windows系统环境中
rem set JAVA_HOME="D:\Program Files\Java\jdk1.6.0_17"
rem set PATH="D:\Program Files\Java\jdk1.6.0_17\bin";%PATH%
rem 由 .java 生成 .class
rem com.user.usr1
rem 这里的路径就是包名的组成
javac .\com\hjk\driver\TestDLL.java
rem 由.class 生成 .h
javah -jni -classpath . com.hjk.driver.TestDLL
@pause
java_call_c+++dll.bat
java -cp . com.hjk.driver.TestDLL
pause
当以前好好的东西不能用时,能重现正常和不正常的场景,分析出原因。问题就好解决了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。