JNI不能直接调用C#的dll,原因是C#没有导出函数,C#的本地方法不能被java加载
可以使用C++做一个桥接,通过C++将C#的dll封装,java再调用C++的dll,即
:java →C++ wrapper → C#
网上有很多这方面的教程,但是中间有很多坑,没有讲明白,会导致很多错误。
jni
public class TestJNI{
public native int add(int a, int b);
static {
System.loadLibrary("wrapper");
}
}
1、需要加载的dll文件为wrapper.dll,但是不能写成System.loadLibrary("wrapper.dll"),这样JNI会去查找wrapper.dll.dll。
2、wrapper.dll加载路径在哪里: jni会从java.library.path的路径里查找指定dll文件。
当然,把生成的dll放到配置好的环境变量里面是一件麻烦的事情,可以通过如下方式加载指定路径的dll文件
jni
public class TestJNI{
public native int add(int a, int b);
static {
String value = System.getProperty("java.library.path");
value = value + ";" + new File("").getAbsolutePath();
System.setProperty("java.library.path", value);
System.loadLibrary("wrapper");
}
}
如此,只用加需要将加载的dll放到工程根目录下即可
由于我们的wapper.dll只是一个封装器,是用C++写的dll,wrapper会依赖C#写的真正的实现方法add.dll,如果将add.dll和wrapper.dll放到
同一个路径下,java会抛出如下错误:
jni
#
# A fatal error has been detected by the Java Runtime Environment:
#
# Internal Error (0xe0434f4d), pid=5252, tid=11108
#
# JRE version: Java(TM) SE Runtime Environment (7.0_80-b15) (build 1.7.0_80-b15)
# Java VM: Java HotSpot(TM) Client VM (24.80-b11 mixed mode windows-x86 )
# Problematic frame:
# C [KERNELBASE.dll+0xc54f]
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# E:\myfile\project\C-plus-plus\javassqlite\hs_err_pid5252.log
#
# If you would like to submit a bug report, please visit:
# http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
打开hs_err_pid5252.log你会发现这么一句话:
jni
Internal exceptions (2 events):
Event: 0.020 Thread 0x0084e000 Threw 0x0460ec20 at C:\re\jdk7u80\2329\hotspot\src\share\vm\prims\jni.cpp:3991
Event: 0.617 Thread 0x0084e000 Threw 0x046b9080 at C:\re\jdk7u80\2329\hotspot\src\share\vm\prims\jvm.cpp:1319
而jni.cpp第3991行是这样的:http://hg.openjdk.java.net/jdk7u/jdk7u60/hotspot/file/tip/src/share/vm/prims/jni.cpp
jni
static bool register_native(KlassHandle k, Symbol* name, Symbol* signature, address entry, TRAPS) {
methodOop method = Klass::cast(k())->lookup_method(name, signature);
if (method == NULL) {
ResourceMark rm;
stringStream st;
st.print("Method %s name or signature does not match",
methodOopDesc::name_and_sig_as_C_string(Klass::cast(k()), name, signature));
THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false); //这是第3991行
}
...
}
从错误中看好像是没找到包装器中的本地方法,其实这不是真正的原因。
通过排查发现,如果包装器中的方法不引用任何C#dll的内容,是没有问题的,只有包装器中的本地方法引用了C#的方法,才会出现问题。
换成jna同样会有这样的问题。只是抛错的形式不一样。
后来通过排查发现,是java没有加载到C++的dll依赖的C#的dll。
如何才能让java加载到C++的dll依赖的C#的dll呢?
一篇文章的提问中发现了思路:https://stackoverflow.com/questions/28093463/unresolved-link-error-using-jni-to-load-c-sharp-into-java-project
其中有一个网友回答:
So to figure out this problem, you can usethe process monitor to monitor the java.exe to see how it loading the DLLs. AsI observed,the java exe will try find the C# DLL in:
1、First in global assembly cache.(GAC). Only strongname assemblies are in GAC.
2、The folder of java.exe.
jni虽然在自己指定的路劲中找到了wrapper.dll,但是wrapper.dll所依赖的add.dll并没有被加载到内存中去,jni在调用wrapper.dll中的add方法时,会加载C#的add.dll的add方法,
此时内存中没有该方法,因为没有加载到C#的dll,从而导致错误。
解决方法是将add.dll放到java的运行时环境所用的bin目录中去,即“Thefolder of java.exe.”,从而让java能加载到所有C++的dll内部依赖的C#的dll。
javah生成的头文件是根据java类的路径命名的,如果java类改变了,头文件需要重新生成,否则就不能找到原来的方法了!!
其余问题网上都有答案。这几个问题比较隐晦,记录一下,以便后面有用到的时候可以借鉴
参考视频:https://www.youtube.com/watch?v=tI0tFUjeffA
参考文档:https://www.codeproject.com/Articles/378826/How-to-wrap-a-Csharp-library-for-use-in-Java