NLS strings and JNI

来源:互联网 发布:台湾网络图片 编辑:程序博客网 时间:2024/05/29 15:59

Introduction

Java applications are intended to be national language independent, just as they are intended to be operating system independent. If native methods are required on Windows, the Java application which implements the JNI methods will require special code when handling strings. Strings in Java are managed in Unicode. In Windows, strings are displayed and inputted in the current code page. Therefore, conversions are necessary if user input or output is required in a native method.

There are two JNI APIs to access Java strings from Windows native methods:

  • GetStringUTFChars
  • GetStringChars

The UTF format is defined by Java and matches the first 127 characters of the ASCII table. The UTF methods cannot generally be used because other national language characters in the UTF format cannot be understood by Windows APIs. However, these methods are very convenient for simple strings, such as debug messages, where a user is not required to convert Java strings directly to and from the C-style char* type.

The non-UTF (Unicode) JNI methods can be used but require special Windows APIs to convert them for display. The data produced by the Unicode methods is called wide-char or wchar* in Windows. The following Windows APIs are used to convert these strings:

  • WideCharToMultiByte
  • MultiByteToWideChar

This paper will develop simple examples showing how these methods are used.



Back to top

Java String in Windows

In this section, we will create a test Java class with a native method displaying string arguments in a Windows message box. There is no reason for this function to be a native method, but it provides a simple example to illustrate how Java strings are displayed.


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);  }}

For more information on JNI see the JNI Specification available from Sun Microsystems. The example above is the complete test program including the native method, showParms0. This method will receive two strings which it will display in a Windows message box.

The native method is implemented in C in file, StringTest.c:


#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;}

The JNI API, GetStringUTFChars is used to create a C string from the Java string or jstring argument. The GetStringChars method creates a jchar array (Unicode characters) from the Java string.

The StringTest.dll is created by compiling the C source. The following compile statement uses the Microsoft Visual C++ compiler:


cl -Ic:/jdk1.1.6/include -Ic:/jdk1.1.6/include/win32 -LD StringTest.c     -FeStringTest.dll user32.lib

where c:/jdk1.1.6 is the path for the installed JDK.

The showParms0 function is passed the same string on both parameters. The reason for this is to show the difference between using the UTF methods and using the Unicode methods for converting strings. The class is run using the Java interpreter and the output is as follows:

   java StringTest 

This will show a window with an entry field, a pushbutton, and a listbox. As a test, some non-English national language characters are entered along with the word HELLO as follows:



Clicking "Send" calls the showParms0 native method which shows two message boxes.



The string characters from the message box were converted using the UTF method. Only the HELLO characters are correct. The second message box displays strings from the Unicode methods.



All characters are displayed correctly.



Back to top

Windows string in Java

This example will accept command line parameters and display them in the Label of the Frame of the Java application. The Java code is written to show the argument strings:


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);  }}

The application can be executed using the java.exe interpreter:

   java MyApp First second last 

which shows the frame window with the string parameters:



Passing parameters to the Java class main from C requires creating a string array from the parameters and passing the array to CallStaticVoidMethod. The following code will launch a Java application from a Windows .EXE and passes command line parameters to the Java program.


#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;}

The __argc and __argv variables are convenient for accessing the command line parameters in a WinMain .EXE and are equivalent to argc and argv in a regular main .EXE.

The MyApp.exe is built using the following command line for Microsoft Visual C++:

  cl -Ic:/jdk1.1.6/include -Ic:/jdk1.1.6/include/win32 -MT MyApp.c c:/jdk1.1.6/lib/javai.lib 

c:/jdk1.1.6 is where the JDK is installed. If MyApp.exe is executed from a Windows command line, any parameters are displayed in the Java window.

In the methods presented above, the UTF strings methods are used to access and create Java strings. These methods cannot be used for other national language character sets like the double-byte character set (DBCS) or other national language characters. For example, if the previous example's MyApp.exe is executed from the command line as follows:

  MyApp d?os?enregistr?

The following is shown in the GUI:



The accented e's are not shown correctly because they are above 127 in the ASCII table. Using Unicode along with the appropriate JNI and Windows APIs will correct this problem.

Java strings can be converted to and from Unicode character arrays using the appropriate JNI APIs. Once the Java Strings are obtained in Unicode, the API MultiByteToWideChar converts a Windows string to a Unicode character array. The following function can be used to create a Java String from a multi-byte string.


jstring 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;}

In the above function, MultiByteToWideChar is called twice. The first call determines the number of Unicode characters, which is then used to compute the size of the memory allocation to hold the character array. The second call performs the conversion into that array. The Java string is created from the array by calling the NewString method. Now the function will be used to correct the problem of displaying the wrong characters introduced in the example. The above WindowsTojstring function is added to the C code and the following line is changed from:

   str = (*env)->NewStringUTF( env, __argv[i] );    to:   str = Windows2jstring( env, __argv[i] ); 

A recompile and re-execute now shows the parameters correctly.





Back to top

Summary

This paper presented examples for implementing native methods dealing with conversion of strings for an NLS-enabled Java program environment. Strings are maintained in Unicode in Java and must be converted appropriately using the following JNI APIs:

  • NewString
  • GetStringChars
  • ReleaseStringChars
  • GetStringLength

and the following Windows APIs:

  • MultiByteToWideChar
  • WideCharToMultiByte

See the Java Native Interface Specification available from Sun Microsystems for more information on JNI.



Back to top



Back to top

About the author

David Wendt is an IBM Programmer for WebSphere Studio in Research Triangle Park, NC. He can be reached at wendt@us.ibm.com.

 
原创粉丝点击