우선 아래 소스는 안드로이드 전체 소스에서 볼 수 있는 소스코드이다.
샘플 이름데로JNI관련해서 기본적인 구조를 알 수 있는데 도움이 될 것이다.
소스 경로: development/samples/SimpleJNI
위 소스에 있는 소스를NDK에서 빌드 및 실행이 되도록 수정을 하여 만들어 보았다. (간단하게 몇몇 부분만 수정하면NDK에서도 사용할 수 있었다.)
샘플의 소스는 문서와 같이 첨부한SimpleJNI.zip 파일을 참고하기 바란다.
소스의 핵심적인 부분과 설명은 하자면 다음과 같다.
SimpleJNI.java
package com.example.android.simplejni; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class SimpleJNI extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv = new TextView(this); int sum = Native.add(2, 3); tv.setText("2 + 3 = " + Integer.toString(sum)); setContentView(tv); } } class Native { static { System.loadLibrary("simplejni"); } static native int add(int a, int b); } |
1) libsimplejni.so 라이브러리를 로딩한다.
2) 사용할 네이티브 메서드 add 를 선언한다.
위 Java 소스는 위에서 설명한 '기본적인 JNI 사용' 에서와 차이는 없다. 차이점은 다음의 네이티브(C++)소스에서 차이가 있다.
native.cpp
#define LOG_TAG "simplejni native.cpp" #include "log.h" #include <stdio.h> #include "jni.h" //(3) JNI에서 사용하는 실제 네이티브 함수 static jint add(JNIEnv *env, jobject thiz, jint a, jint b) { int result = a + b; LOGI("%d + %d = %d", a, b, result); return result; } //(5) 네이티브 함수를 사용할 자바 클래스 static const char *classPathName = "com/example/android/simplejni/Native"; //(2) jni.h 에서 제공하는 JNINativeMethod 구조체 배열로 사용할 JNI 메서드를 정리 static JNINativeMethod methods[] = { {"add", "(II)I", (void*)add }, }; static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = env->FindClass(className); if (clazz == NULL) { LOGE("Native registration unable to find class '%s'", className); return JNI_FALSE; } //(4) methods[] 에서 나열한 JNI 메서드를 실제 함수 등록을 하는 과정이다. if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { LOGE("RegisterNatives failed for '%s'", className); return JNI_FALSE; } return JNI_TRUE; } static int registerNatives(JNIEnv* env) { if (!registerNativeMethods(env, classPathName, methods, sizeof(methods) / sizeof(methods[0]))) { return JNI_FALSE; } return JNI_TRUE; } typedef union { JNIEnv* env; void* venv; } UnionJNIEnvToVoid; //(1) 라이브러리 로딩시 호출 jint JNI_OnLoad(JavaVM* vm, void* reserved) { UnionJNIEnvToVoid uenv; uenv.venv = NULL; jint result = -1; JNIEnv* env = NULL;
LOGI("JNI_OnLoad"); if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) { LOGE("ERROR: GetEnv failed"); goto bail; } env = uenv.env; if (registerNatives(env) != JNI_TRUE) { LOGE("ERROR: registerNatives failed"); goto bail; }
result = JNI_VERSION_1_4;
bail: return result; } |
1) JNI_OnLoad 함수는 Java 에서 라이브러리를 로드하는 시점에서 호출된다. (1)
JNI_OnLoad() 의 기본적인 기능은 자바 가상 머신이 지원하는 JNI 버전을 확인하는 것이다. 그러므로 JNI_OnLoad 메서드에서는 JNI 버전정보를 반드시 반환해야한다.
또한 JNI_OnLoad() 에서 자바 가상 머신이 네이티브 라이브러리를 로드할때 JNI에 대한 초기화 작업을 할 수 있다.
자세히 봐야 할 부분은 JNI_OnLoad 에서 registerNatives 함수를 호출한다.
2) registerNatives 함수안에서 registerNativeMethods 함수를 호출하여 JNI 네이티브 함수를 등록한다.
위 소스에서 JNI 네이티브 함수 등록을 위해서 아래의 배열을 만든다. (2)
static JNINativeMethod methods[] = {
{"add", "(II)I", (void*)add },
};
자바에서 첫번째 파라메터의 api 이름으로 사용하게 되며 파라메터와 리턴값은 II(I) 로 알 수 있다. 두개의 정수 값의 파라메터를 가지고 리턴값이 정수값임을 알 수있다.
자바에서 사용하는 add 함수의 구현은 (void*) add 에 구현되어 있다. (3)
여기서 핵심은 jni.h에 정의된 JNINativeMethod 구조체를 사용하여 등록한다는 것이다.
typedef struct { const char* name; //JNI 함수이름 const char* signature; //JNI 함수의 파라미터와 반환 값 void* fntPtr; //JNI 함수의 네이티브 함수 포인터 } JNINativeMethod; |
3) registerNativeMethods 함수안에서 JNI에서 제공하는 메서드 RegisterNatives 를 사용하여 실제 사용할 함수를 등록한다.
네이티브 함수를 사용할 자바 클래스는 변수 classPathName 에 지정하였다. (5)
위에서 설명한 “1. 일반적인 JNI 사용방법”과 “2. JNI 네이티브 함수에 직접등록방법”의 샘플을 비교했을때, 첫번째 방법의 경우에는 간단하게 사용할 수 있는 장점이 보이고 두번째 방법의 경우 사용하는 네이티브 메서드가 많을 경우 Class이름을 바꾸기가 쉬워보인다.
위 두 방법의 핵심적인 차이는 http://jaehwa.egloos.com/1029760 에서 설명한다.

덧글