◎위챗 : speedseoul
https://cafe.naver.com/sgprogramingcenter/43
http://freeair-textcube.blogspot.com/2010/02/jnijava-native-interface-2%ED%8E%B8.html
자바 객체의 사용
네이티브 코드 내에서 자바 객체를 사용할 수도 있다. 즉 콜백과 비슷한 형식을 지원한다. private영역까지 접근할 수 있다.
자바 객체의 클래스 정보 얻기
일단, 인자로 넘겨지는 자바 객체에 대한 클래스 정보를 획득하는 함수가 제공된다.
<p>jclass cls = (*env)->GetObjectClass(env, jobj);</p>
여기서 주의할 것은 반환값으로 돌아오는 cls의 레퍼런스값은 오직 이 네이티브 메쏘드가 수행이 끝날 동안에만 유효하다는 것이다.
자바 객체 및 클래스 타입의 래퍼런스는 네이티브 메쏘드 실행시마다 결정되므로, 한 번 실행해 얻은 레퍼런스를 다음번 메쏘드에서 다시 사용하려는 것은 잘못된 것이다.
따라서, 원칙적으로 객체 레퍼런스 변수를 전역 변수로 할당하는 것은 위험한 일이다. 하지만, 꼭 허용되지 않는 것은 아니다. 전역 레퍼런스로 지정해 줄 수 있는 함수가 있다.
<p> jobject NewGlobalRef(JNI_Env* env, jobject lobj) </p>
네이티브 코드를 호출한 객체의 메쏘드에 대한 ID얻기
객체의 메쏘드를 호출하기 위해 그 메쏘드의 ID를 획득해야 한다. 이것은 메쏘드의 이름과 시그너쳐를 이용하는데, 이름은 별 무리가 없겠으나, 시그너쳐는 까다롭다.
시그너쳐는 메쏘드의 인자 리스트와 반환 타입 정보를 스트링화한 것으로, 다음과 같은 규칙이 있다.
시그너쳐 : "(인자 리스트)반환값"으로 이루어진 스트링이다.
인자리스트의 인자들은 아래의 타입 기호에 따라 표기되며, 서로 ';'으로 구분된다.
반환값 역시 타입 기호에 따라 표시된다.
타입 기호는 다음과 같다. 이것은 자바 가상 머신 스펙에서 클래스 파일 포맷 부분에 등장하는 기호와 동일하다.
<p>Z : boolean
B : byte
C : char
S : short
I : int
J : long
F : float
D : double </p>
이들의 배열은 이들의 타입 기호 앞에 '['를 붙인다. 즉 int 배열은 [I가 된다.
클래스 타입의 경우, L뒤에 클래스명을 패키지명까지 포함해 나열하면 된다. 즉, String클래스인 경우, Ljava.lang.String이다.
이것은 수작업으로 할 경우, 실수가 많을 수 있으나, javap를 이용해, 쉽게 기호화된 시그너쳐를 확인할 수 있다.
<p>javap -s -p Hello</p><p>Compiled from Hello.java
class Hello extends java.lang.Object {
static {};
/* ()V */
Hello();
/* ()V */
public static void coo(java.lang.String, java.lang.String);
/* (Ljava/lang/String;Ljava/lang/String;)V */
private void foo(java.lang.String, java.lang.String);
/* (Ljava/lang/String;Ljava/lang/String;)V */
public native boolean getBoolean(boolean);
/* (Z)Z */
public native byte getByte(byte);
/* (B)B */
public native char getChar(char);
/* (C)C */
public native double getDouble(double);
/* (D)D */
public native float getFloat(float);
/* (F)F */
public native java.lang.String getHello(java.lang.String);
/* (Ljava/lang/String;)Ljava/lang/String; */
public native int getInt(int);
/* (I)I */
public native int getIntArray(int[])[];
/* ([I)[I */
public native long getLong(long);
/* (J)J */
public native short getShort(short);
/* (S)S */
public static void main(java.lang.String[]);
/* ([Ljava/lang/String;)V */
}</p>
이것은 다음과 같이 Hello클래스에 두 메쏘드를 추가한 후의 javap결과이다.
<p> private void foo(String str,String str1) {
System.out.println("Hello.foo() = "+str+str1);
}</p><p> public static void coo(String str,String str1) {
System.out.println("Hello.coo() = "+str+str1);
}</p>
이제 foo메쏘드의 ID를 획득해 보자.
// 클래스 정보 획득
jclass cls = (*env)->GetObjectClass(env, obj);
// 메쏘드 ID
jmethodID fooM = (*env)->GetMethodID(env, cls, "foo", "(Ljava/lang/String;Ljava/lang/String;)V");
// 그런 메쏘드가 없으면, 0반환
if(fooM == 0) {
printf("Method foo isn't found\n");
}
else {
......
}
<p> </p>
인자 생성 및 메쏘드 호출
이제 메쏘드를 호출하는 일이 다음 순서가 되겠다. 물론 여기에 해당되는 함수들이 제공되는데, 이 함수들을 통해, 클래스 내의 메쏘드를 호출하므로, 메쏘드 ID와 더불어 메쏘드의 인자들을 리스트화해서 보내야 한다. 두 가지 방법이 있다.
C의 va_list구조체를 이용한다.
jvalue 유니온를 이용한다.
여기서는 jvalue 유니온을 이용해 보자. 그 구조는 다음과 같다.
<p>typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;</p>
그러면, 스트링 두 개를 인자로 받는 foo메쏘드를 호출해 보자. String은 객체이므로, l에 스트링 객체의 레퍼런스를 할당하면 된다.
<p>jvalue* args;
.......
args = (jvalue*)malloc(sizeof(jvalue)*2);
args[0].l = (*env)->NewStringUTF(env,"oops");
args[1].l = (*env)->NewStringUTF(env,"la");
(*env)->CallVoidMethodA(env, obj, fooM,args ); // JNIEnv, jobject, Method ID, jvalue</p>
여기에서는 foo가 void 타입이므로, CallVoidMethodA가 쓰였지만, 이 외에에도 반환값에 따라 여러 함수가 제공된다. 이들의 인자는 위와 동일하다.
jboolean CallBooleanMethodA
jbyte CallByteMethodA
jchar CallCharMethodA
jshort CallShortMethodA
jint CallIntMethodA
jfloat CallFloatMethodA
jdouble CallDoubleMethodA
참고로, va_list를 이용하여 인자를 전달하는 함수들은 A로 끝나지 않고, V로 끝나는데, 역시 마지막 인자만 va_list타입으로 수정해 주면 된다.
정적 메쏘드 호출
정적 메쏘드 또한 호출할 수 있는 함수가 따로 있다.
메쏘드 ID : GetStaticMethodID함수 이용
<p>jMethodID cooM = (*env)->GetStaticMethodID(env, cls, "coo", "(Ljava/lang/String;Ljava/lang/String;)V");
if(cooM == 0) {
printf("Method foo isn't found\n");
}
else { .....
} </p>
인자 형성 방법은 동일하다.
메쏘드 호출 : CallStaticVoidMethodA 이용
<p>cooM = (*env)->GetStaticMethodID(env, cls, "coo", "(Ljava/lang/String;Ljava/lang/String;)V");
if(cooM == 0) {
printf("Method foo isn't found\n");
}
else {
args = (jvalue*)malloc(sizeof(jvalue)*2);
args[0].l = (*env)->NewStringUTF(env,"papa");
args[1].l = (*env)->NewStringUTF(env,"ya");
(*env)->CallStaticVoidMethodA(env, cls, cooM,args );
}</p>
여기서 주의할 것은 CallStaticVoidMethodA의 두 번째 인자는 jobject타입이 아닌, jclass타입이란 것이다. 클래스 메쏘드이니 당연하기도 하다.
상위 클래스의 메쏘드 호출
한 걸음 더 나아가 상위 클래스의 메쏘드도 호출할 수 있다. 여기에 해당되는 메쏘드는 모두 NonVirtual이라는 이름을 포함하고 있는데, c++을 배운 이라면, NonVirtual의 의미를 알고 있을 것이다.
상위 클래스 타입의 jclass 객체 획득
<p>jclass sClass = (*env)->GetSuperclass(env,cls);</p>
메쏘드 ID : GetMethodID함수 이용
<p>superFooM = (*env)->GetMethodID(env, sClass, "foo", "(Ljava/lang/String;Ljava/lang/String;)V");
if(superFooM == 0) {
printf("Method foo isn't found\n");
}
else {
.....
}</p>
메쏘드를 호출: CallNonvirtualVoidMethodA
<p>args = (jvalue*)malloc(sizeof(jvalue)*2);
args[0].l = (*env)->NewStringUTF(env,"can");
args[1].l = (*env)->NewStringUTF(env,"dy");
(*env)->CallNonvirtualVoidMethodA(env,obj,sClass ,superFooM,args );
</p>
멤버 필드 접근
필드를 접근하는 방법 또한 메쏘드를 접근하는 방법과 크게 다르지 않다. 필드가 Private일지라도 접근할 수 있다. 이 또한 몇 가지 함수에 의존하고 있는데, 그 함수의 형식이 메쏘드를 호출하는 함수와 거의 유사하기 때문이다. 일단 다음의 기호를 잊지 말자.
<p>Z : boolean
B : byte
C : char
S : short
I : int
J : long
F : float
D : double </p>
이들의 배열은 이들의 타입 기호 앞에 '['를 붙인다. 즉 int 배열은 [I가 된다.
클래스 타입의 경우, L뒤에 클래스명을 패키지명까지 포함해 나열하면 된다. 즉, String클래스인 경우, Ljava.lang.String이다.
이제 Hello.java에 몇 가지 필드를 추가해 보자.
private int intVal;
private static int iStaticIntVal;
private String strVal = "Hello";
이를 재컴파일 후, javah를 실행하고, 새로이 생긴 함수의 선언부를 HelloImpl.c로 복사해 이것에 대해 추가적으로 구현해 보기로 하자.
필드를 접근하는 것은 두 단계로 이루어진다.
클래스와 필드의 이름과 타입을 이용해 필드의 ID를 얻어낸다.
필드의 ID와 객체를 이용해 실제 필드의 값을 얻어낸다.
인스턴스 필드 접근
먼저 필드의 ID를 얻어내기 위한 함수는 다음과 같다.
<p> jfieldID GetFieldID(JNI_Env* env,jclass clazz, const char *name, const char *sig)
</p>
그리고, 인스턴스 필드를 접근하는 함수들은 다음과 같다.
1) Read 함수
jobject GetObjectField(JNI_Env* env,jobject obj, jfieldID fieldID)
jboolean GetBooleanField(JNI_Env* env,jobject obj, jfieldID fieldID)
jbyte GetByteField(JNI_Env* env,jobject obj, jfieldID fieldID)
jchar GetCharField(JNI_Env* env,jobject obj, jfieldID fieldID)
jshort GetShortField(JNI_Env* env,jobject obj, jfieldID fieldID)
jint GetIntField(JNI_Env* env,jobject obj, jfieldID fieldID)
jlong GetLongField(JNI_Env* env,jobject obj, jfieldID fieldID)
jfloat GetFloatField(JNI_Env* env,jobject obj, jfieldID fieldID)
jdouble GetDoubleField(JNI_Env* env,jobject obj, jfieldID fieldID)
2) Write 함수
void SetObjectField(JNI_Env* env,jobject obj, jfieldID fieldID, jobject val)
void SetBooleanField(JNI_Env* env,jobject obj, jfieldID fieldID, jboolean val)
void SetByteField(JNI_Env* env,jobject obj, jfieldID fieldID,jbyte val)
void SetCharField(JNI_Env* env,jobject obj, jfieldID fieldID, jchar val)
void SetShortField(JNI_Env* env,jobject obj, jfieldID fieldID,jshort val)
void SetIntField(JNI_Env* env,jobject obj, jfieldID fieldID,jint val)
void SetLongField(JNI_Env* env,jobject obj, jfieldID fieldID, jlong val)
void SetFloatField(JNI_Env* env,jobject obj, jfieldID fieldID, jfloat val)
void SetDoubleField(JNI_Env* env,jobject obj, jfieldID fieldID, jdouble val)
클래스 필드 접근
Static 필드를 접근하기 위해 먼저 ID를 얻어내야 한다.
<p>jfieldID GetStaticFieldID(JNI_Env* env,jclass clazz, const char *name, const char *sig)</p>
그리고, 필드를 접근하는 함수들은 다음과 같다.
Gettor 함수
jobject GetStaticObjectField(JNI_Env* env,jclass clazz, jfieldID fieldID)
jboolean GetStaticBooleanField(JNI_Env* env,jclass clazz, jfieldID fieldID)
jbyte GetStaticByteField(JNI_Env* env,jclass clazz, jfieldID fieldID)
jchar GetStaticCharField(JNI_Env* env,jclass clazz, jfieldID fieldID)
jshort GetStaticShortField(JNI_Env* env,jclass clazz, jfieldID fieldID)
jint GetStaticIntField(JNI_Env* env,jclass clazz, jfieldID fieldID)
jlong GetStaticLongField(JNI_Env* env,jclass clazz, jfieldID fieldID)
jfloat GetStaticFloatField(JNI_Env* env,jclass clazz, jfieldID fieldID)
jdouble GetStaticDoubleField(JNI_Env* env,jclass clazz, jfieldID fieldID)
Settor 함수
void SetStaticObjectField(JNI_Env* env,jclass clazz, jfieldID fieldID, jobject value)
void SetStaticBooleanField(JNI_Env* env,jclass clazz, jfieldID fieldID, jint value)
void SetStaticByteField(JNI_Env* env,jclass clazz, jfieldID fieldID, jbyte value)
void SetStaticCharField(JNI_Env* env,jclass clazz, jfieldID fieldID, jchar value)
void SetStaticShortField(JNI_Env* env,jclass clazz, jfieldID fieldID, jshort value)
void SetStaticIntField(JNI_Env* env,jclass clazz, jfieldID fieldID, jint value)
void SetStaticLongField(JNI_Env* env,jclass clazz, jfieldID fieldID, jlong value)
void SetStaticFloatField(JNI_Env* env,jclass clazz, jfieldID fieldID, jfloat value)
void SetStaticDoubleField(JNI_Env* env,jclass clazz, jfieldID fieldID, jdouble value)
예제
이제 이들을 사용해 Hello 클래스의 필드들을 접근해 보기로 하자. Hello.java에 native메쏘드인 다음을 추가하고 컴파일 후, HelloImpl.c에서, 다음을 중심으로 코딩해 보자.
<p>public native void testFieldAccess()</p>
정적 변수 접근
정적 필드인 iStaticIntVal 필드를 접근하여, 값을 읽어들인 후, 다시 2를 더해 값을 써 보자.
<p>jfieldID jFieldId;
jint iStaticIntVal;
jclass cls = (*env)->GetObjectClass(env, jobj);
jFieldId = (*env)->GetStaticFieldID(env,cls,"iStaticIntVal","I");
if(jFieldId == 0) {
printf("Field iStaticIntVal not Found in Hello class\n");
return;
}
// 값을 읽어들인 후,
iStaticIntVal = (*env)->GetStaticIntField(env,cls,jFieldId);
// 2를 더해, 다시 그 값을 쓴다.
(*env)->SetStaticIntField(env,cls,jFieldId,(iStaticIntVal+2)); </p>
인스턴스 변수 접근
<p>jint iVal;
jfieldID jFieldId;
jclass cls = (*env)->GetObjectClass(env, jobj);
jFieldId = (*env)->GetFieldID(env,cls,"intVal","I");
if(jFieldId == 0) {
printf("Field intVal not Found in Hello class\n");
return;
}
iVal = (*env)->GetIntField(env,jobj,jFieldId);
printf("iVal in C = %d\n",iVal);
(*env)->SetIntField(env,jobj,jFieldId,iVal);</p>
그런데, 실제로 int를 가지고 실험해 본 결과, C에서 값을 잘 넘겨받는 것까지는 좋은데, 다시 자바로 값을 써 줄 때, 인자가 잘 전달되지 않는 현상을 보였다. 좀더 생각해 볼 문제이다.
스트링 접근
스트링은 객체이므로 객체를 접근하는 방식을 통해 접근해야 한다. 우선 GetFieldID 함수의 인자로 "Ljava/lang/String;"을 주목하면, 객체의 접근 방식과 동일함을 알 수 있다. 여기에서 세미콜론(;)을 빠뜨리면, 인식하지 못하므로 주의하기 바란다.
<p>jstring jstr;
jfieldID jFieldId;
jclass cls = (*env)->GetObjectClass(env, jobj);
jFieldId = (*env)->GetFieldID(env,cls,"strVal","Ljava/lang/String;");
if(jFieldId == 0) {
printf("Field strVal not Found in Hello class\n");
return;
}</p><p>jstr = (*env)->GetObjectField(env,jobj,jFieldId);
(*env)->SetStaticIntField(env,cls,jFieldId,(iStaticIntVal+2));
str = (*env)->GetStringUTFChars(env, jstr, 0);
printf("Get String : %s\n",str);
</p><p>새로운 스트링을 생성하여, 이것의 레퍼런스값을 str 필드에 할당하려면, 다음과 같이 한다.
jstr = (*env)->NewStringUTF(env, "123");
(*env)->SetObjectField(env, jobj, jFieldId, jstr);</p>
기타 객체를 호출하는 방법도 스트링과 거의 동일하다. (*env)->GetObjectField(env,jobj,jFieldId);를 통해, jobject타입의 객체를 얻어낸 후, 필요에 따라 이 객체의 메쏘드를 호출하면 된다.