본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[Java] JNI Tutorial - String 과 Array 사용.

by 돼지왕 왕돼지 2012. 3. 26.
반응형

안녕하세요 돼지왕왕돼지입니다.

오늘의 JNI Tutorial 에 대한 내용은 String 과 Array 사용에 대한 것입니다.

이 글은  http://java.sun.com/docs/books/jni/html/objtypes.html#11202  의 내용을 요약 정리한 내용입니다.

Basic Types, Strings, and Arrays

A Simple Native Method


- Header file을 통해 생성되는 함수정의를 보면 JNIEXPORT [return type] JNICALL 로 시작을 합니다. 이 JNIEXPORT 와 JNICALL 은 이 함수가 native library 로 export 될 것이기에, C compiler 가 이 함수에 대해 calling convention을 작성하도록 하는 역할입니다.


- Java의 primitive type 은 native code에서 j 를 prefix 로 하여 정의합니다. 예를 들어 int -> jint


- Java의 reference type 은 opaque reference 라는 방법을 통해 native code로 연결됩니다. Opaque reference 는 C pointer type으로 Java VM 안의 내부 data structure 을 참조합니다. VM-independent 코드를 위해서 object 들을 적합한 JNI 함수를 통해 변환해서 사용해야 하는데, 그 함수들은 JNIEnv interface pointer를 통해서 가능합니다. 예를 들어 java.lang.String 은 native code로 전달되며 jstring 으로 매핑되는데, string 의 내용물에 접근하기 위해서는 GetStringUTFChars 등을 통해야 변환 후에 해야 합니다.




Accessing Strings


- jstring 은 Java VM 의 string으로 C 언어에서의 일반적인 string type 인 char* 와 다릅니다. 그래서 jstring 을 일반적인 C string 처럼 쓸 수 없습니다. 예를 들어 다음과 같은 코드는 Java VM 의 crash 를 초래합니다.

JNIEXPORT jstring JNICALL

Java_HelloJNI_getEchoString( JNIEnv *env, jobject obj, jstring echoString ){

   /* ERROR : incorrect use of jstring as a char* pointer */

   printf( "%s", echoString );

}

따라서 jstring 을 C코드에서 쓰기 위해서는 적당한 변환이 필요합니다. JNI 는 Unicode 와 UTF-8 format 을 지원합니다. Unicode 는 16-bit 으로 string을 표현하는 것이며, UTF-8 은 7-bit ASCII string과 호환되는 녀석으로 encoding을 사용합니다.( ASCII 코드는 UTF-8 과 같은데, UTF-8 은 더 많은 code를 가지고 있습니다. ) UTF-8 string 은 NULL_terminated C string 처럼 작동합니다. 


- GetStringChars와 GetStringUTFChars 의 return value 는 반드시 확인해주어야 합니다. 왜냐하면 JVM 은 이 string 들을 위해 memory 할당 작업을 거치는데, 이 때 memory allocation 이 실패할 수 있습니다. 할당 실패시에 저 함수는 NULL 을 return 하며 OutOfMemoryError 를 내뿜죠. 나중에 다뤄지겠지만, JNI 에서의 exception throwing 은 자바의 exception throwing 과는 다릅니다. return 이 발생한 다음에서야 caller function 에 exception 이 전달됩니다. 


- GetStringChars나 GetStringUTFChars 를 통해 얻어진 string을 더 이상 쓰지 않는다면 ReleaseStringChrs나 ReleaseStringUTFChars 를 불러주어 얻은 메모리를 해제해주어야 합니다.


- JNI 함수를 통해 새로운 String 을 만들 수도 있습니다. NewString이나 NewStringUTF 를 통해서 말이죠. NewStringUTF 함수는 UTF-8 format 으로 된 C 스트링을 매개변수로 받아서 java.lang.String 을 만듭니다. 만약 java.lang.String 을 위한 메모리 확보에 실패하면, OutOfMemoryError 를 던지고, NULL 을 return 합니다. 





-UTF-8 string 은 항상 \0 으로 끝납니다. Unicode 는 안 그렇고요. UniCode jstring 의 char 수를 구하려면 GetStringLength 를 호출합니다. UTF-8 jstring 의 byte를 구하려면, GetStringUTFChars 를 통해 얻어진 결과에 strlen 을 적용하던가 jstring에 바로 GetStringUTFLength 를 적용하면 됩니다.


- GetStringChars 나 GetStringUTFChars 는 3번째 파라미터로 jboolean *isCopy 를 받는데 copy 본으로 얻어올지 참조값을 그대로 받아올지를 결정하는 것이다. isCopy 가 false 일경우는 original pointer 를 가져오는 것이기 때문에 수정을 안 하는 것이 좋습니다. ( 의도적인 수정이라면 괜찮겠지만요 ). NULL 을 대입하는 경우가 보통인데 이는 copy 이던 아니던 상관 없다는 의미입니다. GetStringChars 나 GetStringUTFChars 를 사용한 경우, ReleaseStringChars 나 ReleaseStringUTFChars 를 호출해주어야 합니다.


- SDK 1.2 부터 java.lang.String 의 direct pointer 를 return 하는 방법을 효율적으로 바꾼 Get/ReleaseStringCritical 을 제공합니다. Get/ReleaseStringChars 와 비슷한 기능을 하지만 다른 점은 GetStringCritical 과 ReleaseStringCritical 사이를 Critical region 이라고 하여 이 안에서는 GC에 발생시키지 않습니다. GC가 발생하지 않으면, 다른 GC를 발생시키는 함수들도 block 이 됩니다. 따라서 thread 가 block 될 수 있는 JNI 함수들을 사용해서는 안되며, GC를 발생시킬 수 있는 상황도 피해야 합니다. 예를 들면 새로운 new object 를 allocate 하는 것도 피해야 합니다. main thread에서 다른 thread 에 대해 wait 를 걸었는데 다른 thread 에서 GC 를 유발하면 deadlock 이 걸립니다. GetStringCritical 과 ReleaseStringCritical 사이에서 쓸 수 있는 JNI 함수는 Get/ReleaseStringCritical 과 Get/ReleasePrimitiveArrayCritical 뿐입니다.


- JNI 는 Get/ReleaseStringUTFCritical 를 지원하지 않습니다. 왜냐하면 주로 VM 은 Unicode 를 사용하기 때문에 Get/ReleaseStringUTFCritical 호출은 memory copy 를 유발하기 때문입니다.


- SDK 1.2 에서 추가 지원되는 함수는 GetStringRegion 과 GetStringUTFReion 입니다. 이 함수들은 preallocated buffer 에 string 을 copy 합니다.


<String 관련 JNI 함수들>

GetStringChars, ReleaseStringChars

- String 을 Unicode format pointer로 얻거나 release 한다.


GetStringUTFChars, ReleaseStringUTFChars

- UTF-8 format 으로 string pointer 를 얻거나 release 한다.


GetStringLength

- Unicode string length return


GetStringUTFLength

- UTF-8 format string 의 byte 를 얻는다.


NewString

- java.lnag.Strig instance 를 생성한다. Unicde로 생성된다.


NewStringUTF

- java.lang.String instance 를 생성한다. UTF-8 c string 이다.


GetStringCritical, ReleaseStringCritical

- Unicode format 으로 string pointer 를 얻거나 release 한다. native code 는 Get/ReleaseStringCritical 사이에 block 되서는 절대 안된다.


GetStringRegion, SetStringRegion

- C buffer 에 unicode 로 미리 allocate 되어 있는 string의 copy 본을 얻는다.


GetStringUTFRegion, SetStringUTFRegion

- C buffer 에 UTF-8 format 으로 미리 allocate 되어 있는 string 의 copy본을 얻는다.

 


 

- 대부분이 1.2 이상이라는 가정 하에 small & fixed-size string 에 대해서는 Get/SetStringRegion 이나 Get/SetStringUTFRegion 을 쓰는 것이 좋습니다. 왜냐면 C buffer 는 C stack 에 매우 가볍게 할당할 수 있으며, copy 에 대한 overhead 도 무시할만큼 작기 때문힙니다. 이것들은 새로운 메모리 할당을 하지 않기 때문에 OutOfMemoryErorr를 발생시키지 않습니다.






Accessing Arrays


- primitive array 들은 jintArray, jfloatArray 등과 같이 j<type>Array 형태로 mapping 된다. C native 에서는 string에서와 같이 Java  형태의 type 을 C형태의 type으로 변환한 후 사용해야 합니다.

JNIEXPORT jint JNICALL

Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr ){

   jint buf[10];

   jint i, sum = 0;

   (*env)->GetIntArrayRegion( env, arr, 0, 10, buf );

   for ( i=0; i < 10; i++ ){

      sum += buf[i];

   }

   return sum;

}


- JNI 는 SetIntArrayRegion 도 지원하는데, 이는 native code 가 jArray에 값을 assign 하도록 하는 함수입니다.


- JNI에서는 Get/Release<Type>ArrayElements 함수를 제공하는데 이는 primitive array element 에 대한 direct pointer 를 제공합니다.


JNIEXPORT jint JNICALL

Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr ){

   jint *carr;

   jint i, sum = 0;

   carr = (*env)->GetIntArrayElements( env, arr, NULL );

   if (carr == NULL )

      return 0;

   for ( i=0; i < 10; i++ ){

      sum += carr[i];

   }

   (*env)->ReleaseIntArrayElements( env, arr, carr, 0 );

   return sum;

}


- GetArrayLength 는 primitive 나 object array 의 length 를 return 합니다.


- SDK 1.2 부터는 Get/ReleasePrimitiveArrayCritical 을 제공합니다. 이는 VM 이 primitive array 를 연결할때 GC를 하지 못하도록 합니다. Get/ReleaseStringCritical 과 같은 주의사항이 요구됩니다.


<Primitive Array 관련 JNI 함수들>

Get/Set<Type>ArrayRegion

- primitive arrays 를 pre-allocated C buffer 에 복사하든가, 아니면 반대로 C buffer 내용을 primitive array 로 복사하는 작업


Get/Release<Type>ArrayElements

- primitive array 에 대한 pointer 를 얻어오고, 이에 대한 link 를 해제한다.


GetArrayLength

- array element 의 수를 얻어온다.


New<Type>Array

- 주어진 length 에 맞는 새로운 array 를 만든다.


Get/ReleasePrimitiveArrayCritical

- primitive array 의 pointer 를 얻어오거나 release 하는데 critical region 안에서는 GC 가 disable 된다.



- 보통은 Get/Set<Type>ArrayRegion 을 사용합니다. String와 마찬가지로 small & fixed size array 의 경우는 이 함수들이 C stack 을 이용하기 때문에 매우 빠르며, overhead 도 무시할만합니다.


- Pre-allocated C buffer 가 없거나, size 가 정해지지 않았거나, native code 가 blocking call 을 하지 않을 수 있다면 Get/ReleasePrimitiveArrayCritical 이 유용합니다. Get/ReleaseStringCritical 처럼 deadlock 을 유발할 수 있는 JNI function call 은 피해야 합니다.


- Get/Release<type>ArrayElements 는 항상 안전합니다.







Accessing Arrays of Objects


object array 는 primitive array 와는 다른 방식으로 접근합니다. Get/SetObjectArrayElement가 주로 사용됩니다.


JNIEXPORT jobjectArray JNICALL

Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size ){

   jobjectArray result

   int i;

   jclass intArrCls = (*env)->FindClass( env, "[I" );

   if ( intArrCls == NULL ){

      return NULL;

   }


   result = (*env)->NewObjectArray( env, size, intArrCls, NULL );

   if( result == NULL ){

      return NULL;

   }

   for ( i=0; i < size; i++ ){

      jint tmp[256];

      int j;

      jintArray iarr = (*env)->NewIntArray( env, size );

      if (iarr == NULL )

         return NULL;

      for( j=0; j<size; j++ ){

         tmp[j] = i+j;

      }

      (*env)->SetIntArrayRegion( env, iarr, 0, size, tmp );

      (*env)->SetObjectArrayElement( env, result, i, iarr );

      (*env)->DeleteLocalRef( env, iarr );

   }

   return result;

}


- FindClass ( "[I" ) 는 int[] object 에 matching 되는 class 를 얻어옵니다. FindClass 가 해당 class loading 에 실패하면 NULL 을 return 합니다. ( missing class file case or OutOfMemory case ).


- NewObjectArray 함수는 3번째 argument 에 입력되는 intArrCls 를 element로 갖는 array 를 생성합니다. NewObjectArray 함수는 1차원 array만 allocate합니다. 2차원을 만드는 것은 NewIntArray 를 통해서 다시 int array 를 만든 후 먼저 만든 array 에 element 로 입력해주면 됩니다. SetIntArrayRegion 은 tmp[] buffer 내용을 1차원 array 에 할당하는 함수입니다. SetObjectArrayElement 를 통해 i번째 reference 로 SetIntArrayRegion으로 반든 1차원 array iarr 를 할당합니다.


- DeleteLocalRef 는 local reference 들을 해제시켜 줌으로써 OutOfMemoryError 를 예방합니다.










반응형

댓글