2012년 1월 6일 금요일

[JNI] 안드로이드 JNI 환경에서 C++과 Java 간의 한글 데이터 전송 문제

출처 : http://snowbora.com/371

지금은 잠깐 쉬고 있지만, 예전에 안드로이드 ndk 환경에서 프로그래밍을 하던 때가 있었습니다.
그 때, JNI 인터페이스를 통해 Native단의 C++과 Application단의 Java와의 데이터를 주고 받아야 하는 API들을 
만들었었는데, 그 중 문자열 전송과 관련해서 한글이 죄다 깨져서 전송되는 현상이 있었습니다.

Java 단에서는 UTF-8 방식을 이용하고, C++에서는 KSC5601 방식의 문자열이었기 때문에 발생하는 문제였습니다.
그래서 구글링을 통해 C++과 Java간의 한글 변환 관련 코드를 확보 및 컨버팅을 했습니다.


C++과 Java간의 한글 변환 코드는 다음과 같습니다. 다음은 헤더 파일입니다.
01.#ifndef _STRING_CONVERTER_FOR_JNI_
02.#define _STRING_CONVERTER_FOR_JNI_
03. 
04.#include <jni.h>
05.#include <stdio.h>
06.#include <string.h>
07. 
08.#ifdef __cplusplus
09.extern "C"
10.{
11.#endif
12. 
13.char *jbyteArray2cstr( JNIEnv *env, jbyteArray javaBytes );
14.jbyteArray cstr2jbyteArray( JNIEnv *env, const char *nativeStr);
15.jbyteArray javaGetBytes( JNIEnv *env, jstring str );
16.jbyteArray javaGetBytesEncoding( JNIEnv *env, jstring str, const char *encoding );
17.jstring javaNewString( JNIEnv *env, jbyteArray javaBytes );
18.jstring javaNewStringEncoding(JNIEnv *env, jbyteArray javaBytes, const char *encoding );
19. 
20.jstring javaNewStringChar(JNIEnv* env, const char* nativeStr);
21. 
22.#ifdef __cplusplus
23.}
24.#endif
25. 
26.#endif //_STRING_CONVERTER_FOR_JNI_
27.</string.h></stdio.h></jni.h>

그리고 다음은 C++ 소스 코드입니다.
001.#include "StringConverter_For_JNI.h"
002. 
003.static jclass class_String;
004.static jmethodID mid_getBytes, mid_getBytesEncoding;
005.static jmethodID mid_newString, mid_newStringEncoding;
006. 
007.char *jbyteArray2cstr( JNIEnv *env, jbyteArray javaBytes )
008.{
009.size_t len = env->GetArrayLength(javaBytes);
010.jbyte *nativeBytes = env->GetByteArrayElements(javaBytes, 0);
011.char *nativeStr = (char *)malloc(len+1);
012.strncpy( nativeStr, (const char*)nativeBytes, len );
013.nativeStr[len] = '\0';
014.env->ReleaseByteArrayElements(javaBytes, nativeBytes, JNI_ABORT);
015. 
016.return nativeStr;
017.}
018. 
019.jbyteArray cstr2jbyteArray( JNIEnv *env, const char *nativeStr)
020.{
021.jbyteArray javaBytes;
022.int len = strlen( nativeStr );
023.javaBytes = env->NewByteArray(len);
024.env->SetByteArrayRegion(javaBytes, 0, len, (jbyte *) nativeStr );
025. 
026.return javaBytes;
027.}
028. 
029.jbyteArray javaGetBytes( JNIEnv *env, jstring str )
030.{
031.if ( mid_getBytes == 0 )
032.{
033.if ( class_String == 0 )
034.{
035.jclass cls = env->FindClass("java/lang/String");
036.if ( cls == 0 )
037.{
038.return 0;
039.}
040. 
041.class_String = (jclass)env->NewGlobalRef(cls);
042.env->DeleteLocalRef(cls);
043.if ( class_String == 0 )
044.{
045.return 0;
046.}
047.}
048. 
049.mid_getBytes = env->GetMethodID(class_String, "getBytes", "()[B");
050.if (mid_getBytes == 0)
051.{
052.return 0;
053.}
054.}
055. 
056.return (jbyteArray)env->CallObjectMethod(str, mid_getBytes );
057.}
058. 
059.jbyteArray javaGetBytesEncoding( JNIEnv *env, jstring str, const char *encoding )
060.{
061.if ( mid_getBytesEncoding == 0 )
062.{
063.if ( class_String == 0 )
064.{
065.jclass cls = env->FindClass("java/lang/String");
066.if ( cls == 0 )
067.{
068.return 0;
069.}
070. 
071.class_String = (jclass)env->NewGlobalRef(cls);
072.env->DeleteLocalRef(cls);
073.if ( class_String == 0 )
074.{
075.return 0;
076.}
077.}
078. 
079.mid_getBytesEncoding = env->GetMethodID(class_String, "getBytes", "(Ljava/lang/String;)[B");
080.if (mid_getBytesEncoding == 0)
081.{
082.return 0;
083.}
084.}
085. 
086.jstring jstr = env->NewStringUTF(encoding);
087.jbyteArray retArray = (jbyteArray)env->CallObjectMethod(str, mid_getBytesEncoding, jstr);
088.env->DeleteLocalRef(jstr);
089. 
090.return retArray;
091.}
092. 
093.jstring javaNewString( JNIEnv *env, jbyteArray javaBytes )
094.{
095.if ( mid_newString == 0 )
096.{
097.if ( class_String == 0 )
098.{
099.jclass cls = env->FindClass("java/lang/String");
100.if ( cls == 0 )
101.{
102.return 0;
103.}
104. 
105.class_String = (jclass)env->NewGlobalRef(cls);
106.env->DeleteLocalRef(cls);
107.if ( class_String == 0 )
108.{
109.return 0;
110.}
111.}
112. 
113.mid_newString = env->GetMethodID(class_String, "<init>", "([B)V");
114.if ( mid_newString == 0 )
115.{
116.return 0;
117.}
118.}
119. 
120.return (jstring)env->NewObject(class_String, mid_newString, javaBytes );
121.}
122. 
123.jstring javaNewStringEncoding(JNIEnv *env, jbyteArray javaBytes, const char *encoding )
124.{
125.int len;
126.jstring str;
127.if ( mid_newString == 0 )
128.{
129.if ( class_String == 0 )
130.{
131.jclass cls = env->FindClass("java/lang/String");
132.if ( cls == 0 )
133.{
134.return 0;
135.}
136. 
137.class_String = (jclass)env->NewGlobalRef(cls);
138.env->DeleteLocalRef(cls);
139.if ( class_String == 0 )
140.{
141.return 0;
142.}
143. 
144.}
145. 
146.mid_newString = env->GetMethodID(class_String, "<init>", "([BLjava/lang/String;)V");
147.if ( mid_newString == 0 )
148.{
149.return 0;
150.}
151.}
152. 
153.jstring jstr = env->NewStringUTF(encoding);
154.str = (jstring)env->NewObject(class_String, mid_newString, javaBytes, jstr);
155.env->DeleteLocalRef(jstr);
156. 
157.return str;
158.}
159. 
160.jstring javaNewStringChar(JNIEnv* env, const char* nativeStr)
161.{
162.jbyteArray byteArray = cstr2jbyteArray(env, nativeStr);
163.jstring jstr = javaNewString(env, byteArray);
164.env->DeleteLocalRef(byteArray);
165. 
166.return jstr;
167.}
168.</init></init>


이 함수들을 이용해서 실제로 사용한 예제는 다음과 같습니다. 
C++의 string을 Java용 jstring으로 변환하는 부분
1.jstring jstrValue;
2.jfieldID fieldID;
3.jmethodID methodID;
4. 
5.methodID = env->GetMethodID( cProgramInfo, "ProgramName","(Ljava/lang/String;)V");
6.jstrValue = javaNewStringChar(env, strProgramName);
7.env->CallVoidMethod( jElement, methodID, jstrValue);
8.env->DeleteLocalRef(jstrValue);

 Java용 jstring를 C++의 string으로 변환하는 함수
01.string      title_;
02.jmethodID methodID;
03. 
04.methodID = env->GetMethodID( cProgramInfo, "ProgramTitle","()Ljava/lang/String;");
05.jstring jstrTitle = (jstring)env->CallObjectMethod(jElement, methodID);
06.if(jstrTitle)
07.{
08.pszTitle = (char*)env->GetStringUTFChars(jstrTitle, &iscopy);
09.title_ = pszTitle;
10.env->ReleaseStringUTFChars(jstrTitle, pszTitle);
11.env->DeleteLocalRef(jstrTitle); 
12.}

The Java Native Interface Programmer's Guide and Specification

출처 : http://java.sun.com/docs/books/jni/html/jniTOC.html



Preface

Part One: Introduction and Tutorial

1 Introduction

1.1 The Java Platform and Host Environment
1.2  Role of the JNI
1.3  Implications of Using the JNI
1.4  When to Use the JNI
1.5  Evolution of the JNI
1.6  Example Programs

2 Getting Started

2.1  Overview
2.2  Declare the Native Method
2.3  Compile the HelloWorld Class
2.4  Create the Native Method Header File
2.5  Write the Native Method Implementation
2.6  Compile the C Source and Create a Native Library
2.7  Run the Program

Part Two: Programmer's Guide

3 Basic Types, Strings, and Arrays

3.1  A Simple Native Method
C Prototype for Implementing the Native Method
Native Method Arguments
Mapping of Types
3.2  Accessing Strings
Converting to Native Strings
Freeing Native String Resources
Constructing New Strings
Other JNI String Functions
New JNI String Functions in Java 2 SDK Release 1.2
Summary of JNI String Functions
Choosing among the String Functions
3.3  Accessing Arrays
Accessing Arrays in C
Accessing Arrays of Primitive Types
Summary of JNI Primitvie Array Functions
Choosing among the Primitive Array Functions
Acessing Arrays of Objects

4 Fields and Methods

4.1  Accessing Fields
Procedure for Accessing an Instance Field
Field Descriptors
Accessing Static Fields
4.2  Calling Methods
Calling Instance Methods
Forming the Method Descriptor
Calling Static Methods
Calling Instance Methods of a Superclass
4.3  Invoking Constructors
4.4  Caching Field and Method IDs
Caching at the Point of Use
Caching in the Defining Class's Initializer
Comparison between the Two Approaches to Caching IDs
4.5  Performance of JNI Field and Method Operations

5 Local and Global References

5.1  Local and Global References
Local References
Global References
Weak Global References
Comparing References
5.2  Freeing References
Freeing Local References
Managing Local References in Java 2 SDK Release 1.2
Freeing Gloabl References
5.3  Rules for Managing References

6 Exceptions

6.1  Overview
Caching and Throwing Exceptions in Native Code
A Utility Function
6.2  Proper Exception Handling
Checking for Exceptions
Handling Exceptions
Exceptions in Utility Functions

7 The Invocation Interface

7.1  Creating the Java Virtual Machine
7.2  Linking Native Applications with the Java Virtual Machine
Linking with a Known Java Virtual Machine
Linking with Unknown Java Virtual Machine
7.3   Attaching Native Threads

8 Additional JNI Features

8.1  JNI and Threads
Contraints
Monitor Entry and Exit
Monitor Wait and Notify
Obtaining a JNIEnv Pointer in Arbitrary Contexts
Matching the Thread Models
8.2  Writing Internationalized Code
Creating jstrings from Native Strings
Translating jstrings to Native Strings
8.3  Registering Native Methods
8.4  Load and Unload Handlers
The JNI_OnLoad Handler
The JNI_OnUnload Handler
8.5  Reflection Support
8.6  JNI Programming in C++

9 Leveraging Existing Native Libraries

9.1  One-to-One Mapping
9.2  Shared Stubs
9.3  One-to-One Mapping versus Shared Stubs
9.4  Implementation of Shared Stubs
The CPointer Class
The CMalloc Class
The CFunction Class
9.5  Peer Classes
Peer Classes in the Java Platform
Freeing Native Data Structures
Backpointers to Peer Instances

10 Traps and Pitfalls

10.1  Error Checking
10.2  Passing Invalid Arguments to JNI Functions
10.3  Confusing jclass with jobject
10.4  Truncating jboolean Arguments
10.5  Boundaries between Java Application and Native Code
10.6  Confusing IDs with References
10.7  Caching Field and Method IDs
10.8  Terminating Unicode Strings
10.9  Violating Access Control Rules
10.10  Disregarding Internationalization
10.11  Retaining Virtual Machine Resources
10.12  Excessive Local Reference Creation
10.13  Using Invalid Local References
10.14  Using the JNIEnv across Threads
10.15  Mismatched Thread Models

Part Three: Specification

11 Overview of the JNI Design

11.1  Design Goals
11.2  Loading Native Libraries
Class Loaders
Class Loaders and Native Libraries
Locating Native Libraries
A Type Safety Restriction
Unloading Native Libraries
11.3  Linking Native Methods
11.4  Calling Conventions
11.5  The JNIEnv Interface Pointer
Organization of the JNIEnv Interface Pointer
Benefits of an Interface Pointer
11.6  Passing Data
Global and Local References
Implementing Local References
Weak Global References
11.7  Accessing Objects
Accessing Primitive Arrays
Fields and Methods
11.8  Errors and Exceptions
No Checking for Programming Errors
Java Virtual Machine Exceptions
Asynchronous Exceptions

12 JNI Types

12.1  Primitive and Reference Types
Primitive Types
Reference Types
The jvalue Type
12.2  Field and Method IDs
12.3  String Formats
UTF-8 Strings
Class Descriptors
Field Descriptors
Method Descriptors
12.4  Constants

13 JNI Functions

13.1  Summary of the JNI Functions
Directly-Exported Invocation Interface Functions
The JavaVM Interface
Functions Defined in Native Libraries
The JNIEnv Interface
13.2  Specification of JNI Functions

Index

JNI : Strings and Arrays

출처 : http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jnistring.html


This section explains how to pass string and array data between a program written in the Java programming language and other languages.
Passing Strings
The String object in the Java language, which is represented as jstring in Java Native Interface (JNI), is a 16 bit unicode string. In C a string is by default constructed from 8 bit characters. So, to access a Java language String object passed to a C or C++ function or return a C or C++ string to a Java language method, you need to use JNI conversion functions in your native method implementation.
The GetStringUTFChars function retrieves 8-bit characters from a 16-bit jstring using the Unicode Transformation Format (UTF). UTF represents Unicode as a string of 8 or 16 bit characters without losing any information. The third parameter GetStringUTFChars results the result JNI_TRUE if it made a local copy of the jstring or JNI_FALSE otherwise.
C Version:
  (*env)->GetStringUTFChars(env, name, iscopy)

C++ Version:
  env->GetStringUTFChars(name, iscopy)

The following C JNI function converts an array of C characters to a jstring:
  (*env)->NewStringUTF(env, lastfile)
The example below converts the lastfile[80] C character array to a jstring, which is returned to the calling Java language method:
  static char lastfile[80];

  JNIEXPORT jstring JNICALL Java_ReadFile_lastFile
    (JNIEnv *env, jobject jobj) {
     return((*env)->NewStringUTF(env, lastfile));
  }

To let the Java1 virtual machine know you are finished with the UTF representation, call the ReleaseStringUTFChars conversion function as shown below. The second argument is the original jstring value used to construct the UTF representation, and the third argument is the reference to the local representation of that String.
 (*env)->ReleaseStringUTFChars(env, name, mfile);
If your native code can work with Unicode, without needing the intermediate UTF representation, call the GetStringChars function to retrieve the unicode string, and release the reference with a call toReleaseStringChars:
  JNIEXPORT jbyteArray JNICALL Java_ReadFile_loadFile
    (JNIEnv * env, jobject jobj, jstring name) {
      caddr_t m;
      jbyteArray jb;
      struct stat finfo;
      jboolean iscopy;
      const jchar *mfile = (*env)->GetStringChars(env, 
  name, &iscopy);
  //...
      (*env)->ReleaseStringChars(env, name, mfile);

Passing Arrays
In the example presented in the last section, the loadFile native method returns the contents of a file in a byte array, which is a primitive type in the Java programming language. You can retrieve and create primitive types in the Java language by calling the appropriate TypeArray function.
For example, to create a new array of floats, call NewFloatArray, or to create a new array of bytes, call NewByteArray. This naming scheme extends to retrieving elements from, adding elements to, and changing elements in the array. To get a new array of bytes, call GetByteArrayElements. To add elements to or change elements in the array, call Set<type>ArrayElements.
The GetByteArrayElements function affects the entire array. To work on a portion of the array, call GetByteArrayRegion instead. There is only a Set<type>ArrayRegion function for changing array elements. However the region could be of size 1, which is equivalent to the non-existent Set<type>ArrayElements.
Native
Code Type
Functions used
jbooleanNewBooleanArray
GetBooleanArrayElements
GetBooleanArrayRegion/SetBooleanArrayRegion
ReleaseBooleanArrayElements
jbyteNewByteArray
GetByteArrayElements
GetByteArrayRegion/SetByteArrayRegion
ReleaseByteArrayElements
jcharNewCharArray
GetCharArrayElements
GetCharArrayRegion/SetCharArrayRegion
ReleaseCharArrayElements
jdoubleNewDoubleArray
GetDoubleArrayElements
GetDoubleArrayRegion/SetDoubleArrayRegion
ReleaseDoubleArrayElements
jfloatNewFloatArray
GetFloatArrayElements
GetFloatArrayRegion/SetFloatArrayRegion
ReleaseFloatArrayElements
jintNewIntArray
GetIntArrayElements
GetIntArrayRegion/SetIntArrayRegion
ReleaseIntArrayElements
jlongNewLongArray
GetLongArrayElements
GetLongArrayRegion/SetLongArrayRegion
ReleaseLongArrayElements
jobjectNewObjectArray
GetObjectArrayElement/SetObjectArrayElement
jshortNewShortArray
GetShortArrayElements
GetShortArrayRegion/SetShortArrayRegion
ReleaseShortArrayElements
In the loadFile native method from the example in the previous section, the entire array is updated by specifying a region that is the size of the file being read in:
  jbyteArray jb;

  jb=(*env)->NewByteArray(env, finfo.st_size);
  (*env)->SetByteArrayRegion(env, jb, 0, 
  finfo.st_size, (jbyte *)m);
  close(fd);

The array is returned to the calling Java language method, which in turn, garbage collects the reference to the array when it is no longer used. The array can be explicitly freed with the following call.
  (*env)-> ReleaseByteArrayElements(env, jb, 
                                        (jbyte *)m, 0);
The last argument to the ReleaseByteArrayElements function above can have the following values:
  • 0: Updates to the array from within the C code are reflected in the Java language copy.
  • JNI_COMMIT: The Java language copy is updated, but the local jbyteArray is not freed.
  • JNI_ABORT: Changes are not copied back, but the jbyteArray is freed. The value is used only if the array is obtained with a get mode of JNI_TRUE meaning the array is a copy.
Pinning Array
When retrieving an array, you can specify if this is a copy (JNI_TRUE) or a reference to the array residing in your Java language program (JNI_FALSE). If you use a reference to the array, you will want the array to stay where it is in the Java heap and not get moved by the garbage collector when it compacts heap memory. To prevent the array references from being moved, the Java virtual machine pins the array into memory. Pinning the array ensures that when the array is released, the correct elements are updated in the Java VM.
In the loadfile native method example from the previous section, the array is not explicitly released. One way to ensure the array is garbage collected when it is no longer needed is to call a Java language method, pass the byte array instead, and then free the local array copy. This technique is shown in the section on Multi-Dimensional Arrays. Object Arrays
You can store any Java language object in an array with the NewObjectArray and SetObjectArrayElement function calls. The main difference between an object array and an array of primitive types is that when constructing a jobjectarray type, the Java language class is used as a parameter.
This next C++ example shows how to call NewObjectArray to create an array of String objects. The size of the array is set to five, the class definition is returned from a call to FindClass, and the elements of the array are initialized with an empty string. The elements of the array are updated by calling SetObjectArrayElement with the position and value to put in the array.
  #include <jni.h>
  #include "ArrayHandler.h"

  JNIEXPORT jobjectArray JNICALL 
               Java_ArrayHandler_returnArray
  (JNIEnv *env, jobject jobj){

    jobjectArray ret;
    int i;

    char *message[5]= {"first", 
 "second", 
 "third", 
 "fourth", 
 "fifth"};

    ret= (jobjectArray)env->NewObjectArray(5,
         env->FindClass("java/lang/String"),
         env->NewStringUTF(""));

    for(i=0;i<5;i++) {
        env->SetObjectArrayElement(
  ret,i,env->NewStringUTF(env, message[i]));
    }
    return(ret);
  }

The Java class that calls this native method is as follows:
  public class ArrayHandler {
    public native String[] returnArray();
    static{
        System.loadLibrary("nativelib");
    }

    public static void main(String args[]) {
        String ar[];
        ArrayHandler ah= new ArrayHandler();
        ar = ah.returnArray();
        for (int i=0; i<5; i++) {
           System.out.println("array element"+i+ 
                                 "=" + ar[i]);
        }
    }
  }

Multi-Dimensional Arrays
You might need to call existing numerical and mathematical libraries such as the linear algebra library CLAPACK/LAPACK or other matrix crunching programs from your Java language program using native methods. Many of these libraries and programs use two-dimensional and higher order arrays.
In the Java programming language, any array that has more than one dimension is treated as an array of arrays. For example, a two-dimensional integer array is handled as an array of integer arrays. The array is read horizontally, or what is also termed as row order.
Other languages such as FORTRAN use column ordering so extra care is needed if your program hands a Java language array to a FORTRAN function. Also, the array elements in an application written in the Java programming language are not guaranteed to be contigous in memory. Some numerical libraries use the knowledge that the array elements are stored next to each other in memory to perform speed optimizations, so you might need to make an additional local copy of the array to pass to those functions.
The next example passes a two-dimensional array to a native method which then extracts the elements, performs a calculation, and calls a Java language method to return the results.
The array is passed as an object array that contains an array of jints. The individual elements are extracted by first retrieving a jintArray instance from the object array by calling GetObjectArrayElement, and then extracting the elements from the jintArray row.
The example uses a fixed size matrix. If you do not know the size of the array being used, the GetArrayLength(array) function returns the size of the outermost array. You will need to call theGetArrayLength(array) function on each dimension of the array to discover the total size of the array.
The new array sent back to the program written in the Java langauge is built in reverse. First, a jintArray instance is created and that instance is set in the object array by calling SetObjectArrayElement.
public class ArrayManipulation {
  private int arrayResults[][];
  Boolean lock=new Boolean(true);
  int arraySize=-1;

  public native void manipulateArray(
  int[][] multiplier, Boolean lock);

  static{
    System.loadLibrary("nativelib");
  }
 
  public void sendArrayResults(int results[][]) {
    arraySize=results.length;
    arrayResults=new int[results.length][];
    System.arraycopy(results,0,arrayResults,
                       0,arraySize);
  }

  public void displayArray() {
    for (int i=0; i<arraySize; i++) {
      for(int j=0; j <arrayResults[i].length;j++) {
        System.out.println("array element "+i+","+j+ 
          "= "  + arrayResults[i][j]);
      }
    }
  }

  public static void main(String args[]) {
    int[][] ar = new int[3][3];
    int count=3;
    for(int i=0;i<3;i++) {
      for(int j=0;j<3;j++) {
        ar[i][j]=count;
      }
      count++;
    }
    ArrayManipulation am= new ArrayManipulation();
    am.manipulateArray(ar, am.lock);
    am.displayArray();
  }
}

#include <jni.h>
#include <iostream.h>
#include "ArrayManipulation.h"

JNIEXPORT void 
     JNICALL Java_ArrayManipulation_manipulateArray
(JNIEnv *env, jobject jobj, jobjectArray elements, 
                            jobject lock){

  jobjectArray ret;
  int i,j;
  jint arraysize;
  int asize;
  jclass cls;
  jmethodID mid;
  jfieldID fid;
  long localArrayCopy[3][3];
  long localMatrix[3]={4,4,4};

  for(i=0; i<3; i++) {
     jintArray oneDim= 
 (jintArray)env->GetObjectArrayElement(
                      elements, i);
     jint *element=env->GetIntArrayElements(oneDim, 0);
     for(j=0; j<3; j++) {
        localArrayCopy[i][j]= element[j];
     }
  }

// With the C++ copy of the array, 
// process the array with LAPACK, BLAS, etc.

  for (i=0;i<3;i++) {
    for (j=0; j<3 ; j++) {
      localArrayCopy[i][j]=
        localArrayCopy[i][j]*localMatrix[i];
     }
  }

// Create array to send back
  jintArray row= (jintArray)env->NewIntArray(3);
  ret=(jobjectArray)env->NewObjectArray(
 3, env->GetObjectClass(row), 0);

  for(i=0;i<3;i++) {
    row= (jintArray)env->NewIntArray(3);
    env->SetIntArrayRegion((jintArray)row,(
 jsize)0,3,(jint *)localArrayCopy[i]);
    env->SetObjectArrayElement(ret,i,row);
  }

  cls=env->GetObjectClass(jobj);
  mid=env->GetMethodID(cls, "sendArrayResults", 
                            "([[I)V");
  if (mid == 0) {
    cout <<"Can't find method sendArrayResults";
    return;
  }

  env->ExceptionClear();
  env->MonitorEnter(lock);
  env->CallVoidMethod(jobj, mid, ret);
  env->MonitorExit(lock);
  if(env->ExceptionOccurred()) {
    cout << "error occured copying array back" << endl;
    env->ExceptionDescribe();
    env->ExceptionClear();
  }
  fid=env->GetFieldID(cls, "arraySize",  "I");
  if (fid == 0) {
    cout <<"Can't find field arraySize";
    return;
  }
  asize=env->GetIntField(jobj,fid);
  if(!env->ExceptionOccurred()) {
    cout<< "Java array size=" << asize << endl;
  } else {
    env->ExceptionClear();
  }
  return;
}

_______
1 As used on this web site, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.