作者: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
欢迎大家与我交流 normalnotebook@126.com
相关文章
在Debian中打造属于自己的deb包 |
构造函数浅析 |
JNI使用技巧点滴(二) |
写程序应该注意的地方 |
java 异常处理学习笔记 |