JNI中文处理问题小结

由于工作关系,需要利用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中文乱码问题在很多情况下都可能发生:不同应用间,不同平台间等等,但以上问题已有大量优秀的文章讨论过,这里不作深入探讨,详见参考 2345。下面简要总结一下:
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 ++中,字符串是用 char8位)或者 wchar_t16位,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.csdn.net/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.csdn.net/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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值