해당 포스트는 "안드로이드 ndk의 모든 것", "안드로이드 NDK 네이티브 프로그래밍" 책의 내용을 요약한 것이다.



※ JNI_OnLoad

우리는 네이티브 코드를 작성할 때 javah를 통해 함수명을 만들어 내거나, 규칙에 맞게 함수명을 만들었다. 이 방법 말고 JNI_OnLoad를 통해 네이티브 함수를 만들 수 있다. JNI_OnLoad 안에서 JNI 버전 체크나 Global Reference 세팅이나 기타 초기화 작업을 하고 자바-네이티브 간 함수 연결을 할 수 있다. 

Dalvik에서 네이티브 라이브러리를 로딩하면 가장 먼저 JNI_OnLoad 함수를 찾는다. 만약 JNI_OnLoad가 없다면 건너뛰고 다음 작업을 진행을 진행한다. JNI_OnLoad를 이용하는 이유는 네이티브 함수명을 패키지명, 클래스명, 메서드명을 연결하여 사용하지 않아도 되기 때문입니다. 함수명을 복잡하게 사용하면 코드의 가독성이 떨어지게 됩니다. 또한 네이티브 단에서 자바 함수에 능동적으로 접근하는 일이 생기는 데 이 때 JVM이 필요합니다. JNI_OnLoad를 사용하면 JVM에 쉽게 접근할 수 있습니다. 

이전 포스트 안드로이드 스튜디오 NDK JNI 예제 프로젝트를 JNI_OnLoad 함수를 이용해 구현해보겠다. 이전 프로젝트에 있었던 javah로 만들어진 헤더파일과 main.cpp를 지우고 새로운 onLoad.cpp 파일을 만들었다. onLoad.cpp 코드는 다음과 같다.


#define LOG_TAG "JNI_OnLoad_Example"
#include <android/log.h>
#include <stdio.h>
#include "jni.h"

#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))

jstring getNumString(JNIEnv * a, jobject b, jstring str, jint num)
{
jclass cls = a->GetObjectClass(b);
jmethodID func = a->GetMethodID(cls, "callback", "(Ljava/lang/String;I)Ljava/lang/String;");
return (jstring)a->CallObjectMethod(b, func, str, num);
}

//자바 안에서 네이티브 키워드를 붙인 함수가 있는 클래스를 설정한다.
static const char* classPathName = "com/example/user/myapplication/JNIClass";

//methods에 등록된 데이터를 통해서 자바-네이티브 간 함수들을 연결한다

//첫 번째 인자는 이름으로 자바 상에서 native 키워드가 붙여진 함수 이름을 넣는다

//두 번째 인자는 함수의 signature이다.

//세 번째 인자는 cpp 파일 안에서 정의된 네이티브 함수의 포인터를 나타낸다.

static JNINativeMethod methods[] = {
{"getNumString", "(Ljava/lang/String;I)Ljava/lang/String;", (void*)getNumString},
};

//Register several native methods for one class
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;
}


//실제 네이티브와 자바간에 함수를 연결한다.
if(env->RegisterNatives(clazz, gMethods, numMethods) < 0)
{
LOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}

//Register native methods for all classes we know about
int registerNatives(JNIEnv* env)
{
if(!registerNativeMethods(env, classPathName, methods, sizeof(methods)/sizeof(methods[0])))
{
return JNI_FALSE;
}
return JNI_TRUE;
}

//This is called by the VM when the shared library is first loaded
typedef union
{
JNIEnv* env;
void* venv;
}UnionJNIEnvToVoid;

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_6) != 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_6;

bail:
return result;
}


※ 한글 처리

자바는 UTF-8 형태의 문자셋을 사용하고 C는 그렇지 않기 때문에 한글처리하는 데 과정이 필요하다. 네이티브 함수에서 생성된 한글 문자열을 자바로 가져가기 위해서는 한글 문자열을 바이트형 배열에 복사해 준 후 자바에서 바이트형 배열을 String 으로 바꿔주면 된다.

//native 단
........StringFromJNI()
{
   jbyte buffer[] = "가나다라Test123";
   jbyteArray jb = (*env)->NewByteArray(env, sizeof(buffer));
   (*env)->SetByteArrayRegion(env, jb, 0, sizeof(buffer), buffer);
   return jb
}
//// 자바
....
    byte[] result;
    result = StringFromJNI();
    String output = new String(result, "KSC5601");
....


+ Recent posts