网站的用户认证使用RSA加密方式,架构师认为使用JCA需要耗费CPU的资源,要使用Openssl,我想了一下使用c就不消耗cpu资源了,所以准备使用jni。服务器使用英文版linux,因为linux命令是英文,而且在远端vi中,看到的中文页是乱码,所以我们安装服务器就是安装英文linux,没想到这也是个问题。因为用户名是中文,java传递一些中文字串到 C,但是c是乱码JNIEXPORT jint JNICALL Java_test_string (JNIEnv *env, jobject jobj, jstring name, jint no){ jboolean iscopy; int length; const char *temp = (*env)->GetStringUTFChars(env, name, &iscopy); printf("temp is %s\n",temp); length = no * 2; return length;}后来发现GetStringUTFChars 传回來的是字串以 Java modified UTF-8 编码的 byte array(一ge unicode char 是 2 bytes,以 UTF-8 是1到3个bytes 不等),用 printf一个 byte一个 byte打出来就是乱码。 应该用底层平台提供处理 unicode 的函数來作输出,因为printf只用于ascii码,只好使用String 的 getBytes 方法,指定平台使用的中文的编码GBK。 JNIEXPORTjint JNICALL Java_test_string(JNIEnv *env, jobject jobj, jstring msg, jint no){ jclass klass = env->GetObjectClass(msg); jmethodID getBytes = env->GetMethodID(klass, "getBytes", "(Ljava/lang/String;)[B"); jvalue arg; arg.l = env->NewStringUTF("GBK"); jbyteArray chs = (jbyteArray) env->CallObjectMethodA(msg, getBytes, &arg); int len = env->GetArrayLength(chs); jbyte* msgP = env->GetByteArrayElements(chs, 0); char* buf = new char[len + 1]; buf[len] = '\0'; for (int i = 0; i < len; ++i) buf[i] = msgP[i]; env->ReleaseByteArrayElements(chs, msgP, JNI_ABORT); printf("Message is: %s\n", buf); delete[] buf; return no * 2;} jmethodID getBytes = env->GetMethodID(klass, "getBytes", "(Ljava/lang/String;)[B");是取得 method: {byte[] getBytes(String)} 的 method id。c的代码JNIEXPORT jint JNICALL Java_testString(JNIEnv *jenv, jclass jcls, jstring jarg1) { jclass klass; jmethodID getByteID; jbyteArray chs=0; jvalue arg; int len, i; char buf[128]; jbyte *msgP; klass = (*jenv)->GetObjectClass(jenv, jarg1); getByteID = (*jenv)->GetMethodID(jenv, klass, "getBytes", "(Ljava/lang/String;)[B"); arg.l = (*jenv)->NewStringUTF(jenv, "GBK"); chs = (jbyteArray)(*jenv)->CallObjectMethodA(jenv, jarg1, getByteID, &arg); len = (*jenv)->GetArrayLength(jenv, chs); printf("len=%d\n",len); msgP = (*jenv)->GetByteArrayElements(jenv, chs, 0); for ( i = 0; i < len; ++i) buf[i] = msgP[i]; buf[len]= '\0'; (*jenv)->ReleaseByteArrayElements(jenv, chs, msgP, JNI_ABORT); printf("Message is: %s\n", buf); return 1;}发现jni还是乱码,只好 把byte array一个一个打印出来和UltraEdit比较,记得或是你使用在 windows 平台 , MessageBox 显示buf ,可以看到中文,因为 VC,使用 character set 为MBCS(Muilti-Byte Character Set)。后来发现编译 Java source 使用 ISO8859-1 来编译,所以编译是 source 中的string的 literal以 ISO8859-1 的编码抓换成 unicode,,如果unicode 字串再以 GBK转为成 byte array 就不是真正的GBK的 byte array。 也可以可以用 JDK 的native2ascii来转码,最好看一下 file.encoding 是那个编码。 如果编译时没有有指定编码方式,编译器会以系统设定编码 source code,英文linux是 ISO8859-1, 在编译-encoding GBK指定编码。又发现jni是中文,但是java又成了乱码,看了看文档,明白了javac -encoding GBK *.java编译 必须用 java -Dfile.encoding=GBK 执行 因为output steam 的 charset 默认把非 ISO 的GBK字符都转为 ? 才 print 出来。但是发现还是不行,因为使用-encoding GBK编译,但是System.getProperty("file.encoding")还是ISO-8859-1,只好设linux了,export LANG=zh_CN,这下不用-encoding GBK,居然也是中文。在Java系统中使用 PrintStream 打印,如果 PrintStream没有指定 encoding 就是使用OS设定的 encoding例如System.out 。PrintStream 在输出字符串是就是指定 PrintStream 使用的 encoding ,使用 String 的 getBytes方法得到一个 byte array,再把 byte array element 输出,所以 unicode String 的用户名 以 ISO-8859-1 encoding 解码时,大于 127 的 byte 变成 '?'。可以强制使用try{ System.setOut( new PrintStream(System.out, true, "GBK"));}catch(java.io.UnsupportedEncodingException e){ }finally{ }关于c++把中文返回java的写法C语言中的字符串不是 UTF-8而是,而是 mbcs。 可以使用 C++ wchar_t ,或是事先用UltraEdit把中文字符转成 UTF8 格式。 wchar_t* p = L"邢红瑞"; // unicode return env->NewString(reinterpret_cast<jchar*>(p), wcslen(p));c中没有casting 这样写(*env)->NewString(env, (jchar*) p, wcslen(p));发现jni调用java的类却是麻烦,看了看NLS strings规范使用 Windows本地方法有两个 JNI APIs 访问Java strings使用 Windows本地方法GetStringUTFCharsGetStringCharswindows的例子import java.awt.*;import java.awt.event.*;public class StringTest extends Frame implements ActionListener{ private TextField input = new TextField("",20); private Button sendButton = new Button( "Send" ); public StringTest() { super("String Test"); setLayout( new FlowLayout() ); add( input ); add( sendButton ); sendButton.addActionListener( this ); } public void actionPerformed( ActionEvent evt ) { if( evt.getSource()==sendButton ) { String s = input.getText(); showParms( s, s ); } } public void showParms( String s1, String s2 ) { showParms0( s1, s2 ); } public native void showParms0( String s1, String s2 ); static { System.loadLibrary( "StringTest" ); } public static void main( String args[] ) { StringTest frame = new StringTest(); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); frame.setSize(180, 200); frame.setVisible(true); }}#include <windows.h>#include <stdio.h>#include "StringTest.h"char* jstringToWindows( JNIEnv* env, jstring jstr );JNIEXPORT void JNICALL Java_StringTest_showParms0 (JNIEnv *env, jobject obj, jstring s1, jstring s2){ const char* szStr1 = (*env)->GetStringUTFChars( env, s1, 0 ); const char* szStr2 = jstringToWindows( env, s2 ); MessageBox( HWND_DESKTOP, (LPCSTR)szStr1, (LPCSTR)"String1", 0 ); MessageBox( HWND_DESKTOP, (LPCSTR)szStr2, (LPCSTR)"String2", 0 ); (*env)->ReleaseStringUTFChars( env, s1, szStr1 );}char* jstringToWindows( JNIEnv* env, jstring jstr ){ int length = (*env)->GetStringLength( env, jstr ); const jchar* jcstr = (*env)->GetStringChars( env, jstr, 0 ); char* rtn = (char*)malloc( length*2+1 ); int size = 0; size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length, rtn, (length*2+1), NULL, NULL ); if( size <= 0 ) return NULL; (*env)->ReleaseStringChars( env, jstr, jcstr ); rtn[size] = 0; return rtn;}GetStringUTFChars创建c格式的字符串,GetStringChars创建unicode的字符串,windows传到java的string MultiByteToWideChar 转化windows string 到 Unicode character arrayjstring WindowsTojstring( JNIEnv* env, char* str ){ jstring rtn = 0; int slen = strlen(str); wchar_t* buffer = 0; if( slen == 0 ) rtn = (*env)->NewStringUTF( env, str ); //UTF ok since empty string else { int length = 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 ); return rtn;}MultiByteToWideChar被调用两次,第一次是Unicode character字符的个数,用于内存分配,第二次是进行转换,NewString 创建java的String,下面给出c中如何调用java类,使用命令行参数import java.awt.*;import java.awt.event.*;public class MyApp extends Frame{ public MyApp( String[] msgs ) { super( "My Java App" ); Label label = new Label(); String labelMsg = new String(); for( int i=0; i < msgs.length; i++ ) labelMsg += msgs[i] + " "; label.setText( labelMsg ); add( label ); } public static void main( String[] args ) { MyApp frame = new MyApp( args ); frame.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); frame.setSize(200,200); frame.setVisible(true); }}#include <windows.h>#include <stdio.h>#include <jni.h>#define MAIN_CLASS "MyApp"int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ JNIEnv* env; JavaVM* jvm; JDK1_1InitArgs vmargs; jint rc; jclass cls; jmethodID mainId; /* get CLASSPATH environment variable setting */ char* szClasspath = getenv( "CLASSPATH" ); vmargs.version = 0x00010001; /* version 1.1 */ JNI_GetDefaultJavaVMInitArgs( &vmargs ); /* init vmargs */ /* the classpath returned by JNI_GetDefaultJavaVMInitArgs is wrong */ vmargs.classpath = szClasspath; rc = JNI_CreateJavaVM( &jvm, &env, &vmargs ); /* create JVM */ if( rc < 0 ) return 1; /* load the class containing the static main() method */ cls = (*env)->FindClass( env, MAIN_CLASS ); if( cls == 0 ) return 1; /* find the main() method */ mainId = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V"); if( mainId == 0 ) return 1; /* error */ /* setup the parameters to pass to main() */ { jstring str; jobjectArray args; int i=0; args = (*env)->NewObjectArray(env, __argc-1, (*env)->FindClass(env, "java/lang/String"), 0); for( i=1; i<__argc; i++ ) { str = (*env)->NewStringUTF( env, __argv[i] ); (*env)->SetObjectArrayElement(env, args, i-1, str); } (*env)->CallStaticVoidMethod(env, cls, mainId, args); /* call main() */ } (*jvm)->DestroyJavaVM( jvm ); /* kill JVM */ return 0;}将 str = (*env)->NewStringUTF( env, __argv[i] ); 换为 str = Windows2jstring( env, __argv[i] ); 解决中文问题。 |