강좌 & 팁
JNI 를 사용하면서 java 함수와 c / c++ 함수를 mapping 하는 방법은 두가지가 있다.
c / c++ 함수의 이름을 규칙에 맞게 선언해서 사용하는 방법을 지난 번에 알아보았고,
두번째 방법인 JNI_OnLoad 함수에 대해 지금부터 알아보겠다.
JNI 에 대해서 가장 많은 예제를 포함하고 있는 것이 android 소스이므로,
android 소스에서 사용법은 확인하도록 해보자.
===== (7) JNI_OnLoad 함t수 =========================================================
1. JNI_OnLoad, JNI_OnUnload 의 선언
JNI 에서 java 로 library 가 load 되는 과정을 살펴보면,
System.loadLibrary() 함수가 호출될 때, 우선 JNI_OnLoad 함수하는지 확인하고
있을 경우에는 함수의 내용을 수행하고 없을 경우에는 기본 규칙에 따라 함수를 mapping 하게 된다.
따라서 JNI_OnLoad 를 구현할 경우 함수의 mapping 을 직접해줘야 한다
하지만 경우에 따라서는 JNI_OnLoad 함수에 부가적이 내용 끼워넣어서
필요한 초기화 작업이 자동으로 이루어지도록 할 수도 있다.
jni.h 에서 함수의 선언을 찾을 수 있다.
/usr/lib/jvm/java-6-openjdk/include/jni.h
.... 1943 /* Defined by native libraries. */ 1944 JNIEXPORT jint JNICALL 1945 JNI_OnLoad(JavaVM *vm, void *reserved); 1946 1947 JNIEXPORT void JNICALL 1948 JNI_OnUnload(JavaVM *vm, void *reserved); .... |
2. android 에서의 구현
직접적인 사용의 예를 찾아보기 위해 android 소스를 살펴보자.
..../frameworks/base/services/jni/onload.cpp
#include "JNIHelp.h" #include "jni.h" #include "utils/Log.h" #include "utils/misc.h" namespace android { int register_android_server_AlarmManagerService(JNIEnv* env); .... }; using namespace android; extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { LOGE("GetEnv failed!"); return result; } LOG_ASSERT(env, "Could not retrieve the env!"); register_android_server_AlarmManagerService(env); .... return JNI_VERSION_1_4; } |
jni 소스 부분에서 JNI_OnLoad 함수를 찾을 수 있다.
jni 에서 필요한 함수들을 포함하고 있는 'JNIEnv* env' 를 얻어와서 register_android_server_XXX 함수들에
넘겨주고 실제적인 함수 등록은 각 소스파일에 구현되어 있는 register_android_server_XXX 함수에서 한다.
..../frameworks/base/services/jni/com_android_server_AlarmManagerService.cpp
.... static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"init", "()I", (void*)android_server_AlarmManagerService_init}, {"close", "(I)V", (void*)android_server_AlarmManagerService_close}, {"set", "(IIJJ)V", (void*)android_server_AlarmManagerService_set}, {"waitForAlarm", "(I)I", (void*)android_server_AlarmManagerService_waitForAlarm}, {"setKernelTimezone", "(II)I", (void*)android_server_AlarmManagerService_setKernelTimezone}, }; int register_android_server_AlarmManagerService(JNIEnv* env) { jclass clazz = env->FindClass("com/android/server/AlarmManagerService"); if (clazz == NULL) { LOGE("Can't find com/android/server/AlarmManagerService"); return -1; } return jniRegisterNativeMethods(env, "com/android/server/AlarmManagerService", sMethods, NELEM(sMethods)); } |
mapping 할 함수들의 정보는 "sMethods[]" 에 넣어둔다.
해당 클래스가 java 에 있는지 확인하고, 클래스 이름과 함수의 정보를 "jniRegisterNativeMethods" 에 넘겨준다.
"NELEM" 은 sMethods[] 의 길이를 알려주는 메크로이다.
모든 jni 소스는 이와 같은 형태로 구성되어 있으므로 "jniRegisterNativeMethods" 함수만 확인해서 이해하고 있으면 된다.
..../dalvik/libnativehelper/JNIHelp.c
.... /* * Register native JNI-callable methods. * * "className" looks like "java/lang/String". */ int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { jclass clazz; LOGV("Registering %s natives\n", className); clazz = (*env)->FindClass(env, className); if (clazz == NULL) { LOGE("Native registration unable to find class '%s'\n", className); return -1; } int result = 0; if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { LOGE("RegisterNatives failed for '%s'\n", className); result = -1; } (*env)->DeleteLocalRef(env, clazz); return result; } .... |
함수들의 이름은 낯설지만 생각보다 단순하다.
JNIEnv 에 선언되어 있는 RegisterNatives 함수에 mapping 에 필요한 몇가지 정보만 넘겨주면 된다.
android 가 아닌 다른 JNI 프로그램에서도 이 모듈을 때어다 써도 아무 지장에 없다.
잘 만들어진 모듈이므로 적절히 배치해서 그대로 사용해도 된다.
다만, JNINativeMethod 구조체에 함수 정보를 넣을 때 사용되는 문자열의 내용이 마치 암호와 같이 되어있다.
static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"init", "()I", (void*)android_server_AlarmManagerService_init}, .... }; |
결국 argument 와 반환형, 함수 이름에 대한 내용들인데...
이 부분에 대해서는 다음에 적도록 하겠다.
=============================================================================