古怪的JNI问题
用过java的朋友们都知道JNI是java与C++等native程序交互的一个强大工具。它支持从java虚拟机里面访问系统的本地程序,不管是C++/C,VB等,只要是能变成DLL的(这里指windows平台,Linux平台的应该是so文件),Java都可以通过JNI来调用;反过来,C++要调用java程序也可以通过JNI实现。
不过,今天我就遇到了一个古怪的JNI调用问题。是这样的逻辑
1. Java程序通过JNI调用一个C++写的DLL
2. 这个DLL里面又动态load了另一个DLL
3. 后面这个DLL在里面有使用了COM组件
大家都知道要使用COM组件,那就得调用CoInitialize/Ex()函数来初始化。今天的问题就是出现在这个地方,一调这个函数程序就crash了。我刚开始是怀疑自己程序什么地方用来非法指针,但是检查再三后发现没有问题。后来我又怀疑是CoInitializeEx()函数用法有问题,我是这样用的
CoInitializeEx(0, COINIT_MULTITHREADED);
这里使用了多线程套间模式,但是这又有什么问题呢?除非java虚拟机在对JNI调用只是有单线程模式,显然Java不会这么去做,否则本来大多数使用JNI的目的是为了是程序跑的更快,限制只用单线程就违背了这个目的。经过,再三的上网搜索,最后我发现还是windows系统的问题。什么问题呢?通过用windbg查看了一个加载的DLL版本信息,我找到了问题所在。因为,我的操作系统上装了3个VS开发环境,VS2005,VS2008,VS2010,结果导致了系统默认的CRT运行库的版本是90,而java程序是采用版本71的CRT来工作的。根据MSDN上的描述,当java虚拟机的CRT版本与目标程序的CRT版本不一致时,JNI的操作行为将具有不确定性。这也是为什么,在有的环境里运行没问题,在本机上却不能work。
接下来就需要去修改系统默认的CRT版本了,应该是通过修改注册表来解决,我还得研究一下。^_^
最后找到了问题所在,不是运行库的问题,而是loadlibrary参数的问题,哈哈!
千万注意LoadLibraryEx中的标志DONT_RESOLVE_DLL_REFERENCES,这个标志极其危险,MSDN中是这样描述的:
If this value is used, and the executable module is a DLL, the system does not call DllMain for process and thread initialization and termination. Also, the system does not load additional executable modules that are referenced by the specified module.
If you are planning only to access data or resources in the DLL, it is better to use LOAD_LIBRARY_AS_DATAFILE.
如果这个值被设置,并且目标模块是DLL,系统将不会调用DLLMain在进程和线程初始和终止的时候。因此也就是说,系统不会加载额外的执行模块,尽管这些模块被指定模块引用。
如果你计划只访问在这个DLL里面数据和资源,这个标准要优于LOAD_LIBRARY_AS_DATAFILE.
看到这里,你或许会问这有什么危险的,很正常的一个标志而已。没错,当你是这个DLL的作者,你很清楚这个DLL的用途,那么不存在任何的问题。但是,如果你不是这个DLL的作者,你拿到这个DLL后没有人告诉你说它只是一个用于存放数据和资源功能的DLL。并且你很高兴在发现这个DLL里面有几个看起来不错的导出函数,于是你就尝试着去load这个DLL,Great! load成功!然后获取函数指针,也成功!这个时候你不知道自己走上了一条危险的路,因为但你在调用这个函数时可能会发生Crash。为什么会这样?MSDN已经描述的很清楚,当你load这个DLL的时候,系统不会去调用DLLMain这个函数,而通常情况下DLLMain会做一些初始化的工作,特别是引用COM组件的时候。
于是,你开始疯狂的调试找出错的地方,够郁闷。所以,建议大家如果你想将一个DLL当数据来使用,请不要使用里面的任何导出函数;如果你想添加导出函数,那么就不要使用LOAD_LIBRARY_AS_DATAFILE标志来load.