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타입의 객체를 얻어낸 후, 필요에 따라 이 객체의 메쏘드를 호출하면 된다.