java.lang.UnsatisfiedLinkError

前言

今天有同事要用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了。

笔记

调用DLL的java文件

  • 生成的DLL平台类型 必须和 java工程要求的
  • 这个java文件必须和要调用的DLL同名 e.g. dll 名称 = TestDll.dll,那么java文件名称必须为TestDll.java
  • 在TestDll.java中的包名必须和此java文件在同事工程那的包名一致(同事必须先将具体的包名给写jniDLL的c同事).
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)

\com\hjk\driver下的TestDLL.java

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());
	}
}

从.java生成接口定义头文件

/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 class 的命令行脚本

java_call_c+++dll.bat

java -cp . com.hjk.driver.TestDLL
pause

总结

当以前好好的东西不能用时,能重现正常和不正常的场景,分析出原因。问题就好解决了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值