안녕하세요 돼지왕왕돼지입니다.
오늘은 JNI 중 Fields 과 Methods 에 관한 내용을 알아보겠습니다.
http://java.sun.com/docs/books/jni/html/fldmeth.html#11202 글을 번역 및 요약한 내용입니다.
Fields anbd Methods
Accessing Fields
Procedure for Accessing an Instance Field
fid = (*env)->GetFieldID( env, cls, "s", "Ljava/lang/String;" );
jstr = (*env)->GetObjectField( env, obj, fid );
Field Descriptors.
javap -s -p InstanceFieldAccess
<Result>
...
s Ljava/lang/String
...
Accessing Static Fields.
Calling Methods
Calling Instance Methods
<Thread instance의 Runnable.run 실행시키기>
findClass 를 통해서 interface 도 얻어올 수 있습니다.
Forming the Method Descriptor
예를 들어 String을 argument 로 받고, String 을 return하는 method 가 있다면 다음과 같은 descriptor 를 사용합니다.
(Ljava/lang/String;)Ljava/lang/String;
array 관련된 descriptor 예를 들어보겠습니다. String[] 를 argument 로 받고, void return 이라면 다음과 같은 descriptor를 사용합니다.
([java/lang/String;)V
field 와 같이 javap tool 을 사용하면, JNI method descriptor 를 얻을 수 있습니다.
Calling Static Methods
Instance Method 를 호출하듯 Static Method도 호출할 수 있습니다. 다음의 두가지 step 을 통해 호출합니다.
1. GetMethodID 대신 GetStaticMethodID 를 통해 methodID 를 얻어옵니다.
2. class, methodID, 그리고 arguments 를 CallStatic<Type>Method 를 통해서 호출합니다.
Instance Method 와 Static Method 를 호출할 때 주의해야 할 것은, Instance Method Call은 두번째 argument 로 object reference 를 전달하는 반면, Static Method Call 할 떄는 두번째 argument 로 class reference 를 전달합니다. ( object reference 를 통해서도 호출할 수 있다고는 합니다. )
JNIEXPORT void JNICALL
Java_StaticMethodCall_nativeMethod( JNIEnv *env, jobject obj ){
jclass cls = (*env)->GetObjectClass( env, obj );
jmethodID mid = (*env)->GetStaticMethodID( env, cls, "callback", "()V" );
if ( mid == NULL )
return; /* method not found */
printf("In C\n");
(*env)->CallStaticVoidMethod( env, cls, mid );
}
Calling Instance Methods of a Superclass
Superclass 에 정의되어 있지만, subclass 에는 override 되지 않은 Instance method 를 호출하는 방법이 있습니다. JNI는 CallNonvirtual<Type>Method 함수를 이 용도로 제공합니다. 다음과 같은 step 을 통해 호출 가능합니다.
1. superclass 의 reference 를 GetMethodID 에 전달하여 methodID 를 얻어옵니다.
2. object, superclass, methodID 를 CallNoncirtual<Type>Method 에 전달하여 호출합니다.
Invoking Constructors.
Constructor 호출은 instance method 호출과 매우 비슷한 방법으로 호출합니다. methodID 를 얻기 위해서 method 이름으로 "<init>" 를 전달하고, method descriptor 의 return type은 "V" 를 사용합니다. NewObject 와 같은 JNI function 에 methodID 를 전달하여 constructor 를 호출할 수 있습니다.
jstring
MyNewString( JNIEnv *env, jchar *chars, jint len ){
jclass stringClass;
jmethodID cid;
jcharArray elemArr;
jstring result;
stringClass = (*env)->FindClass( env, "java/lang/String" );
if ( stringClass == NULL )
return NULL; /* exception thrown */
/* Get the methodID for the String(char[]) constructor */
cid = (*env)->GetMethodID( env, stringClass, "<init>", "([C)V" );
if ( cid == NULL )
return NULL;
/* Create a char[] that holds the string characters */
elemArr = (*env)->NewCharArray( env, len );
if ( elemArr == NULL )
return NULL; /* exception thrown */
(*env)->SetCharArrayRegion( env, elemArr, 0, len, chars );
/* Construct a java.lang.String object */
result = (*env)->NewObject( env, stringClass, cid, elemArr );
/* Free local references */
(*env)->DeleteLocalRef( env, elemArr );
(*env)->DeleteLocalRef( env, stringClass );
return result;
}
NewObject 함수는 Constructor 를 호출하는 데 사용됩니다. argument 로 class reference 와 methodID, 그리고 constructor 에 필요한 argument 들을 전달합니다.
JNI 는 NewString 과 같은 built-in 함수들을 제공합니다. 이것들은 빈번히 쓰이는 녀석들이며, Java의 String class 를 부르는 것보다 훨씬 효과적이라 좋습니다.
CallNonvirtual<Type>Method 함수를 이용해서도 constructor 를 호출할 수 있습니다. 이 경우에는 먼저 AllocObject 함수를 통해 uninitialized object 를 먼저 만들고, 그 다음에 CallNonvirtual<Type>Method 를 사용해야 합니다.
result = (*env)->AllocObject( env, stringClass );
if ( result ){
(*env)->CallNonvirtualVoidMethod( env, result, stringClass, cid, elemArr );
/* We need to check for possible exceptions */
if ( (*env)->ExceptionCheck( env ) ){
(*env)->DeleteLocalRef( env, result );
result = NULL;
}
}
AllocObject 는 uninitialized object 를 만듭니다. 그리고 각 object 에 대해 constructor 는 반드시 한번만 불려야 합니다. 가끔은 AllocObject 를 이용하여 uninitialized object 를 먼저 만들고, Constructor 를 호출해야 더 유리한 경우도 있지만, 대부분은 NewObject 를 통해 Constructor 를 호출하는 것이 에러율도 적고 좋습니다.
Caching Field and Method IDs
fieldID 나 methodID 를 얻어오는 것은 이름과 descriptor 를 이용한 검색을 통합니다. 이런것을 symbolic lookup 이라고 하는데 symbolic lookup 은 매우 무거운 operation 입니다. 이 섹션에서는 어떻게 이 overhead 를 줄일 수 있는지 살펴봅니다.
overhead 를 줄이는 방법은 fieldID 와 methodID 의 cache 를 떠 놓고, 나중에 다시 이용하는 것입니다. cache 를 하는 방법은 2가지가 있습니다. fieldID 나 methodID 가 필요하여 사용하는 순간에 cache 를 하는 것이 있고, class의 static initializer에서 미리 fieldID와 methodID를 cache 하는 것입니다.
Caching at the Point of Use
다음은 fieldID 를 static variable 에 cache 를 해놓는 예제입니다.
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField( JNIEnv *env, jobject obj ){
static jfieldID fid_s = NULL; /* cached field ID for s */
jclass cls = (*env)->GetObjectClass( env, obj );
jstring jstr;
const char *str;
if ( fid_s == NULL ){
fid_s = (*env)->GetFieldID( env, cls "s", "Ljava/lang/String;" );
if ( fid_s == NULL )
return; /* exception already thrown */
}
printf( "In C:\n");
jstr = (*env)->GetObjectField( env, obj, fid_s );
str = (*env) ->GetStringUTFChars( env, jstr, NULL );
if ( str == NULL )
return; /* out of memory */
printf( " c.s = \"%s\"\n", str );
(*env)->ReleaseStringUTFChars( env, jstr, str );
jstr = (*env)->NewStringUTF( env, "123" );
if ( jstr == NULL )
return; /* out of memory */
(*env)->SetObjectField( env, obj, fid_s, jstr );
}
다음은 methodID 를 cache 하는 예제입니다.
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len){
jclass stringClass;
jcharArray elemArr;
static jmethodID cid = NULL;
jstring result;
stringClass = (*env)->FindClass(env, "java/lang/String");
if (stringClass == NULL)
return NULL; /* exception thrown */
/* Note that cid is a static variable */
if (cid == NULL) {
/* Get the method ID for the String constructor */
cid = (*env)->GetMethodID(env, stringClass, "<init>", "([C)V");
if (cid == NULL)
return NULL; /* exception thrown */
}
/* Create a char[] that holds the string characters */
elemArr = (*env)->NewCharArray(env, len);
if (elemArr == NULL)
return NULL; /* exception thrown */
(*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);
/* Construct a java.lang.String object */
result = (*env)->NewObject(env, stringClass, cid, elemArr);
/* Free local references */
(*env)->DeleteLocalRef(env, elemArr);
(*env)->DeleteLocalRef(env, stringClass);
return result;
}
Caching in the Defining Class's Initializer
사용순간에 cache 를 하는 경우는 사용시점에 cache 가 되어 있는지 항상 확인해야 합니다. 이 경우는 작은 performance 문제를 야기할 수도 있고, multi-thread 에서 사용하는 경우에는 여러 번의 cache 와 checking 이 발생할 수도 있습니다.
그래서 보통의 경우에 fieldID 와 methodID 를 미리 cache 해 놓는 것이 좋습니다. 미리 cache 를 하는 point는 static initializer 가 좋습니다. 예를 들면 다음과 같이 말이죠.
class InstanceMethodCall{
private static native void initIDs();
private native void nativeMethod();
private void callback(){
System.out.println("In Java");
}
public static void main( String args[] ){
InstanceMethodCall c = new InstanceMethodCall();
c.nativeMethod();
}
static{
System.loadLibrary("InstanceMethodCall");
initIDs();
}
}
Comparison between the Two Approaches to Caching IDs
Original Java Code 에 대한 control 을 가지고 있다면, 사용 순간에 cache 하는 것이 좋은 방안입니다. 하지만 그 외의 경우 사용 시점에 cache 를 하게 되면 다음과 같은 단점이 있습니다.
1. fieldID 나 methodID 를 사용하는 순간마다 cache 여부를 확인하고, cache 를 해야 합니다.
2. fieldID 와 methodID 가 class가 unload 되기 전까지만 유효합니다. 사용할 당시에 cache 를 하게 되면, 프로그래머는 "반드시" cachedID 를 사용하는 동안에는 class 가 unload 및 reload 되지 않도록 확실히 해주어야 합니다. 반면 static initializer 를 통해 cache가 된 경우는 cached ID 가 unloaded 및 reload 가 될 때 자동으로 re cache 됩니다.
Performance of JNI Field and Method Operations.
JNI 를 사용함에 있어 performance 는 JVM 이 JNI 를 어떻게 구현했느냐에 따라 확연히 달라집니다. 그래서 절대적인 performance 에 대해서는 말할 수 없지만, 개략적이며 공통적인 performance 에 대해서는 이야기 할 수 있습니다. 일반적으로 Java에서 native 함수를 호출하는 것이 Java에서 Java 함수를 호출하는 것보다 다음의 이유로 더 느립니다.
1. Native method 는 대부분 Java에서 Java 함수를 호출하는 것과는 다른 calling convention 을 사용합니다. 그래서 JVM 은 native method 의 entry point 로 진입하기 전에 argument 를 만들고, stack frame 을 만드는 추가 작업들이 필요합니다.
2. VM 은 inline method call 이 일반적입니다. 하지만 Java에서 native 를 호출하는 것은 inline 화 하는 것이 어렵습니다.
대략적으로 Java/Native call 은 Java/Java call 에 비해 2~3배정도 느립니다. 물론 VM 을 어떻게 구현하느냐에 따라 Java/Native call 과 Java/Java call 의 performance 가 거의 동일하도록 할 수도 있습니다.
native/Java call 은 Java/native call 과 그 performance 가 매우 비슷합니다. 대부분 native/Java call 은 빈번하지 않기 떄문에 VM 을 구현할 때 이 부분의 optimize 는 많이 배제하는 편입니다. 그래서 많은 native/Java call 은 Java/Java call 에 비해 10배정도 느리곤 합니다.
JNI 에서 field 에 접근하는 것은 JNIEnv 를 통해 call 한다는 데 달려 있습니다. 이 녀석도 사실 조금 느리기는 하겠지만, function call 에 비해서는 매우 사소하기 때문에 무시할만합니다.
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
[Java] JNI Tutorial - Local and Global References (0) | 2012.03.30 |
---|---|
[Android/안드로이드] Unity3D 를 이용하여 안드로이드 게임 만들기. (2) | 2012.03.28 |
[Android/안드로이드] 32bit machine 에서 GingerBread or ICS build 하기. (0) | 2012.03.27 |
[Android/안드로이드] 64bit Ubuntu Android ICS Source 다운받기 (0) | 2012.03.27 |
[Android/안드로이드] Ubuntu에서 E: Package 'lib32readline5-dev' has no installation candidate. 발생시 대처 방법. (0) | 2012.03.26 |
댓글