分享
 
 
 

JNI使用技巧点滴(二)

王朝other·作者佚名  2007-02-08
窄屏简体版  字體: |||超大  

作者: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

如果转载请注明出处与作者。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有