由于工作关系,需要利用JNI在C
++与Java程序之间进行方法调用和数据传递,但以前总是在英文环境下工作,对中文(其他语言编码同理)问题反倒没有太关注,最近抽了点时间研究了一下,将自己的体会整理如下,供大家讨论或参考。
在进一步讨论之前,有几点基础知识需要说明:
1、在Java内部,所有的字符串编码采用的是Unicode即UCS - 2。Unicode是用两个字节表示每个字符的字符编码方案。Unicode有一个特性:它包括了世界上所有的字符字形。所以,各个地区的语言都可以建立与Unicode的映射关系,而Java正是利用了这一点以达到异种语言之间的转换。
2、UTF - 8是另一种不同于UCS - 2 /UCS - 4的编码方案,其中UTF代表UCS Transformation Format,它采用变长的方式进行编码,编码长度可以是 1 ~ 3(据说理论上最长可以到 6,不懂)。
由于UCS - 2 /UCS - 4编码定长的原因,编码产生的字符串会包含一些特殊的字符,如 / 0(即 0x0,所有 0 ~ 256的字符Unicode编码的第一个字节),这在有些情况下(如传输或解析时)会给我们带来一些麻烦,而且对于一般的英文字母浪费了太多的空间,此外,据说UTF - 8还有Unicode所没有的纠错能力(不懂!),因此,Unicode往往只是被用作一种中间码,用于逻辑表示。关于Unicode /UTF - 8的更多信息,见参考 1。
Java中文乱码问题在很多情况下都可能发生:不同应用间,不同平台间等等,但以上问题已有大量优秀的文章讨论过,这里不作深入探讨,详见参考 2、 3、 4、 5。下面简要总结一下:
1、当我们使用默认编码方式保存源文件时,文件内容实际上是按照我们的系统设定进行编码保存的,这个设定值即file .encoding可以通过下面的程序获得:
public class Encoding {
public static void main (String [] args ) {
System .out .println (System .getProperty ( "file.encoding" ));
}
}
javac在不指定encoding参数时,如果区域设定不正确,则可能造成编 /解码错误,这个问题在编译一个从别的环境传过来的文件时可能发生。
2、虽然在Java内部(即运行期间,Runtime)字符串是以Unicode形式存在的,但在 class文件中信息是以UTF - 8形式存储的(Unicode仅被用作逻辑表示中间码)。
3、对于Web应用,以Tomcat为例,JSP /Servlet引擎提供的JSP转换工具(jspc)搜索JSP文件中用 <%@ page contentType = "text/html; charset=<Jsp-charset>" %>指定的charset。如果在JSP文件中未指定 <Jsp -charset >,则取系统默认的file .encoding(这个值在中文平台上是GBK),可通过控制面板的Regional Options进行修改;jspc用相当于“javac –encoding <Jsp -charset >”的命令解释JSP文件中出现的所有字符,包括中文字符和ASCII字符,然后把这些字符转换成Unicode字符,再转化成UTF - 8格式,存为JAVA文件。
我曾经偶然将jsp文件存成UTF - 8,而在文件内部使用的charset却是GB2312,结果运行时总是无法正常显示中文,后来转存为默认编码方式才正常。只要文件存储格式与JSP开头的charset设置一致,就都可以正常显示(不过将文件保存成UTF - 16的情况下我还没有试验成功)。
4、在XML文件中,encoding表示的是文件本身的编码方式,如果这个参数设定与文件本身实际的编码方式不一致的话,则可能解码失败,所以应该总是将encoding设置成与文件编码方式一致的值;而JSP /HTML的charset则表示按照何种字符集来解码从文件中读取出来的字符串(在理解中文问题时应该把字符串理解成一个二进制或 16进制的串,按照不同的charset可能映射成不同的字符)。
我曾经在网上就encoding的具体含义跟别人讨论过:如果encoding指的是文件本身的编码方式,那么读取该文件的应用程序在不知道encoding设置的情况下如何正确解读该文件呢?
根据讨论及个人理解,处理程序(如jspc)总是按ISO8859 - 1来读取输入文件,然后检查文件开始的几个字节(即Byte Order Mark,BOM,具体如何判断,可以参考Tomcat源码$SOURCE_DIR /jasper /jasper2 /src /share /org /apache /jasper /xmlparser /XMLEncodingDetector .java的getEncodingName方法,在JSP Specification的Page Character Encoding一节也有详细论述)以探测文件是以何种格式保存的,当解析到encoding选项时,若encoding设置与文件实际保存格式不一致,会尝试进行转换,但这种转换可能在文件实际以ISO8859 - 1 /UTF - 8等单字节编码而encoding被设置成Unicode、UTF - 16等双字节编码时发生错误。
下面重点讨论JNI中在C ++程序与Java程序间进行数据传递时需要注意的问题。
在JNI中jstring采用的是UCS - 2编码,与Java中String的编码方式一致。但是在C ++中,字符串是用 char( 8位)或者 wchar_t( 16位,Unicode编码与jchar一致,但并非所有开发平台上都是Unicode编码,详见参考 6),下面的程序证明了这一点(编译环境:VC6):
#include <iostream>
using namespace std ;
int main ()
{
locale loc ( "Chinese-simplified" );
//locale loc( "chs" );
//locale loc( "ZHI" );
//locale loc( ".936" );
wcout .imbue ( loc );
wcout << L"中文" << endl ; //若没有L,会出问题
wchar_t wch [] = { 0x4E2D , 0x6587 , 0x0 }; //"中文"二字的Unicode编码
wcout << wch << endl ;
return 0 ;
}
JNI提供了几个方法来实现jstring与 char / wchar_t之间的转换。
jsize GetStringLength (jstring str )
const jchar *GetStringChars (jstring str , jboolean *isCopy )
void ReleaseStringChars (jstring str , const jchar *chars )
此外,为了便于以UTF - 8方式进行传输、存储,JNI还提供了几个操作UTF格式的方法:
jsize GetStringUTFLength (jstring str )
const char * GetStringUTFChars (jstring str , jboolean *isCopy )
void ReleaseStringUTFChars (jstring str , const char * chars )
GetStringChars返回的是Unicode格式的编码串,而GetStringUTFChars返回的是UTF - 8格式的编码串。
要创建一个jstring,可以用如下方式:
jstring NewJString ( JNIEnv * env , LPCTSTR str )
{
if (!env || !str )
return 0 ;
int slen = strlen (str );
jchar * buffer = new jchar [slen ];
int len = MultiByteToWideChar (CP_ACP , 0 , str , strlen (str ), buffer , slen );
if (len > 0 && len < slen )
buffer [len ] = 0 ;
jstring js = env ->NewString (buffer , len );
delete [] buffer ;
return js ;
}
而要将一个jstring对象转为一个 char字符串数组,可以:
int JStringToChar ( JNIEnv * env , jstring str , LPTSTR desc , int desc_len )
{
int len = 0 ;
if (desc == NULL || str == NULL )
return - 1 ;
// Check buffer size
if (env ->GetStringLength (str ) * 2 + 1 > desc_len )
{
return - 2 ;
}
memset (desc , 0 , desc_len );
const wchar_t * w_buffer = env ->GetStringChars (str , 0 );
len = WideCharToMultiByte (CP_ACP , 0 , w_buffer , wcslen (w_buffer ) + 1 , desc , desc_len , NULL , NULL );
env ->ReleaseStringChars (str , w_buffer );
if (len > 0 && len < desc_len )
desc [len ] = 0 ;
return strlen (desc );
}
当然,按照上面的分析,你也可以直接将GetStringChars的返回结果作为 wchar_t串来进行操作。或者,如果你愿意,你也可以将GetStringUTFChars的结果通过MultiByteToWideChar转换为UCS2编码串,再通过WideCharToMultiByte转换为多字节串。
const char * pstr = env ->GetStringUTFChars (str , false );
int nLen = MultiByteToWideChar ( CP_UTF8 , 0 , pstr , - 1 , NULL , NULL ); //得到UTF-8编码的字符串长度
LPWSTR lpwsz = new WCHAR [nLen ];
MultiByteToWideChar ( CP_UTF8 , 0 , pstr , - 1 , lpwsz , nLen ); //转换的结果是UCS2格式的编码串
int nLen1 = WideCharToMultiByte ( CP_ACP , 0 , lpwsz , nLen , NULL , NULL , NULL , NULL );
LPSTR lpsz = new CHAR [nLen1 ];
WideCharToMultiByte ( CP_ACP , 0 , lpwsz , nLen , lpsz , nLen1 , NULL , NULL ); //将UCS2格式的编码串转换为多字节
cout << "Out:" << lpsz << endl ;
delete [] lpwsz ; delete [] lpsz ;
当然,我相信很少有人想要或者需要这么做。
这里需要注意一点,GetStringChars的返回值是jchar,而GetStringUTFChars的返回值是 const char *.
除了上面的办法外,当需要经常在jstring和 char *之间进行转换时我们还有一个选择,那就是下面的这个类。这个类本来是一个叫Roger S . Reynolds的老外提供的,想法非常棒,但用起来却不太灵光,因为作者将考虑的重心放在UTF格式串上,但在实际操作中,我们往往使用的却是ACP(ANSI code page)串。下面是原作者的程序:
class UTFString {
private :
UTFString (); // Default ctor - disallowed
public :
// Create a new instance from the specified jstring
UTFString (JNIEnv * env , const jstring & str ) :
mEnv (env ),
mJstr (str ),
mUtfChars (( char * )mEnv ->GetStringUTFChars (mJstr , 0 )),
mString (mUtfChars ) { }
// Create a new instance from the specified string
UTFString (JNIEnv * env , const string & str ) :
mEnv (env ),
mString (str ),
mJstr (env ->NewStringUTF (str .c_str ())),
mUtfChars (( char * )mEnv ->GetStringUTFChars (mJstr , 0 )) { }
// Create a new instance as a copy of the specified UTFString
UTFString ( const UTFString & rhs ) :
mEnv (rhs .mEnv ),
mJstr (mEnv ->NewStringUTF (rhs .mUtfChars )),
mUtfChars (( char * )mEnv ->GetStringUTFChars (mJstr , 0 )),
mString (mUtfChars ) { }
// Delete the instance and release allocated storage
~UTFString () { mEnv ->ReleaseStringUTFChars (mJstr , mUtfChars ); }
// assign a new value to this instance from the given string
UTFString & operator =( const string & rhs ) {
mEnv ->ReleaseStringUTFChars (mJstr , mUtfChars );
mJstr = mEnv ->NewStringUTF (rhs .c_str ());
mUtfChars = ( char * )mEnv ->GetStringUTFChars (mJstr , 0 );
mString = mUtfChars ;
return * this ;
}
// assign a new value to this instance from the given char*
UTFString & operator =( const char * ptr ) {
mEnv ->ReleaseStringUTFChars (mJstr , mUtfChars );
mJstr = mEnv ->NewStringUTF (ptr );
mUtfChars = ( char * )mEnv ->GetStringUTFChars (mJstr , 0 );
mString = mUtfChars ;
return * this ;
}
// Supply operator methods for converting the UTFString to a string
// or char*, making it easy to pass UTFString arguments to functions
// that require string or char* parameters.
string & GetString () { return mString ; }
operator string () { return mString ; }
operator const char * () { return mString .c_str (); }
operator jstring () { return mJstr ; }
private :
JNIEnv * mEnv ; // The enviroment pointer for this native method.
jstring mJstr ; // A copy of the jstring object that this UTFString represents
char * mUtfChars ; // Pointer to the data returned by GetStringUTFChars
string mString ; // string buffer for holding the "value" of this instance
};
我将它改了改:
class JNIString {
private :
JNIString (); // Default ctor - disallowed
public :
// Create a new instance from the specified jstring
JNIString (JNIEnv * env , const jstring & str ) :
mEnv (env ) {
const jchar * w_buffer = env ->GetStringChars (str , 0 );
mJstr = env ->NewString (w_buffer ,
wcslen (w_buffer )); // Deep Copy, in usual case we only need Shallow Copy as we just need this class to provide some convenience for handling jstring
mChars = new char [wcslen (w_buffer ) * 2 + 1 ];
WideCharToMultiByte (CP_ACP , 0 , w_buffer , wcslen (w_buffer ) + 1 , mChars , wcslen (w_buffer ) * 2 + 1 ,
NULL , NULL );
env ->ReleaseStringChars (str , w_buffer );
mString = mChars ;
}
// Create a new instance from the specified string
JNIString (JNIEnv * env , const string & str ) :
mEnv (env ) {
int slen = str .length ();
jchar * buffer = new jchar [slen ];
int len = MultiByteToWideChar (CP_ACP , 0 , str .c_str (), str .length (), buffer , slen );
if (len > 0 && len < slen )
buffer [len ] = 0 ;
mJstr = env ->NewString (buffer , len );
delete [] buffer ;
mChars = new char [str .length () + 1 ];
strcpy (mChars , str .c_str ());
mString .empty ();
mString = str .c_str ();
}
// Create a new instance as a copy of the specified JNIString
JNIString ( const JNIString & rhs ) :
mEnv (rhs .mEnv ) {
const jchar * wstr = mEnv ->GetStringChars (rhs .mJstr , 0 );
mJstr = mEnv ->NewString (wstr , wcslen (wstr ));
mEnv ->ReleaseStringChars (rhs .mJstr , wstr );
mChars = new char [strlen (rhs .mChars ) + 1 ];
strcpy (mChars , rhs .mChars );
mString = rhs .mString .c_str ();
}
// Delete the instance and release allocated storage
~JNIString () { delete [] mChars ; }
// assign a new value to this instance from the given string
JNIString & operator =( const string & rhs ) {
delete [] mChars ;
int slen = rhs .length ();
jchar * buffer = new jchar [slen ];
int len = MultiByteToWideChar (CP_ACP , 0 , rhs .c_str (), rhs .length (), buffer , slen );
if (len > 0 && len < slen )
buffer [len ] = 0 ;
mJstr = mEnv ->NewString (buffer , len );
delete [] buffer ;
mChars = new char [rhs .length () + 1 ];
strcpy (mChars , rhs .c_str ());
mString = rhs .c_str ();
return * this ;
}
// Supply operator methods for converting the JNIString to a string
// or char*, making it easy to pass JNIString arguments to functions
// that require string or char* parameters.
string & GetString () { return mString ; }
operator string () { return mString ; }
operator const char * () { return mString .c_str (); }
operator jstring () { return mJstr ; }
private :
JNIEnv * mEnv ; // The enviroment pointer for this native method.
jstring mJstr ; // A copy of the jstring object that this JNIString represents
char * mChars ; // Pointer to a ANSI code page char array
string mString ; // string buffer for holding the "value" of this instance (ANSI code page)
};
后者除了将面向UTF编码改成了面向ANSI编码外,还去掉了 operator =( const char * ptr )的定义,因为 operator =( const string & rhs )可以在需要的时候替代前者而无需任何额外编码。(因为按照C ++规范, const reference可以自动转换,详见本人另一文章《关于 const reference的几点说明》http : //blog.vckbase.com/billdavid/archive/2004/11/11/1453.html)
如果你愿意,给JNIString再加个JNIString (JNIEnv * env , const wstring & str )和一个 operator =( const wstring & rhs )操作符重载就比较完美了, :),很简单,留给用得到的朋友自己加吧。
下面是一个使用该类的例子(真正跟用于演示的code很少,大部分都是些routine code, :)):
#include <iostream>
#include <string>
#include <assert.h>
#include <jni.h>
using namespace std ;
int main () {
int res ;
JavaVM * jvm ;
JNIEnv * env ;
JavaVMInitArgs vm_args ;
JavaVMOption options [ 3 ];
options [ 0 ].optionString = "-Djava.compiler=NONE" ;
options [ 1 ].optionString = "-Djava.class.path=.;.." ; // .. is specially for this project
options [ 2 ].optionString = "-verbose:jni" ;
vm_args .version = JNI_VERSION_1_4 ;
vm_args .nOptions = 3 ;
vm_args .options = options ;
vm_args .ignoreUnrecognized = JNI_TRUE ;
res = JNI_CreateJavaVM (& jvm , ( void * * )& env , & vm_args );
if (res < 0 ) {
fprintf (stderr , "Can''''t create Java VM/n" );
return 1 ;
}
jclass cls = env ->FindClass ( "jni/test/Demo" );
assert ( 0 != cls );
jmethodID mid = env ->GetMethodID (cls , "<init>" , "(Ljava/lang/String;)V" );
assert ( 0 != mid );
wchar_t * p = L"中国" ;
jobject obj = env ->NewObject (cls , mid , env ->NewString ( reinterpret_cast <jchar *> (p ), wcslen (p )));
assert ( 0 != obj );
mid = env ->GetMethodID (cls , "getMessage" , "()Ljava/lang/String;" );
assert ( 0 != mid );
jstring str = (jstring )env ->CallObjectMethod (obj , mid );
// use JNIString for easier handling.
JNIString jnistr (env , str );
cout << "JNIString:" << jnistr .GetString () << endl ;
jnistr = "中文" ;
cout << jnistr .GetString () << endl ;
jvm ->DestroyJavaVM ();
fprintf (stdout , "Java VM destory./n" );
return 0 ;
}
参考:
1、UTF - 8 and Unicode FAQ for Unix /Linuxs,http : //www.cl.cam.ac.uk/~mgk25/unicode.html,其中文翻译见http://www.linuxforum.net/books/UTF-8-Unicode.html
2、深入剖析Java编程中的中文问题及建议最优解决方法,http : //blog.youkuaiyun.com/abnerchai/archive/2004/04/28/18576.aspx
3、关于Java中文问题的几条分析原则,http : //www-900.ibm.com/developerWorks/cn/java/l-javachinese/index.shtml
4、Java 编程技术中汉字问题的分析及解决,http : //www-900.ibm.com/developerWorks/cn/java/java_chinese/index.shtml
5、深入剖析JSP和Servlet对中文的处理过程,http : //blog.youkuaiyun.com/deuso/archive/2005/12/01/541511.aspx
6、宽字符标量 L"xx"在VC6 .0 / 7.0和GNU g ++中的不同实现,http : //blog.vckbase.com/smileonce/archive/2004/12/09/1972.html
7、XML Encoding,http : //www.w3schools.com/xml/xml_encoding.asp
在进一步讨论之前,有几点基础知识需要说明:
1、在Java内部,所有的字符串编码采用的是Unicode即UCS - 2。Unicode是用两个字节表示每个字符的字符编码方案。Unicode有一个特性:它包括了世界上所有的字符字形。所以,各个地区的语言都可以建立与Unicode的映射关系,而Java正是利用了这一点以达到异种语言之间的转换。
2、UTF - 8是另一种不同于UCS - 2 /UCS - 4的编码方案,其中UTF代表UCS Transformation Format,它采用变长的方式进行编码,编码长度可以是 1 ~ 3(据说理论上最长可以到 6,不懂)。
由于UCS - 2 /UCS - 4编码定长的原因,编码产生的字符串会包含一些特殊的字符,如 / 0(即 0x0,所有 0 ~ 256的字符Unicode编码的第一个字节),这在有些情况下(如传输或解析时)会给我们带来一些麻烦,而且对于一般的英文字母浪费了太多的空间,此外,据说UTF - 8还有Unicode所没有的纠错能力(不懂!),因此,Unicode往往只是被用作一种中间码,用于逻辑表示。关于Unicode /UTF - 8的更多信息,见参考 1。
Java中文乱码问题在很多情况下都可能发生:不同应用间,不同平台间等等,但以上问题已有大量优秀的文章讨论过,这里不作深入探讨,详见参考 2、 3、 4、 5。下面简要总结一下:
1、当我们使用默认编码方式保存源文件时,文件内容实际上是按照我们的系统设定进行编码保存的,这个设定值即file .encoding可以通过下面的程序获得:
public class Encoding {
public static void main (String [] args ) {
System .out .println (System .getProperty ( "file.encoding" ));
}
}
javac在不指定encoding参数时,如果区域设定不正确,则可能造成编 /解码错误,这个问题在编译一个从别的环境传过来的文件时可能发生。
2、虽然在Java内部(即运行期间,Runtime)字符串是以Unicode形式存在的,但在 class文件中信息是以UTF - 8形式存储的(Unicode仅被用作逻辑表示中间码)。
3、对于Web应用,以Tomcat为例,JSP /Servlet引擎提供的JSP转换工具(jspc)搜索JSP文件中用 <%@ page contentType = "text/html; charset=<Jsp-charset>" %>指定的charset。如果在JSP文件中未指定 <Jsp -charset >,则取系统默认的file .encoding(这个值在中文平台上是GBK),可通过控制面板的Regional Options进行修改;jspc用相当于“javac –encoding <Jsp -charset >”的命令解释JSP文件中出现的所有字符,包括中文字符和ASCII字符,然后把这些字符转换成Unicode字符,再转化成UTF - 8格式,存为JAVA文件。
我曾经偶然将jsp文件存成UTF - 8,而在文件内部使用的charset却是GB2312,结果运行时总是无法正常显示中文,后来转存为默认编码方式才正常。只要文件存储格式与JSP开头的charset设置一致,就都可以正常显示(不过将文件保存成UTF - 16的情况下我还没有试验成功)。
4、在XML文件中,encoding表示的是文件本身的编码方式,如果这个参数设定与文件本身实际的编码方式不一致的话,则可能解码失败,所以应该总是将encoding设置成与文件编码方式一致的值;而JSP /HTML的charset则表示按照何种字符集来解码从文件中读取出来的字符串(在理解中文问题时应该把字符串理解成一个二进制或 16进制的串,按照不同的charset可能映射成不同的字符)。
我曾经在网上就encoding的具体含义跟别人讨论过:如果encoding指的是文件本身的编码方式,那么读取该文件的应用程序在不知道encoding设置的情况下如何正确解读该文件呢?
根据讨论及个人理解,处理程序(如jspc)总是按ISO8859 - 1来读取输入文件,然后检查文件开始的几个字节(即Byte Order Mark,BOM,具体如何判断,可以参考Tomcat源码$SOURCE_DIR /jasper /jasper2 /src /share /org /apache /jasper /xmlparser /XMLEncodingDetector .java的getEncodingName方法,在JSP Specification的Page Character Encoding一节也有详细论述)以探测文件是以何种格式保存的,当解析到encoding选项时,若encoding设置与文件实际保存格式不一致,会尝试进行转换,但这种转换可能在文件实际以ISO8859 - 1 /UTF - 8等单字节编码而encoding被设置成Unicode、UTF - 16等双字节编码时发生错误。
下面重点讨论JNI中在C ++程序与Java程序间进行数据传递时需要注意的问题。
在JNI中jstring采用的是UCS - 2编码,与Java中String的编码方式一致。但是在C ++中,字符串是用 char( 8位)或者 wchar_t( 16位,Unicode编码与jchar一致,但并非所有开发平台上都是Unicode编码,详见参考 6),下面的程序证明了这一点(编译环境:VC6):
#include <iostream>
using namespace std ;
int main ()
{
locale loc ( "Chinese-simplified" );
//locale loc( "chs" );
//locale loc( "ZHI" );
//locale loc( ".936" );
wcout .imbue ( loc );
wcout << L"中文" << endl ; //若没有L,会出问题
wchar_t wch [] = { 0x4E2D , 0x6587 , 0x0 }; //"中文"二字的Unicode编码
wcout << wch << endl ;
return 0 ;
}
JNI提供了几个方法来实现jstring与 char / wchar_t之间的转换。
jsize GetStringLength (jstring str )
const jchar *GetStringChars (jstring str , jboolean *isCopy )
void ReleaseStringChars (jstring str , const jchar *chars )
此外,为了便于以UTF - 8方式进行传输、存储,JNI还提供了几个操作UTF格式的方法:
jsize GetStringUTFLength (jstring str )
const char * GetStringUTFChars (jstring str , jboolean *isCopy )
void ReleaseStringUTFChars (jstring str , const char * chars )
GetStringChars返回的是Unicode格式的编码串,而GetStringUTFChars返回的是UTF - 8格式的编码串。
要创建一个jstring,可以用如下方式:
jstring NewJString ( JNIEnv * env , LPCTSTR str )
{
if (!env || !str )
return 0 ;
int slen = strlen (str );
jchar * buffer = new jchar [slen ];
int len = MultiByteToWideChar (CP_ACP , 0 , str , strlen (str ), buffer , slen );
if (len > 0 && len < slen )
buffer [len ] = 0 ;
jstring js = env ->NewString (buffer , len );
delete [] buffer ;
return js ;
}
而要将一个jstring对象转为一个 char字符串数组,可以:
int JStringToChar ( JNIEnv * env , jstring str , LPTSTR desc , int desc_len )
{
int len = 0 ;
if (desc == NULL || str == NULL )
return - 1 ;
// Check buffer size
if (env ->GetStringLength (str ) * 2 + 1 > desc_len )
{
return - 2 ;
}
memset (desc , 0 , desc_len );
const wchar_t * w_buffer = env ->GetStringChars (str , 0 );
len = WideCharToMultiByte (CP_ACP , 0 , w_buffer , wcslen (w_buffer ) + 1 , desc , desc_len , NULL , NULL );
env ->ReleaseStringChars (str , w_buffer );
if (len > 0 && len < desc_len )
desc [len ] = 0 ;
return strlen (desc );
}
当然,按照上面的分析,你也可以直接将GetStringChars的返回结果作为 wchar_t串来进行操作。或者,如果你愿意,你也可以将GetStringUTFChars的结果通过MultiByteToWideChar转换为UCS2编码串,再通过WideCharToMultiByte转换为多字节串。
const char * pstr = env ->GetStringUTFChars (str , false );
int nLen = MultiByteToWideChar ( CP_UTF8 , 0 , pstr , - 1 , NULL , NULL ); //得到UTF-8编码的字符串长度
LPWSTR lpwsz = new WCHAR [nLen ];
MultiByteToWideChar ( CP_UTF8 , 0 , pstr , - 1 , lpwsz , nLen ); //转换的结果是UCS2格式的编码串
int nLen1 = WideCharToMultiByte ( CP_ACP , 0 , lpwsz , nLen , NULL , NULL , NULL , NULL );
LPSTR lpsz = new CHAR [nLen1 ];
WideCharToMultiByte ( CP_ACP , 0 , lpwsz , nLen , lpsz , nLen1 , NULL , NULL ); //将UCS2格式的编码串转换为多字节
cout << "Out:" << lpsz << endl ;
delete [] lpwsz ; delete [] lpsz ;
当然,我相信很少有人想要或者需要这么做。
这里需要注意一点,GetStringChars的返回值是jchar,而GetStringUTFChars的返回值是 const char *.
除了上面的办法外,当需要经常在jstring和 char *之间进行转换时我们还有一个选择,那就是下面的这个类。这个类本来是一个叫Roger S . Reynolds的老外提供的,想法非常棒,但用起来却不太灵光,因为作者将考虑的重心放在UTF格式串上,但在实际操作中,我们往往使用的却是ACP(ANSI code page)串。下面是原作者的程序:
class UTFString {
private :
UTFString (); // Default ctor - disallowed
public :
// Create a new instance from the specified jstring
UTFString (JNIEnv * env , const jstring & str ) :
mEnv (env ),
mJstr (str ),
mUtfChars (( char * )mEnv ->GetStringUTFChars (mJstr , 0 )),
mString (mUtfChars ) { }
// Create a new instance from the specified string
UTFString (JNIEnv * env , const string & str ) :
mEnv (env ),
mString (str ),
mJstr (env ->NewStringUTF (str .c_str ())),
mUtfChars (( char * )mEnv ->GetStringUTFChars (mJstr , 0 )) { }
// Create a new instance as a copy of the specified UTFString
UTFString ( const UTFString & rhs ) :
mEnv (rhs .mEnv ),
mJstr (mEnv ->NewStringUTF (rhs .mUtfChars )),
mUtfChars (( char * )mEnv ->GetStringUTFChars (mJstr , 0 )),
mString (mUtfChars ) { }
// Delete the instance and release allocated storage
~UTFString () { mEnv ->ReleaseStringUTFChars (mJstr , mUtfChars ); }
// assign a new value to this instance from the given string
UTFString & operator =( const string & rhs ) {
mEnv ->ReleaseStringUTFChars (mJstr , mUtfChars );
mJstr = mEnv ->NewStringUTF (rhs .c_str ());
mUtfChars = ( char * )mEnv ->GetStringUTFChars (mJstr , 0 );
mString = mUtfChars ;
return * this ;
}
// assign a new value to this instance from the given char*
UTFString & operator =( const char * ptr ) {
mEnv ->ReleaseStringUTFChars (mJstr , mUtfChars );
mJstr = mEnv ->NewStringUTF (ptr );
mUtfChars = ( char * )mEnv ->GetStringUTFChars (mJstr , 0 );
mString = mUtfChars ;
return * this ;
}
// Supply operator methods for converting the UTFString to a string
// or char*, making it easy to pass UTFString arguments to functions
// that require string or char* parameters.
string & GetString () { return mString ; }
operator string () { return mString ; }
operator const char * () { return mString .c_str (); }
operator jstring () { return mJstr ; }
private :
JNIEnv * mEnv ; // The enviroment pointer for this native method.
jstring mJstr ; // A copy of the jstring object that this UTFString represents
char * mUtfChars ; // Pointer to the data returned by GetStringUTFChars
string mString ; // string buffer for holding the "value" of this instance
};
我将它改了改:
class JNIString {
private :
JNIString (); // Default ctor - disallowed
public :
// Create a new instance from the specified jstring
JNIString (JNIEnv * env , const jstring & str ) :
mEnv (env ) {
const jchar * w_buffer = env ->GetStringChars (str , 0 );
mJstr = env ->NewString (w_buffer ,
wcslen (w_buffer )); // Deep Copy, in usual case we only need Shallow Copy as we just need this class to provide some convenience for handling jstring
mChars = new char [wcslen (w_buffer ) * 2 + 1 ];
WideCharToMultiByte (CP_ACP , 0 , w_buffer , wcslen (w_buffer ) + 1 , mChars , wcslen (w_buffer ) * 2 + 1 ,
NULL , NULL );
env ->ReleaseStringChars (str , w_buffer );
mString = mChars ;
}
// Create a new instance from the specified string
JNIString (JNIEnv * env , const string & str ) :
mEnv (env ) {
int slen = str .length ();
jchar * buffer = new jchar [slen ];
int len = MultiByteToWideChar (CP_ACP , 0 , str .c_str (), str .length (), buffer , slen );
if (len > 0 && len < slen )
buffer [len ] = 0 ;
mJstr = env ->NewString (buffer , len );
delete [] buffer ;
mChars = new char [str .length () + 1 ];
strcpy (mChars , str .c_str ());
mString .empty ();
mString = str .c_str ();
}
// Create a new instance as a copy of the specified JNIString
JNIString ( const JNIString & rhs ) :
mEnv (rhs .mEnv ) {
const jchar * wstr = mEnv ->GetStringChars (rhs .mJstr , 0 );
mJstr = mEnv ->NewString (wstr , wcslen (wstr ));
mEnv ->ReleaseStringChars (rhs .mJstr , wstr );
mChars = new char [strlen (rhs .mChars ) + 1 ];
strcpy (mChars , rhs .mChars );
mString = rhs .mString .c_str ();
}
// Delete the instance and release allocated storage
~JNIString () { delete [] mChars ; }
// assign a new value to this instance from the given string
JNIString & operator =( const string & rhs ) {
delete [] mChars ;
int slen = rhs .length ();
jchar * buffer = new jchar [slen ];
int len = MultiByteToWideChar (CP_ACP , 0 , rhs .c_str (), rhs .length (), buffer , slen );
if (len > 0 && len < slen )
buffer [len ] = 0 ;
mJstr = mEnv ->NewString (buffer , len );
delete [] buffer ;
mChars = new char [rhs .length () + 1 ];
strcpy (mChars , rhs .c_str ());
mString = rhs .c_str ();
return * this ;
}
// Supply operator methods for converting the JNIString to a string
// or char*, making it easy to pass JNIString arguments to functions
// that require string or char* parameters.
string & GetString () { return mString ; }
operator string () { return mString ; }
operator const char * () { return mString .c_str (); }
operator jstring () { return mJstr ; }
private :
JNIEnv * mEnv ; // The enviroment pointer for this native method.
jstring mJstr ; // A copy of the jstring object that this JNIString represents
char * mChars ; // Pointer to a ANSI code page char array
string mString ; // string buffer for holding the "value" of this instance (ANSI code page)
};
后者除了将面向UTF编码改成了面向ANSI编码外,还去掉了 operator =( const char * ptr )的定义,因为 operator =( const string & rhs )可以在需要的时候替代前者而无需任何额外编码。(因为按照C ++规范, const reference可以自动转换,详见本人另一文章《关于 const reference的几点说明》http : //blog.vckbase.com/billdavid/archive/2004/11/11/1453.html)
如果你愿意,给JNIString再加个JNIString (JNIEnv * env , const wstring & str )和一个 operator =( const wstring & rhs )操作符重载就比较完美了, :),很简单,留给用得到的朋友自己加吧。
下面是一个使用该类的例子(真正跟用于演示的code很少,大部分都是些routine code, :)):
#include <iostream>
#include <string>
#include <assert.h>
#include <jni.h>
using namespace std ;
int main () {
int res ;
JavaVM * jvm ;
JNIEnv * env ;
JavaVMInitArgs vm_args ;
JavaVMOption options [ 3 ];
options [ 0 ].optionString = "-Djava.compiler=NONE" ;
options [ 1 ].optionString = "-Djava.class.path=.;.." ; // .. is specially for this project
options [ 2 ].optionString = "-verbose:jni" ;
vm_args .version = JNI_VERSION_1_4 ;
vm_args .nOptions = 3 ;
vm_args .options = options ;
vm_args .ignoreUnrecognized = JNI_TRUE ;
res = JNI_CreateJavaVM (& jvm , ( void * * )& env , & vm_args );
if (res < 0 ) {
fprintf (stderr , "Can''''t create Java VM/n" );
return 1 ;
}
jclass cls = env ->FindClass ( "jni/test/Demo" );
assert ( 0 != cls );
jmethodID mid = env ->GetMethodID (cls , "<init>" , "(Ljava/lang/String;)V" );
assert ( 0 != mid );
wchar_t * p = L"中国" ;
jobject obj = env ->NewObject (cls , mid , env ->NewString ( reinterpret_cast <jchar *> (p ), wcslen (p )));
assert ( 0 != obj );
mid = env ->GetMethodID (cls , "getMessage" , "()Ljava/lang/String;" );
assert ( 0 != mid );
jstring str = (jstring )env ->CallObjectMethod (obj , mid );
// use JNIString for easier handling.
JNIString jnistr (env , str );
cout << "JNIString:" << jnistr .GetString () << endl ;
jnistr = "中文" ;
cout << jnistr .GetString () << endl ;
jvm ->DestroyJavaVM ();
fprintf (stdout , "Java VM destory./n" );
return 0 ;
}
参考:
1、UTF - 8 and Unicode FAQ for Unix /Linuxs,http : //www.cl.cam.ac.uk/~mgk25/unicode.html,其中文翻译见http://www.linuxforum.net/books/UTF-8-Unicode.html
2、深入剖析Java编程中的中文问题及建议最优解决方法,http : //blog.youkuaiyun.com/abnerchai/archive/2004/04/28/18576.aspx
3、关于Java中文问题的几条分析原则,http : //www-900.ibm.com/developerWorks/cn/java/l-javachinese/index.shtml
4、Java 编程技术中汉字问题的分析及解决,http : //www-900.ibm.com/developerWorks/cn/java/java_chinese/index.shtml
5、深入剖析JSP和Servlet对中文的处理过程,http : //blog.youkuaiyun.com/deuso/archive/2005/12/01/541511.aspx
6、宽字符标量 L"xx"在VC6 .0 / 7.0和GNU g ++中的不同实现,http : //blog.vckbase.com/smileonce/archive/2004/12/09/1972.html
7、XML Encoding,http : //www.w3schools.com/xml/xml_encoding.asp