◎위챗 : speedseoul
https://myunani.blog.me/90173132057
자바 객체의 사용네이티브 코드 내에서 자바 객체를 사용할 수도 있다. 즉 콜백과 비슷한 형식을 지원한다. private영역까지 접근할 수 있다.
자바 객체의 클래스 정보 얻기일단, 인자로 넘겨지는 자바 객체에 대한 클래스 정보를 획득하는 함수가 제공된다.
jclass cls = (*env)->GetObjectClass(env, jobj);
여기서 주의할 것은 반환값으로 돌아오는 cls의 레퍼런스값은 오직 이 네이티브 메쏘드가 수행이 끝날 동안에만 유효하다는 것이다.
자바 객체 및 클래스 타입의 래퍼런스는 네이티브 메쏘드 실행시마다 결정되므로, 한 번 실행해 얻은 레퍼런스를 다음번 메쏘드에서 다시 사용하려는 것은 잘못된 것이다.
따라서, 원칙적으로 객체 레퍼런스 변수를 전역 변수로 할당하는 것은 위험한 일이다. 하지만, 꼭 허용되지 않는 것은 아니다. 전역 레퍼런스로 지정해 줄 수 있는 함수가 있다.
jobject NewGlobalRef(JNI_Env* env, jobject lobj)
네이티브 코드를 호출한 객체의 메쏘드에 대한 ID얻기객체의 메쏘드를 호출하기 위해 그 메쏘드의 ID를 획득해야 한다. 이것은 메쏘드의 이름과 시그너쳐를 이용하는데, 이름은 별 무리가 없겠으나, 시그너쳐는 까다롭다.
시그너쳐는 메쏘드의 인자 리스트와 반환 타입 정보를 스트링화한 것으로, 다음과 같은 규칙이 있다.
시그너쳐 : "(인자 리스트)반환값"으로 이루어진 스트링이다.
인자리스트의 인자들은 아래의 타입 기호에 따라 표기되며, 서로 ';'으로 구분된다.
반환값 역시 타입 기호에 따라 표시된다.
타입 기호는 다음과 같다. 이것은 자바 가상 머신 스펙에서 클래스 파일 포맷 부분에 등장하는 기호와 동일하다.
Z : boolean
B : byte
C : char
S : short
I : int
J : long
F : float
D : double
이들의 배열은 이들의 타입 기호 앞에 '['를 붙인다. 즉 int 배열은 [I가 된다.
클래스 타입의 경우, L뒤에 클래스명을 패키지명까지 포함해 나열하면 된다. 즉, String클래스인 경우, Ljava.lang.String이다.
이것은 수작업으로 할 경우, 실수가 많을 수 있으나, javap를 이용해, 쉽게 기호화된 시그너쳐를 확인할 수 있다.
javap -s -p Hello
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 */
}
이것은 다음과 같이 Hello클래스에 두 메쏘드를 추가한 후의 javap결과이다.
private void foo(String str,String str1) {
System.out.println("Hello.foo() = "+str+str1);
}
public static void coo(String str,String str1) {
System.out.println("Hello.coo() = "+str+str1);
}
이제 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 {
......
}
인자 생성 및 메쏘드 호출이제 메쏘드를 호출하는 일이 다음 순서가 되겠다. 물론 여기에 해당되는 함수들이 제공되는데, 이 함수들을 통해, 클래스 내의 메쏘드를 호출하므로, 메쏘드 ID와 더불어 메쏘드의 인자들을 리스트화해서 보내야 한다. 두 가지 방법이 있다.
C의 va_list구조체를 이용한다.
jvalue 유니온를 이용한다.
여기서는 jvalue 유니온을 이용해 보자. 그 구조는 다음과 같다.
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
그러면, 스트링 두 개를 인자로 받는 foo메쏘드를 호출해 보자. String은 객체이므로, l에 스트링 객체의 레퍼런스를 할당하면 된다.
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
여기에서는 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함수 이용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 { .....
}
인자 형성 방법은 동일하다.메쏘드 호출 : CallStaticVoidMethodA 이용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 );
}
여기서 주의할 것은 CallStaticVoidMethodA의 두 번째 인자는 jobject타입이 아닌, jclass타입이란 것이다. 클래스 메쏘드이니 당연하기도 하다.
상위 클래스의 메쏘드 호출한 걸음 더 나아가 상위 클래스의 메쏘드도 호출할 수 있다. 여기에 해당되는 메쏘드는 모두 NonVirtual이라는 이름을 포함하고 있는데, c++을 배운 이라면, NonVirtual의 의미를 알고 있을 것이다.
상위 클래스 타입의 jclass 객체 획득jclass sClass = (*env)->GetSuperclass(env,cls);
메쏘드 ID : GetMethodID함수 이용superFooM = (*env)->GetMethodID(env, sClass, "foo", "(Ljava/lang/String;Ljava/lang/String;)V");
if(superFooM == 0) {
printf("Method foo isn't found\n");
}
else {
.....
}
메쏘드를 호출: CallNonvirtualVoidMethodAargs = (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 );
멤버 필드 접근 필드를 접근하는 방법 또한 메쏘드를 접근하는 방법과 크게 다르지 않다. 필드가 Private일지라도 접근할 수 있다. 이 또한 몇 가지 함수에 의존하고 있는데, 그 함수의 형식이 메쏘드를 호출하는 함수와 거의 유사하기 때문이다. 일단 다음의 기호를 잊지 말자.
Z : boolean
B : byte
C : char
S : short
I : int
J : long
F : float
D : double
이들의 배열은 이들의 타입 기호 앞에 '['를 붙인다. 즉 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를 얻어내기 위한 함수는 다음과 같다.
jfieldID GetFieldID(JNI_Env* env,jclass clazz, const char *name, const char *sig)
그리고, 인스턴스 필드를 접근하는 함수들은 다음과 같다.
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를 얻어내야 한다.
jfieldID GetStaticFieldID(JNI_Env* env,jclass clazz, const char *name, const char *sig)
그리고, 필드를 접근하는 함수들은 다음과 같다.
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에서, 다음을 중심으로 코딩해 보자.
public native void testFieldAccess()
정적 변수 접근정적 필드인 iStaticIntVal 필드를 접근하여, 값을 읽어들인 후, 다시 2를 더해 값을 써 보자.
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));
인스턴스 변수 접근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);
그런데, 실제로 int를 가지고 실험해 본 결과, C에서 값을 잘 넘겨받는 것까지는 좋은데, 다시 자바로 값을 써 줄 때, 인자가 잘 전달되지 않는 현상을 보였다. 좀더 생각해 볼 문제이다.
스트링 접근스트링은 객체이므로 객체를 접근하는 방식을 통해 접근해야 한다. 우선 GetFieldID 함수의 인자로 "Ljava/lang/String;"을 주목하면, 객체의 접근 방식과 동일함을 알 수 있다. 여기에서 세미콜론(;)을 빠뜨리면, 인식하지 못하므로 주의하기 바란다.
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;
}
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);
새로운 스트링을 생성하여, 이것의 레퍼런스값을 str 필드에 할당하려면, 다음과 같이 한다.
jstr = (*env)->NewStringUTF(env, "123");
(*env)->SetObjectField(env, jobj, jFieldId, jstr);
기타 객체를 호출하는 방법도 스트링과 거의 동일하다. (*env)->GetObjectField(env,jobj,jFieldId);를 통해, jobject타입의 객체를 얻어낸 후, 필요에 따라 이 객체의 메쏘드를 호출하면 된다.
[출처] native 코드내에서 자바 객체의 사용작성자 츠바샤
http://blog.naver.com/ksock2?Redirect=Log&logNo=40003134981