servlet中Java调用C++,JNI



我想尝试用Java调用旧有的c++代码,在servlet中。这样以前的就代码就可以变成web应用了
其实我研究这个是想以前的应用只要满足了servlet的接口是不是就可以部署到云上,因为Amazon是支持这种部署的,所以想做些试验。
c++是很熟悉了,但是JNI或者Java都不大熟悉。
先要搞出个servlet,我还不太会,所以用的是GWT的模板,这样就有了一个可以debug的servlet。
就这个小事还有点故事,IE总是不能运行,总让我下载GWT的插件,我手动下载并且安装了,但是它还是让我下载。
后来我修改IE的security让activeX可以运行就好了,就有个提示需要手动确认。但是我找了activeX的安装路径并没有发现任 何新的ActiveX。
实在是不明白没有安装ActiveX,为啥需要运行ActiveX。可能因为我还不够理解GWT到底是什么。
anyway,我的servlet是有了。
下面就是要创建Java会用的C++程序了。
先在Java中定义这个要在C++里实现的函数的原型

package example.HelloServletAAA.server; 
 
 public class NativeCaller { 
 
 public native int CallNativeGetInt(int argPlayerID); 
 
 static 
 
 { 
 
 System.out.print('start load!!!'); 
 
 System.loadLibrary('NativeHelloAAA'); 
 
 System.out.print('end load!!!'); 
 
 } 
 
 }


这个函数接受一个int返回一个int
然后创建C++的项目,我发现这个c++的项目需要是多字节码的
在c++的 头文件中声明要实现的那个函数

#include 
 
 #ifndef _Included_APCluster 
 
 #define _Included_APCluster 
 
 #ifdef __cplusplus 
 
 extern 'C' { 
 
 #endif 
 
 JNIEXPORTjint JNICALL Java_APCluster_CallAPClusterDll 
 
 (JNIEnv *, jobject, jint); 
 
 #ifdef __cplusplus 
 
 } 
 
 #endif 
 
 #endif


注意前面多的参数,一定是转换过程中需要的
头文件和库文件都从JDk中拿来
然后在cpp中实现刚才声明的函数

#ifdef __cplusplus 
 
 extern 'C' { 
 
 #endif 
 
 JNIEXPORT jint JNICALL Java_APCluster_CallAPClusterDll 
 
 (JNIEnv *env, jobject _obj, jint _arg_int) 
 
 { 
 
 return 59; 
 
 } 
 
 #ifdef __cplusplus 
 
 } 
 
 #endif


如果Java代码没有找到想加载的dll会报这个异常java.lang.UnsatisfiedLinkError: no NativeHelloAAA in java.library.path
放在这里依然不好,C:\Users\Administrator\workspace\HelloServletAAA\war\WEB-INF\lib。
最后我把加载路径改成绝对路径就可以了
System.load('C:/Users/Administrator/workspace/HelloServletAAA/war/WEB-INF/lib/NativeHelloAAA.dll');
或者放在这里C:\Program Files\Java\jre6\bin
其实可以放的地方可以查到
System.out.println(System.getProperty('java.library.path'));
得到新的错误
java.lang.UnsatisfiedLinkError: C:\Users\Administrator\workspace\HelloServletAAA\war\WEB-INF\lib\NativeHelloAAA.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
这是由于我的c++项目设置不对,设置成64的以后就可以加载了
设置成64位的项目要注意设置正确的sub-system和entry point。
SubSystem = Windows (/SUBSYSTEM:WINDOWS)
Entry Point = DllMain
然后接着就是这个错误
Caused by: java.lang.UnsatisfiedLinkError: example.HelloServletAAA.server.NativeCaller.CallNativeGetInt(I)I
这个错误看起来是在指定的dll中找不到方法定义
这个错误的原因就是java和c++两边的签名不吻合
其实有个简单的解决办法,就是用java带的工具生成c++项目用的头文件
workspace\HelloJNICallerAAA\bin>javah -jni NativeCaller
要想用这个命令,要设置java的路径到环境变量中另外java class所在的路径要么也设置成环境变量,要么就当成当前的运行路径才可以
这样生成的头文件类似下面

#include 
 
 #ifndef _Included_NativeCaller 
 
 #define _Included_NativeCaller 
 
 #ifdef __cplusplus 
 
 extern 'C' { 
 
 #endif 
 
 JNIEXPORT jint JNICALL Java_NativeCaller_CallerNativeGetInt 
 
 (JNIEnv *, jobject, jint); 
 
 #ifdef __cplusplus 
 
 } 
 
 #endif 
 
 #endif


我就发现我的错误了,java中的类要和c++的函数名对应,我以前以为的是java的类是和c++的dll文件名对应,实际上dll文件名在整个调用中不重要,只要能加载就可以了
---------------------------------------------------------------------------------
然后我在我的servlet上试验有遇到了一些困难
servlet的路径要复杂,另外还有包的命名也会增加困难。
最后发现正确的命令是这样

C:\Users\Administrator\workspace\HelloServletAAA\war\WEB-INF\classes>javah -jni -classpath . example.HelloServletAAA.server.NativeCaller 
 
 生成的头文件类似这样 
 
 #include <jni.h> 
 
 #ifndef _Included_example_HelloServletAAA_server_NativeCaller 
 
 #define _Included_example_HelloServletAAA_server_NativeCaller 
 
 #ifdef __cplusplus 
 
 extern 'C' { 
 
 #endif 
 
 JNIEXPORT jint JNICALL Java_example_HelloServletAAA_server_NativeCaller_CallNativeGetInt 
 
 (JNIEnv *, jobject, jint); 
 
 #ifdef __cplusplus 
 
 } 
 
 #endif 
 
 #endif


函数的签名反映了包和类的名字
-------------------------------------------------------------------------------------------
怎么debug JNI中的c++项目
很简单,先让java这边停在native的调用之前
然后用c++的编译器attach上去,attach JavaW或者Java的进程就可以了
Switch to Visual Studio, and bring up the 'Attach to process' dialog (Ctrl-Alt-P). Select the java process (javaw.exe or java.exe) by typing 'j'


4 加载中...


内容加载失败,点击此处重试