JNI使用技巧点滴(二)

 
作者:normalnotebook

背景
上一讲我们介绍了 JNI 的基本概念和基本用法。现在介绍复杂一点的例子,用 JNI 调用其他工具生成的 DLL ,实现某个特定的功能。在这里我们将用 VC 编写一个 DLL ,供 JAVA 调用。
 
VC 编写简单 DLL
这个 DLL 实现一个简单的字符串传递,然后弹出一个消息框,显示所传递的字符串,同时将所传递的字符串变成小写,返回给 JAVA 程序。即 JAVA 程序和 VC 编写的 DLL 实现字符串互传。
打开 VC 集成开发环境,选择 file->new->Projects ,选择 MFC AppWizard(dll) ,然后输入工程名,在这里我们输入的是 VJString 。单击下一步,其余取默认选项。
VJString.h 头文件里面声明两个方法,示列代码如下:
………..
#include "resource.h"             // main symbols  这是 VC 生成的代码
//---------add by normalnotebook 8/9/2004 start----------//
void showMessage( char *text, char *caption);
void cToJavaStr( char *context);
//---------add by normalnotebook 8/9/2004   end ----------//
…………..
然后在 VJString.cpp 里面加入这两个方法的实现。
//---------add by normalnotebook 8/9/2004 start----------//
void showMessage( char *text, char *caption)
{
                  CString strText,strCaption;
                  strText.Format(_T("%s"),text);
                  strCaption.Format(_T("%s"),caption);
                  MessageBox(NULL,text,caption,MB_OK);
}
 
void cToJavaStr( char *context)
{
                  CString strContext;
                  strContext.Format("%s",context);
                  strContext.MakeLower();
                  strcpy(context,(LPCTSTR)strContext);
}
//---------add by normalnotebook 8/9/2004   end ----------//
到这里为止, DLL 部分就基本差不多了。大家也许很奇怪,为什么不用 CString 做参数,为什么要用最原始的 char * 。原因是这样的:在写 JNI 对应的 C 实现部分时,因为那是在 C/C++ 的编译环境下,根本不认识 CString 这个 MFC 类。
还应该在 VJString.def 文件里面做导出函数的说明,示列代码如下:
EXPORTS
    ; Explicit exports can go here
         showMessage
         cToJavaStr
现在就可以开始编译,链接它。然后它会生成一个 VJString.dll 这个 DLL 文件,同时也会生成一个 VJString.lib 这个文件,供调用这个 DLL 的程序使用(那个调用程序是静态链接这个 dll )。
dll lib 文件拷贝到一个文件夹下。我们此时还可以做一个 .h 文件,供调用者使用。在这里我们将导出两个方法,在这个文件夹下生成一个 VJString.h 的头文件。如果调用程序是动态链接,可以不要 .lib .h 文件。 VJString.h 头文件的内容如下:
_declspec ( dllexport ) void showMessage( char * , char *);
_declspec ( dllexport ) void cToJavaStr( char *);
到这里为止, VC DLL 部分就编写完毕。接下来就是完成 JNI 部分了。
 
 
JNI 部分
一. JAVA 部分
在这里我们将举一个包含包的情况,因为有包要比无包复杂。在实际的项目中,可能都是包含包的情况(具体有什么需要注意的地方,请参看我前一篇文章)。
我们新建一个 JAVA 文件,取名为 VJString.java; 其内容如下所示:
package com.convertString;
public class VJString
{
         static
         {
                   System.out.println(System.getProperty("java.library.path"));
                   System.loadLibrary("JNIDLL");
         }
         public native void showMsg(String text,String caption);
         public native String convertString(String context);
}
然后开始编译它,我们在这里写一个批处理文件。取名为 VJ.bat ,其内容如下:
javac -d . VJString.java
jar -cf VJString.jar com/
javah com.convertString.VJString
pause
然后保存它。双击 VJ.bat 运行它。是不是在当前文件夹下生成了三个文件,一个是 VJString.jar 文件, com_convertString_VJString.h 文件和 com 的文件夹。
也可以不写批处理文件,直接在 dos 下敲入命令,具体方法参见我前一篇文章。
 
二.对应的 C 实现部分
         在这里我们选择 VC 开发工具完成 C 实现部分。
         打开 VC 集成开发环境,选择 file->new->Projects ,选择 win32 Dynamic-link Library ,我们把工程命名为 JNIDLL ,其余选择默认。同时把 com_convertString_VJString.h VJString.h 两个头文件拷贝到这个工程下,同时也把 VJString.lib 文件也拷贝到这个工程下。 jni.h 的设置参见我前一篇文章。
         com_convertString_VJString.h VJString.h 两个头文件加入到工程中去。我们修改 com_convertString_VJString.h 里面的内容。我们将在里面添加两个函数,一个用来把 ANSI 编码转化为 UNICODE 编码。一个用来把 UNICODE 编码转化为 ANSI 编码。在 #ifdef __cplusplus 之前添加,添加的代码如下:
//---------add by normalnotebook 8/9/2004 start----------//
void convertUniToANSI(JNIEnv *,jstring , char * );
jstring convertANSIToUNI(JNIEnv *, char * );
//---------add by normalnotebook 8/9/2004   end ----------//
然后选择 new->File->C++ Source File ,输入文件名,我们取名为 JNIDLL 。然后就会生成一个空的 JNIDLL.cpp 文件,接下来就开始实现 JAVA 的对应方法了,示列如下:
         #include "com_convertString_VJString.h"
#include "stdio.h"
#include "VJString.h"
#include "windows.h"
 
/*
        * Class:     com_convertString_VJString
* Method:    showMsg
* Signature: (Ljava/lang/String;Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_convertString_VJString_showMsg
      (JNIEnv *env, jobject obj, jstring text, jstring caption)
{
                  char chText[256*2];
                  char chCaption[256*2];
                  convertUniToANSI(env,text,chText);
                  convertUniToANSI(env,caption,chCaption);
                  showMessage(chText,chCaption);
}
 
/*
        * Class:     com_convertString_VJString
        * Method:    convertString
* Signature: (Ljava/lang/String;)V
        */
JNIEXPORT jstring JNICALL Java_com_convertString_VJString_convertString
      (JNIEnv *env , jobject obj, jstring context)
{
                  char chContext[256*2];
                  convertUniToANSI(env,context,chContext);
                  cToJavaStr(chContext);
                  return convertANSIToUNI(env,chContext);
 
}
 
/*
* Class:     null
* Method:    convertUniToANSI
* detail:    UNICODE 编码转换成 ANSI 编码
*/
void convertUniToANSI(JNIEnv *env,jstring oldStr, char * newStr)
{
                  int desc_len=256*2;
                  int len;
                  if (oldStr==NULL||newStr==NULL)
                           return ;
                  wchar_t *w_buffer = new wchar_t[256];
                  wcscpy(w_buffer,env->GetStringChars(oldStr,0));
                  env->ReleaseStringChars(oldStr,w_buffer);
                  len = WideCharToMultiByte(CP_ACP,0,w_buffer,1024,newStr,desc_len,NULL,NULL);
                  if (len>0 && len<desc_len)
                  {
                           newStr[len]='/0';
                  }
                  delete [] w_buffer;
}
 
 
/*
* Class:     null
* Method:    convertANSIToUNI
* detail:    ANSI 编码转换成 UNICODE 编码
*/
jstring convertANSIToUNI(JNIEnv *env, char * cStr)
{      
                  int slen = strlen(cStr);
                  if (!env||slen==0)
                           return NULL;
                  jchar* buffer = new jchar[slen];
                  int len = MultiByteToWideChar(CP_ACP,0,cStr,strlen(cStr),buffer,slen);
                  if (len>0 && len < slen)
                           buffer[len]='/0';
                  jstring js = env->NewString(buffer,len);
                  delete [] buffer;
                  return js;   
}
函数 convertANSIToUNI() convertUniToANSI () 是两个通用函数,可以直接拷贝到你的代码中使用,只需要把字符串传递进来就可以了。在使用这两个函数前,一定要包含 #include "windows.h" 这个头文件,因为 MultiByteToWideChar() WideCharToMultiByte() 是在这里面声明的。
一定记得要在 project à setting à link à library module 里面加入 VJString.lib
现在就开始编译它,会生成一个 JNIDLL.dll 的文件。
 
测试部分
         JNIDLL.dll VJString.dll 拷贝到正确的路径下(路径设置具体参见前一篇文章)。然后开始写一个测试程序。对于有包的情况,包也一定要设置正确。如果是 jar 文件,直接丢到 JDK 目录下的 lib 文件夹下。或者设置路径也可。
         testJni.java 的内容如下所示:
         import com.convertString.VJString;
public class testJni
{
               public static void main(String args[])
               {
                            VJString str=new VJString();
                            String text,caption,context;
                            text=" 成功啦! ";
                            caption="Haha......";
                            context="FOUNDER";
                            str.showMsg(text,caption);
                            System.out.println(str.convertString(context));
               }
}
然后编译,运行。
呵呵,是不是成功了!但不要高兴的太早了。我在 java 控制台里面可以成功,但把它用到 jsp 页面就失败了,说是找不到本地化方法里面的函数。
 
后记
我们来通过其他的工具,打开 JNIDLL.dll 看看。
文件名: ……../JNIDLL/Debug/JNIDLL.dll
------------------------------------------------
导出表所处的节: .rdata
------------------------------------------------
原始文件名           JNIDLL.dll
nBase               00000001
NumberOfFunctions   00000002
NumberOfNames      00000002
AddressOfFunctions 0002AED8
AddressOfNames      0002AEE0
AddressOfNameOrd    0002AEE8
------------------------------------------------
导出序号   虚拟地址   导出函数名称
------------------------------------------------
00000001 0000101E _Java_com_convertString_VJString_convertString@12
00000002 0000100A _Java_com_convertString_VJString_showMsg@16
发现我们导出的函数是不是都变了,我们在程序中明明调用的是 convertString() showMsg(), 怎么都成了这样了?
其实这是微软的一套规则, @ 表示函数名称的结束,后面的数字代表的是参数所占的字节数。上面的原因可能就是这里出了错,因为你导出的函数和你调用的函数名称不一致。所以你的把导出函数的名称改正过来。方法就是加一个 def 文件。在写 JNI C 实现部分中,加入一个 JNIDLL.def 。其内容如下:
LIBRARY      "JNIDLL"
DESCRIPTION 'ImageConvertDll Windows Dynamic Link Library'
 
EXPORTS
    ; Explicit exports can go here
         Java_com_convertString_VJString_showMsg
         Java_com_convertString_VJString_convertString
然后重新编译它,然后用新的 JNIDLL.dll 替换旧的 dll ,在运行程序,发现在 jsp 页面也成功了。
我们再用工具分析这个 DLL 看看。
文件名: ……../JNIDLL/Debug/JNIDLL.dll
------------------------------------------------
导出表所处的节: .rdata
------------------------------------------------
原始文件名           JNIDLL.dll
nBase               00000001
NumberOfFunctions   00000002
NumberOfNames       00000002
AddressOfFunctions 0002AED8
AddressOfNames      0002AEE0
AddressOfNameOrd    0002AEE8
------------------------------------------------
导出序号   虚拟地址   导出函数名称
------------------------------------------------
00000001 0000101E Java_com_convertString_VJString_convertString
00000002 0000100A Java_com_convertString_VJString_showMsg
发现函数名称是不是变了。但现在的名字也不是我们所调用的名称啊?我想情况应该是这样的: JNI 是本地化方法,要在 java 虚拟机和其他的平台建立起关系,必然有她自己的一套规则,可能在这里有所体现。当我们在 java 中调用 JNI 生成的 DLL 里面的函数时,直接调用的是 convertString() showMsg() ,编译器这时就会在前面加上一个 Java ,然后加上包名,再加上本地化方法的类名,最后再加上所调用的函数,到 DLL 里面去找相应的函数,这时就变成了 Java_com_convertString_VJString_convertString
Java_com_convertString_VJString_showMsg ,是不是和 DLL 里面导出的函数一致了。如果你的包名换了,当然你得重新写一个 DLL
         还有一个有趣的现象是,用 dos 编译和在 Eclipse 里面写的程序,竟然打印出的 java.library.path 路径顺序有所不同。在我们实际的项目中,写 dos 下的程序,非要把 dll 放到系统盘的 windows/system32/ 下面。因为我们 COM 组件里面有一个要取当前的路径,然后在进行其他的操作。这是一种特殊的情况。但也同时告诉我们了一件事情,在 dos 下,在调用本地方法时,是 windows/system32/ 下面的某个东东再起桥梁作用。但在 Eclipse 里面,随便放置都可以。 我在写这个示列时,就放在与 java 文件的同一级目录下,运行是成功的。
    欢迎大家与我交流 normalnotebook@126.com
 

相关文章
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值