Java-JNI-资料全整理(含自己的常见问题总结)_第1页
Java-JNI-资料全整理(含自己的常见问题总结)_第2页
Java-JNI-资料全整理(含自己的常见问题总结)_第3页
Java-JNI-资料全整理(含自己的常见问题总结)_第4页
Java-JNI-资料全整理(含自己的常见问题总结)_第5页
已阅读5页,还剩74页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

JNI调用的考前须知2JNI系统一些细节点总结3Java和C之间互相传递字符串7Java和C互操作的异常问题处理9Java与C\C++之间通过JNI传递中文字符串9Java:JNI完全手册17JNI使用技巧点滴〔二〕23JNI局部24Tomcat+jsp+javaBean的例子26JNI调用固有方法30Java根底知识——JNI入门介绍〔下〕32Java调用本地方法(JNI浅谈)38使用JNI的步骤44JAVA与C++::关于JNI中文字符串操作问题总结47Windows平台上实现Java本地方法实用的例如、步骤和准那么51例解VC++6.0实现JNI58JNI中文处理问题小结63jni的中文字符串处理72JNI中jstring类型与c语言中的字符串的转换74jstring和char*之间的转换方法76用Delphi开发JNI(JavaNativeInterface)应用(一)77java通过JNI与delphi交互80使用JNI技术实现JAVA程序调用dll83HP-UX下使用JNI访问标准C++程序89JNI调用的考前须知JNI的简单教程网上很多,看看就能够明白,照着操作也根本能够做下来。但是因为大多的教程写的都不够详细,在照着操作时候可能会遇到几个小问题,故开篇时在这里简单总结一下。在C语言环境中编译代码,出现编译错误,类型为找不到jni.h文件。解决方法:在jdk中寻找到jni.h和jni_md.h文件,将这两个文件拷贝到C语言编译器的include目录中。具体的目录可能会跟编译器有关。比方DevC++中,将其直接拷贝到include目录下即可。在C语言环境中编译代码,出现编译错误,类型为参数错误。解决方法:不能简单的把生成的.h文件中的方法名直接拷贝到.c文件中去,需要参加具体的参数。例如JNIEXPORTvoidJNICALLJava_testdll(JNIEnv*,jclass,jint)应该更改为JNIEXPORTvoidJNICALLJava_testdll(JNIEnv*para1,jclass,jintpara2)。在Java中调用JNI时候,java.lang.UnsatisfiedLinkError错误发生。解决方法:生成的dll文件放错了位置,所以不能加载dll文件。试试将dll文件放在与class文件同一目录,再试试放在package的根目录。1、建的dll工程里一定要是.c文件。2、一定要是release编译的dll文件,不要用debug生成的。3、javah时使用完整包名.类名(重要!!!)编译举例:调用JNI的Java源文件FpMatch.java。packagepackagecom.until.tc;publicclassFpMatch{ publicstaticnativeintFpMatch_TESO(StringszReg,StringszVer,intnLevel); publicFpMatch(){ } /*载入动态库*/ static{ try{ System.loadLibrary("FpMatch_TESO_JNI"); }catch(Exceptione){ e.printStackTrace(); } }}编译说明: javac-d./ FpMatch.java-d./参数会在当前目录下,依次建立package指定的目录,并把FpMatch.class放在package指定路径下,为下一步的javah做准备。 javah-d./com.until.tc.FpMatch 增加-d参数才可以生成正确的.h文件。这样JNI调用函数名里才包括了package的路径。JNI系统一些细节点总结一.系统环境2二.JNI的简要使用例子2三.JNI调用中考虑的问题3四.JNI中对JAVA传入数据的处理5五.JAVA调用本地接口后的数据处理问题5六.JAVA中的一些小东西5一.系统环境linux操作系统kernel2.4.2,安装j2sdk1.4.0二.JNI的简要使用例子下面是一个简单的例子实现打印一句话的功能,但是用的c的printf最终实现。一般提供应java的jni接口包括一个so文件〔封装了c函数的实现〕和一个java文件〔需要调用path的类〕。1.JNI的目的是使java方法中能够调用c实现的一些函数,比方以下的java类,就需要调用一个本地函数testjni〔一般声明为privatenative类型〕,首先需要创立文件weiqiong.java,内容如下:classweiqiong{static{System.loadLibrary("testjni");//载入静态库,test函数在其中实现}privatenativevoidtestjni();//声明本地调用publicvoidtest(){testjni();}publicstaticvoidmain(Stringargs[]){weiqionghaha=newweiqiong();haha.test();}}2.然后执行javacweiqiong.java,如果没有报错,会生成一个weiqiong.class。3.然后执行javahweiqiong,会生成一个文件weiqiong.h文件,其中有一个函数的声明如下:JNIEXPORTvoidJNICALLJava_weiqiong_testjni(JNIEnv*,jobject);4.创立文件testjni.c将上面那个函数实现,内容如下:#include<stdio.h>#include<weiqiong.h>JNIEXPORTvoidJNICALLJava_weiqiong_testjni(JNIEnv*env,jobjectobj){printf("hahagointoc!!!n");}5.为了生成.so文件,创立makefile文件如下:libtestjni.so:testjni.omakefilegcc-Wall-rdynamic-shared-olibtestjni.sotestjni.otestjni.o:testjni.cweiqiong.hgcc-Wall-ctestjni.c-I./-I/usr/java/j2sdk1.4.0/include-I/usr/java/j2sdk1.4.0/include/linuxcl:rm-rf*.o*.so注意:gcc前面是tab空,j2sdk的目录根据自己装的j2sdk的具体版本来写,生成的so文件的名字必须是loadLibrary的参数名前加“lib〞。6.exportLD_LIBRARY_PATH=.,由此设置library路径为当前目录,这样java文件才能找到so文件。一般的做法是将so文件copy到本机的LD_LIBRARY_PATH目录下。7.执行javaweiqiong,打印出结果:“hahagointoc!!!〞三.JNI调用中考虑的问题在首次使用JNI的时候有些疑问,后来在使用中一一解决,下面就是这些问题的备忘:1。java和c是如何互通的?其实不能互通的原因主要是数据类型的问题,jni解决了这个问题,例如那个c文件中的jstring数据类型就是java传入的String对象,经过jni函数的转化就能成为c的char*。对应数据类型关系如下表:Java类型本地c类型说明booleanjboolean无符号,8位bytejbyte无符号,8位charjchar无符号,16位shortjshort有符号,16位intjint有符号,32位longjlong有符号,64位floatjfloat32位doublejdouble64位voidvoidN/AJNI还包含了很多对应于不同Java对象的引用类型如以下图:2。如何将java传入的String参数转换为c的char*,然后使用?java传入的String参数,在c文件中被jni转换为jstring的数据类型,在c文件中声明char*test,然后test=(char*)(*env)->GetStringUTFChars(env,jstring,NULL);注意:test使用完后,通知虚拟机平台相关代码无需再访问:(*env)->ReleaseStringUTFChars(env,jstring,test);3.将c中获取的一个char*的buffer传递给java?这个char*如果是一般的字符串的话,作为string传回去就可以了。如果是含有’’的buffer,最好作为bytearray传出,因为可以制定copy的length,如果copy到string,可能到’’就截断了。有两种方式传递得到的数据:一种是在jni中直接new一个byte数组,然后调用函数(*env)->SetByteArrayRegion(env,bytearray,0,len,buffer);将buffer的值copy到bytearray中,函数直接returnbytearray就可以了。一种是return错误号,数据作为参数传出,但是java的根本数据类型是传值,对象是传递的引用,所以将这个需要传出的byte数组用某个类包一下,如下:classRetObj{publicbyte[]bytearray;}这个对象作为函数的参数retobj传出,通过如下函数将retobj中的byte数组赋值便于传出。代码如下:jclasscls;jfieldIDfid;jbyteArraybytearray;bytearray=(*env)->NewByteArray(env,len);(*env)->SetByteArrayRegion(env,bytearray,0,len,buffer);cls=(*env)->GetObjectClass(env,retobj);fid=(*env)->GetFieldID(env,cls,"retbytes","[B"]);(*env)->SetObjectField(env,retobj,fid,bytearray);4。不知道占用多少空间的buffer,如何传递出去呢?在jni的c文件中new出空间,传递出去。java的数据不初始化,指向传递出去的空间即可。四.JNI中对JAVA传入数据的处理1.如果传入的是bytearray的话,作如下处理得到buffer:char*tmpdata=(char*)(*env)->GetByteArrayElements(env,bytearray,NULL);(*env)->ReleaseByteArrayElements(env,bytearray,tmpdata,0);五.JAVA调用本地接口后的数据处理问题1.java得到的数据是String的话,直接处理就可以了。2.得到的如果是bytearray的话,作如下处理:DataInputStreamin=newDataInputStream(newByteArrayInputStream(bytearray));byte[]byte1=newbyte[36];in.read(byte1,0,36);Stringstring=newString(byte1);System.out.println("读出的第一个字段为:"+string);六.JAVA中的一些小东西1.JAVA中常用数据类型转换函数string->byteBytestaticbyteparseByte(Strings)byte->stringBytestaticStringtoString(byteb)char->stringCharacterstaticStringtoString(charc)string->ShortShortstaticShortparseShort(Strings)Short->StringShortstaticStringtoString(Shorts)String->IntegerIntegerstaticintparseInt(Strings)Integer->StringIntegerstaticStringtostring(inti)String->LongLongstaticlongparseLong(Strings)Long->StringLongstaticStringtoString(Longi)String->FloatFloatstaticfloatparseFloat(Strings)Float->StringFloatstaticStringtoString(floatf)String->DoubleDoublestaticdoubleparseDouble(Strings)Double->StringDoublestaticStringtoString(Doubled)2.类的数据类型转换//转换string为intpublicstaticintstringToInt(Stringintstr){Integerinteger;integer=Integer.valueOf(intstr);returnValue();}//转换int为stringpublicstaticStringintToString(intvalue){Integerinteger=newInteger(value);returninteger.toString();}//转换string为floatpublicstaticfloatstringToFloat(Stringfloatstr){Floatfloatee;floatee=Float.valueOf(floatstr);returnfloatee.floatValue();}//转换float为stringpublicstaticStringfloatToString(floatvalue){Floatfloatee=newFloat(value);returnfloatee.toString();}//changethestringtypetothesqlDatetypepublicstaticjava.sql.DatestringToDate(StringdateStr){returnjava.sql.Date.valueOf(dateStr);}//changethesqlDatetypetothestringtypepublicstaticStringdateToString(java.sql.Datedatee){returndatee.toString();}Java和C之间互相传递字符串Passastringto/fromJavato/fromCToJavafromC(asseenfromthepreviousHow-to):#include"JavaHowTo.h"JNIEXPORTjstringJNICALLJava_JavaHowTo_sayHello(JNIEnv*env,jobjectobj){returnenv->NewStringUTF("Helloworld");}FromJavatoC:SupposewehaveaJavaClasspublicclassMyClass{publicStringsayHello(){return"HelloworldFromJava";}}thenfromC,wewanttocalltheJavasayHello()methodwhichreturnsaString:JNIEXPORTvoidJNICALLJava_JavaHowTo_sayHello(JNIEnv*env,jobjectobj){constchar*str;jclassmyclass_class=(jclass)env->NewGlobalRef(env->FindClass("MyClass"));//weneedtheMyClassconstructorjmethodIDconstructorID=env->GetMethodID(myclass_class,"","()V");//andthesayHello()methodjmethodIDmethodID=env->GetMethodID(myclass_class,"sayHello","()Ljava/lang/String;");//instanciateaMyClassobjectjobjectmyclass_object=env->NewObject(myclass_class,constructorID);//callthesayHello()methodjstrings=(jstring)env->CallObjectMethod(myclass_object,methodID);//converttheJavaStringtouseitinCstr=env->GetStringUTFChars(s,0);printf("%s",str);env->ReleaseStringUTFChars(s,str);}TheJavaJNIwrapperwouldbeclassJavaHowTo{publicnativevoidsayHello();static{System.loadLibrary("javahowto");}}Andfinally,touseitpublicclassJNIJavaHowTo{publicstaticvoidmain(String[]args){JavaHowTojht=newJavaHowTo();jht.sayHello();}}Java和C互操作的异常问题处理Java中需要调用C代码,C代码中反过来又会操作Java中的对象,这就需要在C代码中处理Java操作可能出现的异常。如果不进行处理,一旦发生异常,C代码局部就会直接退出,同时抛给调用它的Java代码一个莫名其妙的错误〔可能是一个没有任何意义的Exception〕直接看这个错误是无法分析真正的错误原因的。所以C代码中必须要处理这些异常,要么将其抛出,要么忽略掉。下面是专用的JNI函数,可以对异常进行处理。Throw():丢弃一个现有的异常对象;在固有方法中用于重新丢弃一个异常。ThrowNew():生成一个新的异常对象,并将其丢弃。ExceptionOccurred():判断一个异常是否已被丢弃,但尚未去除。ExceptionDescribe():打印一个异常和堆栈跟踪信息。ExceptionClear():去除一个待决的异常。FatalError():造成一个严重错误,不返回。在所有这些函数中,最不能无视的就是ExceptionOccurred()和ExceptionClear()。大多数JNI函数都能产生异常,而且没有象在Java的try块内的那种语言特性可供利用。所以在每一次JNI函数调用之后,都必须调用ExceptionOccurred(),了解异常是否已被丢弃。假设侦测到一个异常,可选择对其加以控制〔可能时还要重新丢弃它〕。然而,必须确保异常最终被去除。这可以在自己的函数中用ExceptionClear()来实现;假设异常被重新丢弃,也可能在其他某些函数中进行。举个最简单的例子,如果调用Java中的方法后出现异常,忽略。jobjectobjectAttr=(*env)->CallObjectMethod(env,objectDocument,createAttributeMid,stoJstring(env,"ABC"));//dealwithexceptionexc=(*env)->ExceptionOccurred(env);if(exc){(*env)->ExceptionClear(env);doSomething();}Java与C\C++之间通过JNI传递中文字符串Java与C之间通过JNI传递中文字符串

简介

本文提供一个具体的实例,说明Java和C之间通过JNI传递含有中文字符串的参数,解决中文乱码问题〔双字节或多字节乱码〕。本文给出具体的源代码,对源代码进行了解释,C局部提供了C和C++两种方式实现的完整代码。并且所有代码均在实验环境下测试通过。本文读者须对Java和C(或C++)有一定的根底知识。

实验环境

WindowsXP〔或Windows2000〕

j2sdk1.4.2/j2re1.4.2

MicrosoftVisualC++6.0

安装好j2sdk后需要设置环境变量

CLASSPATH=.;C:\j2sdk1.4.2\bin;C:\j2sdk1.4.2\lib\dt.jar;C:\j2sdk1.4.2\lib\tools.jar;C:\j2sdk1.4.2\lib\htmlconverter.jar〔j2sdk安装在c:\j2sdk1.4.2目录下〕

源代码及代码说明

Java代码:

/*

*javactransfer.java

*Bydpwu

*e-mail:dpwu_js@sina

*/

publicclassjavactransfer

{

publicStringhypotenuse(Stringsend_buf,Stringrecv_buf,interrno)

{

returnhypotenuse0(send_buf,recv_buf,errno);

}

privatenativeString

hypotenuse0(Stringsend_buf,Stringrecv_buf,interrno);

static

{

System.loadLibrary("javactransfer");//调用dll

}

publicstaticvoidmain(String[]args)

{

javactransferobj=newjavactransfer();

System.out.println("");

System.out.println("");

System.out.println("

begin!");

System.out.println("");

System.out.println("");

Stringjavasend="发送中文chinese!";

System.out.println("javasend:"+"["+javasend+"]");

Stringjavarecv=obj.hypotenuse("teststr",javasend,1);

//javasend传含有中文字符串给C

//javarecv接受C含有中的文字符串

System.out.println("javarecv:"+"["+javarecv+"]");

System.out.println("");

System.out.println("");

System.out.println("

end!");

}

}

C实现代码为:

/*

*javactransfer.c

*Bydpwu

*e-mail:dpwu_js@sina

*/

#include

#include"javactransfer.h"//通过javah–jnijavactransfer生成

#include

#include"stdlib.h"

#include"string.h"

char*jstringToWindows(JNIEnv*env,jstringjstr);

jstringWindowsTojstring(JNIEnv*env,char*str);

JNIEXPORTjstringJNICALL

Java_javactransfer_hypotenuse0(JNIEnv*env,jobjectobj,jstringsend_buf,jstringrecv_buf,jinterrno)

{

char*Buf_Return;

Buf_Return

=

(char*)malloc(1024);

constchar*recvtest=jstringToWindows(env,recv_buf);

/*上句recvtest接收Java传过来的中文字符串正确,如果改为下句,那么出现乱码:

constchar*recvtest=(*env)->GetStringUTFChars(env,recv_buf,0);

*/

printf("c

recv

:[%s]\n",recvtest);

sprintf(Buf_Return,"接收中文chinese!");

printf("\n\n\ncsend

:[%s]\n",Buf_Return);

recv_buf=WindowsTojstring(env,Buf_Return);

/*上句recv_buf传windows本地C中含有中文字符串给Java正确;如果改为下句出现乱码:

recv_buf=(*env)->NewStringUTF(env,Buf_Return);

*/

returnrecv_buf;

}

char*jstringToWindows(JNIEnv

*env,jstringjstr)

{

intlength=(*env)->GetStringLength(env,jstr);

constjchar*jcstr=(*env)->GetStringChars(env,jstr,0);

char*rtn=(char*)malloc(length*2+1);

intsize=0;

size=WideCharToMultiByte(CP_ACP,0,(LPCWSTR)jcstr,length,rtn,(length*2+1),NULL,NULL);

if(size<=0)

returnNULL;

(*env)->ReleaseStringChars(env,jstr,jcstr);

rtn[size]=0;

returnrtn;

}

jstringWindowsTojstring(JNIEnv*env,char*str)

{

jstringrtn=0;

intslen=strlen(str);

unsignedshort*buffer=0;

if(slen==0)

rtn=(*env)->NewStringUTF(env,str);

else

{

intlength=MultiByteToWideChar(CP_ACP,0,(LPCSTR)str,slen,NULL,0);

buffer=malloc(length*2+1);

if(MultiByteToWideChar(CP_ACP,0,(LPCSTR)str,slen,(LPWSTR)buffer,length)>0)

rtn=(*env)->NewString(env,(jchar*)buffer,length);

}

if(buffer)

free(buffer);

returnrtn;

}

假设javactransfer.java,javactransfer.c均放在d:\javac目录下,

然后执行

(此处图片不能上传〕

javacjavactransfer.java:编译java源代码;

javah–jnijavactransfer:生成头文件;

cl-Ic:\j2sdk1.4.2\include-Ic:\j2sdk1.4.2\include\win32

-LDjavactransfer.c-Fejavactransfer.dll

通过MicrosoftVisualC++将C源代码生成dll文件,供java调用。

执行结果如下:

〔此处图片不能上传〕

执行键入:javajavactranfer

C++实现代码为:

/*

*javactransfer.cpp

*Bydpwu

*e-mail:dpwu_js@sina

*/

#include

#include"javactransfer.h"//通过javah–jnijavactransfer生成

#include

#include"stdlib.h"

#include"string.h"

char*jstringToWindows(JNIEnv*env,jstringjstr);

jstringWindowsTojstring(JNIEnv*env,char*str);

JNIEXPORTjstringJNICALL

Java_javactransfer_hypotenuse0(JNIEnv*env,jobjectobj,jstringsend_buf,jstringrecv_buf,jint_tperrno)

{

char*Buf_Return;

Buf_Return

=

(char*)malloc(1024);

constchar*ctest=jstringToWindows(env,recv_buf);

/*

如果把上句改为下句,C接收Java字符串时将出现乱码:

constchar*ctest=(env)->GetStringUTFChars(

recv_buf,0);

*/

printf("c

recv

:[%s]\n",ctest);

sprintf(Buf_Return,"接收中文chinese!");

printf("\n\n\ncsend

:[%s]\n",Buf_Return);

recv_buf=WindowsTojstring(env,Buf_Return);

/*上句执行正确,如果把上句换成下句,Java在接收C字符串时出现乱码

recv_buf=(env)->NewStringUTF(Buf_Return);

*/

returnrecv_buf;

}

char*jstringToWindows(JNIEnv

*env,jstringjstr)

{

intlength=(env)->GetStringLength(jstr);

constjchar*jcstr=(env)->GetStringChars(jstr,0);

char*rtn=(char*)malloc(length*2+1);

intsize=0;

size=WideCharToMultiByte(CP_ACP,0,(LPCWSTR)jcstr,length,rtn,(length*2+1),NULL,NULL);

if(size<=0)

returnNULL;

(env)->ReleaseStringChars(jstr,jcstr);

rtn[size]=0;

returnrtn;

}

jstringWindowsTojstring(JNIEnv*env,char*str)

{

jstringrtn=0;

intslen=strlen(str);

unsignedshort*buffer=0;

if(slen==0)

rtn=(env)->NewStringUTF(str);

else

{

intlength=MultiByteToWideChar(CP_ACP,0,(LPCSTR)str,slen,NULL,0);

buffer=(unsignedshort*)malloc(length*2+1);

if(MultiByteToWideChar(CP_ACP,0,(LPCSTR)str,slen,(LPWSTR)buffer,length)>0)

rtn=(env)->NewString(

(jchar*)buffer,length);

}

if(buffer)

free(buffer);

returnrtn;

}

当为C++实现时,只需要在编译时改为:

cl-Ic:\j2sdk1.4.2\include-Ic:\j2sdk1.4.2\include\win32-LDjavactransfer.cpp-Fejavactransfer.dll

后话

本文所述内容可以应用于复杂系统的改造,现有大型生产系统〔例如:金融等〕很多都是unix+C+tuxedo基于共享内存实现,而这些系统为了满足平安和响应时间要求不可能在短期内完全丢弃,本文中提供的方法,可以和tuxedo客户端通过VC++封装成各种dll,这些dll可以在tuxedo本身很多优良特性的根底上进行基于Java的开发,以用来解决新的基于WEB应用的需求。如有错误之处,请与笔者联系:dpwu_js@sina。

参考资料:DavidWendt《NLSstringsandJNI》

WebSphereprogrammer,IBM

下面是我在这个根底上完成的C++CallJava的反向例程:

C++传递中文字符给java

/*

*Chinese.java

*ByRookie.Zhang

*e-mail:rookieport@msn

*/

publicclassChinese

{

privatestaticvoidsayHello(Stringname){

System.out.println("Hello:"+name);

}

publicstaticvoidmain(String[]args){

for(intidx=0;idx<args.length;idx++){

sayHello(args[idx]);

}

}

};

/*

*jnidemo.cpp

*ByRookie.Zhang

*e-mail:rookieport@msn

*

*/

/*

*环境:jdk1.5.0_05+vc7+winxp

*Usage:

1。编译

cl-I%JAVA_HOME%\include-I%JAVA_HOME%\include\win32jnidemo.cpp/link%JAVA_HOME%\lib\jvm.lib

2。设置JVM路径

setpath=%JAVA_HOME%\jre\bin\client;%PATH%

3。运行

jnidemo

4.输出

Hello:Rookie.张[中文]

*/

#include

#include

#include

jstringWindowsTojstring(JNIEnv*env,char*str);

intmain()

{

JavaVMOptionoptions[1];

JavaVMInitArgsvm_args;

JavaVM*jvm;

JNIEnv*env;

longstatus;

jclassclass_Chinese;

jclassclass_String;

jobjectArrayargs;

jmethodIDid_main;

options[0].optionString="-Djava.class.path=.";

memset(&vm_args,0,sizeof(vm_args));

vm_args.version=JNI_VERSION_1_2;

vm_args.nOptions=1;

vm_args.options=options;

status=JNI_CreateJavaVM(&jvm,(void**)&env,&vm_args);

if(status==JNI_ERR)

{

printf("ErrorcreatingVM\n");

return1;

}

class_Chinese=(*env).FindClass("Chinese");

id_main=(*env).GetStaticMethodID(class_Chinese,"main","([Ljava/lang/String;)V");

class_String=(*env).FindClass("java/lang/String");

//Callmain(String[]{""Rookie.张[中文]"})

args=(*env).NewObjectArray(1,class_String,WindowsTojstring(env,"Rookie.张[中文]"));

(*env).CallStaticVoidMethod(class_Chinese,id_main,args);

(*jvm).DestroyJavaVM();

return0;

}

//修改自dpwu's代码

jstringWindowsTojstring(JNIEnv*env,char*str)

{

jstringrtn=0;

intslen=strlen(str);

unsignedshort*buffer=0;

if(slen==0)

rtn=(*env).NewStringUTF(str);

else

{

intlength=MultiByteToWideChar(CP_ACP,0,(LPCSTR)str,slen,NULL,0);

buffer=(unsignedshort*)malloc(length*2+1);

if(MultiByteToWideChar(CP_ACP,0,(LPCSTR)str,slen,(LPWSTR)buffer,length)>0)

rtn=(*env).NewString((jchar*)buffer,length);

}

if(buffer)

free(buffer);

returnrtn;

}Java:JNI完全手册最近在公司里做了一个的工程,需要JAVA程序在发送短信的时候和第三方的短信效劳器连接。短信接口是用C++写的。琢磨了三天,大致搞懂了JNI的主体局部。先将心得整理,希望各位朋友少走弯路。首先引用一篇文章,介绍一个简单的JNI的调用的过程。

JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。

JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的〔在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式〕。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。简单介绍及应用如下:

一、JAVA中所需要做的工作

在JAVA程序中,首先需要在类中声明所调用的库名称,如下:static{

System.loadLibrary(“goodluck〞);

}

在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。

还需对将要调用的方法做本地声明,关键字为native。且只需要声明,而不需要具体实现。如下:publicnativestaticvoidset(inti);

publicnativestaticintget();

然后编译该JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就会生成C/C++的头文件。

例如程序testdll.java,内容为:publicclasstestdll

{

static

{

System.loadLibrary("goodluck");

}

publicnativestaticintget();

publicnativestaticvoidset(inti);

publicstaticvoidmain(String[]args)

{

testdlltest=newtestdll();

test.set(10);

System.out.println(test.get());

}

}用javactestdll.java编译它,会生成testdll.class。

再用javahtestdll,那么会在当前目录下生成testdll.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。二、C/C++中所需要做的工作对于已生成的.h头文件,C/C++所需要做的,就是把它的各个方法具体的实现。然后编译连接成库文件即可。再把库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/C++所实现的功能了。

接上例子。我们先看一下testdll.h文件的内容:/*DONOTEDITTHISFILE-itismachinegenerated*/

#include

/*Headerforclasstestdll*/

#ifndef_Included_testdll

#define_Included_testdll

#ifdef__cplusplus

extern"C"{

#endif

/*

*Class:testdll

*Method:get

*Signature:()I

*/

JNIEXPORTjintJNICALLJava_testdll_get(JNIEnv*,jclass);

/*

*Class:testdll

*Method:set

*Signature:(I)V

*/

JNIEXPORTvoidJNICALLJava_testdll_set(JNIEnv*,jclass,jint);

#ifdef__cplusplus

}

#endif

#endif在具体实现的时候,我们只关心两个函数原型

JNIEXPORTjintJNICALLJava_testdll_get(JNIEnv*,jclass);和

JNIEXPORTvoidJNICALLJava_testdll_set(JNIEnv*,jclass,jint);这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint是以JNI为中介使JAVA的int类型与本地的int沟通的一种类型,我们可以视而不见,就当做int使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。好,下面我们用testdll.cpp文件具体实现这两个函数:#include"testdll.h"

inti=0;

JNIEXPORTjintJNICALLJava_testdll_get(JNIEnv*,jclass)

{

returni;

}

JNIEXPORTvoidJNICALLJava_testdll_set(JNIEnv*,jclass,jintj)

{

i=j;

}编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是goodluck.dll。把goodluck.dll拷贝到testdll.class的目录下,javatestdll运行它,就可以观察到结果了。我的工程比拟复杂,需要调用动态链接库,这样在JNI传送参数到C程序时,需要对参数进行处理转换。才可以被C程序识别。

大体程序如下:

publicclassSendSMS{

static

{

System.out.println(System.getProperty("java.library.path"));

System.loadLibrary("sms");

}

publicnativestaticintSmsInit();

publicnativestaticintSmsSend(byte[]mobileNo,byte[]smContent);

}

在这里要注意的是,path里一定要包含类库的路径,否那么在程序运行时会抛出异常:

java.lang.UnsatisfiedLinkError:nosmsinjava.library.path

atjava.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)

atjava.lang.Runtime.loadLibrary0(Runtime.java:788)

atjava.lang.System.loadLibrary(System.java:834)

atcom.mobilesoft.sms.mobilesoftinfo.SendSMS.(SendSMS.java:14)

atcom.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)

Exceptioninthread"main"

指引的路径应该到.dll文件的上一级,如果指到.dll,那么会报:

java.lang.UnsatisfiedLinkError:C:\sms.dll:Can'tfinddependentlibraries

atjava.lang.ClassLoader$NativeLibrary.load(NativeMethod)

atjava.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)

atjava.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)

atjava.lang.Runtime.loadLibrary0(Runtime.java:788)

atjava.lang.System.loadLibrary(System.java:834)

atcom.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)

Exceptioninthread"main"

通过编译,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h头文件。〔建议使用Jbuilder进行编译,操作比拟简单!〕这个头文件就是Java和C之间的纽带。要特别注意的是方法中传递的参数jbyteArray,这在接下来的过程中会重点介绍。/*DONOTEDITTHISFILE-itismachinegenerated*/

#include

/*Headerforclasscom_mobilesoft_sms_mobilesoftinfo_SendSMS*/

#ifndef_Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS

#define_Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS

#ifdef__cplusplus

extern"C"{

#endif

/*

*Class:com_mobilesoft_sms_mobilesoftinfo_SendSMS

*Method:SmsInit

*Signature:()I

*/

JNIEXPORTjintJNICALLJava_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit

(JNIEnv*,jclass);

/*

*Class:com_mobilesoft_sms_mobilesoftinfo_SendSMS

*Method:SmsSend

*Signature:([B[B)I

*/

JNIEXPORTjintJNICALLJava_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend

(JNIEnv*,jclass,jbyteArray,jbyteArray);

#ifdef__cplusplus

}

#endif

#endif

对于我要调用的C程序的动态链接库,C程序也要提供一个头文件,sms.h。这个文件将要调用的方法罗列了出来。/*

*SMSAPI

*Author:yippit

*Date:2004.6.8

*/

#ifndefMCS_SMS_H

#defineMCS_SMS_H

#defineDLLEXPORT__declspec(dllexport)

/*smsstorage*/

#defineSMS_SIM0

#defineSMS_MT1

/*smsstates*/

#defineSMS_UNREAD0

#defineSMS_READ1

/*smstype*/

#defineSMS_NOPARSE-1

#defineSMS_NORMAL0

#defineSMS_FLASH1

#defineSMS_MMSNOTI2

typedefstructtagSmsEntry{

intindex;/*index,startfrom1*/

intstatus;/*read,unread*/

inttype;/*-1-can'tparser0-normal,1-flash,2-mms*/

intstorage;/*SMS_SIM,SMS_MT*/

chardate[24];

charnumber[32];

chartext[144];

}SmsEntry;

DLLEXPORTintSmsInit(void);

DLLEXPORTintSmsSend(char*phonenum,char*content);

DLLEXPORTintSmsSetSCA(char*sca);

DLLEXPORTintSmsGetSCA(char*sca);

DLLEXPORTintSmsSetInd(intind);

DLLEXPORTintSmsGetInd(void);

DLLEXPORTintSmsGetInfo(intstorage,int*max,int*used);

DLLEXPORTintSmsSaveFlash(intflag);

DLLEXPORTintSmsRead(SmsEntry*entry,intstorage,intindex);

DLLEXPORTintSmsDelete(intstorage,intindex);

DLLEXPORTintSmsModifyStatus(intstorage,intindex);/*unread->read*/

#endif在有了这两个头文件之后,就可以进行C程序的编写了。也就是实现对JNI调用的两个方法。在网上的资料中,由于调用的方法实现的都比拟简单,〔大多是打印字符串等〕所以避开了JNI中最麻烦的局部,也是最关键的局部,参数的传递。由于Java和C的编码是不同的,所以传递的参数是要进行再处理,否那么C程序是会对参数在编译过程中提出警告,例如;warningC4024:'SmsSend':differenttypesforformalandactualparameter2等。Sms.c的程序如下:

#include"sms.h"

#include"com_mobilesoft_sms_mobilesoftinfo_SendSMS.h"

JNIEXPORTjintJNICALLJava_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv*env,jclassjobject)

{

returnSmsInit();

}

JNIEXPORTjintJNICALLJava_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv*env,jclassjobject,jbyteArraymobileno,jbyteArraysmscontent)

{

char*pSmscontent;

//jsizetheArrayLengthJ=(*env)->GetArrayLength(env,mobileno);

jbyte*arrayBody=(*env)->GetByteArrayElements(env,mobileno,0);

char*pMobileNo=(char*)arrayBody;

printf("[%s]\n",pMobileNo);

//jsizesize=(*env)->GetArrayLength(env,smscontent);

arrayBody=(*env)->GetByteArrayElements(env,smscontent,0);

pSmscontent=(char*)arrayBody;

printf("<%s>\n",pSmscontent);

returnSmsSend(pMobileNo,pSmscontent);

}

对于C或C++,在程序上是会有稍微的不同,这可以由读者对其进行适当的修改。这里要注意的是GetArrayLength,GetByteArrayElements等这些JNI中已经包含的方法,这些方法是专门对转换参数类型而提供的。具体的方法有很多,在下一篇中会做专门的介绍。

在完成了上述的文件后,可以对sms.c进行编译,生成.dll文件〔建议在release中编译,这样动态链接库的容积会比拟小!〕

完成.dll文件的编译后,就可以在Java中调用C程序中的方法了。例如文件test.javapublicclasstest{

publictest(){

}

publicstaticvoidmain(String[]args){

byte[]mobileno={

0x31,0x33,0x36,0x36,0x31,0x36,0x33,0x30,0x36,0x36,0x37,0x00};

Stringsmscontentemp="早上好";

byte[]temp={0};

try{

byte[]smscontentdb=smscontentemp.getBytes("gbk");

byte[]smscontent=newbyte[smscontentdb.length+temp.length];

System.arraycopy(smscontentdb,0,smscontent,0,smscontentdb.length);

System.arraycopy(temp,0,smscontent,smscontentdb.length,temp.length);

SendSMSsendSMS=newSendSMS();

sendSMS.SmsInit();

if(sendSMS.SmsSend(mobileno,smscontent)>=0){

System.out.println("chenggong!");

}

else{

System.out.println("shibai!");

}

}catch(Exceptionex){}

}

}在这个文件中要注意的有一点,就是在传递字节数组到C程序中时,最后的结尾一定要以0结束。这是一个偷懒的做法,不过是个有效的做法。因为大多数情况下,接口是由第三方提供的。所以我们一般是不知道在C的方法里,具体是怎么处理参数的。而C又是要求数组是有长度。所以,在Java中,如果你不想写程序传数组的长度,那么在数组中以0结尾就是最方便的方法了。当然,如果有更好的方法也希望大家提出。

到这里,一个完整的Java通过JNI调用动态链接库的程序就完成了。实际上也不是很复杂。只要多注意一下细节,是很容易得出来的。当然可以,以下是测试:你可试一下:

//Test.java

public

class

Test

{

static

int

x;

public

static

native

void

test();

static

{

System.loadLibrary(

"test"

);

}

public

static

void

main(

String[]

args

)

{

x=100;

System.out.println(x);

test();

System.out.println(x);

}

}

//test.cpp

#include

"Test.h"

JNIEXPORT

void

JNICALL

Java_Test_test

(JNIEnv

*env,

jclass

cls)

{

jfieldID

field

=

env->GetStaticFieldID(cls,"x","I");

//得到x的id

jint

x=(jint)env->

GetStaticIntField(cls,

fiel

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论