JNI Basic for NDK -2
카테고리 없음 2012. 12. 27. 22:36 |JNI Basic for NDK - 2
일반적으로 NDK(JNI) 프로그램을 하려면 안드로이드 프로그램(Java)에서 네이티브 코드(C/C++)로 인자를 전달하고 그 결과를 리턴 값으로 받게 됩니다. 그러기 위해서는 두 언어 간의 데이터 형을 어떻게 매핑이 되는지를 알아야합니다. 앞으로 몇 장에 걸쳐서 자주 사용되는 데이터 형에 대하여 알아보도록 하겠습니다.
1. 간단한 네이티브 메써드
이번 장은 앞에서 사용한 Hello NDK and JNI 예제와 비슷한 예제로 시작을 하겠습니다. 이전에는 네이티브 메써드에서 문자열을 받아와 디스플레이를 했습니다. 이번에는 Java에서 단어를 전달하면 네이티브 메써드에서 그 단어에 다른 단어를 붙여서 리턴하는 예입니다.
Android Java 어플은 아래와 같이 작성했습니다.
public class JniStringActivity extends Activity { |
이클립스에서 새 Android Project를 생성합니다. New Android Project 창에 아래와 같이 입력하고 Finish를 클릭합니다.
Project Name : JniString |
자동 생성된 JniStringActivity.java의 JniStringActivity class의 내용을 위의 코드와 같이 수정합니다.
이 클래스의 onCreate에서 concatString이라고하는 네이티브 메써드를 호출합니다. 인자로 'Apple'을 전달하면 네이티브 메써드에서 'Hell'를 붙여 'Hello Aplle'을 리턴합니다. System.loadLibray는 스태틱 초기화에서 실행이 되면서 concatString이라고 하는 라이브러리를 로드합니다.
네이티브 메써드인 concatString은 아래와 같은 원형으로 구현이 됩니다.
JNIEXPORT jstring JNICALL Java_ndk_jnistring_JniStringActivity_concatString
(JNIEnv *env, jobject obj, jstring what)
네이티브 메써드의 함수 원형은 javah를 사용하면 쉽게 만들어 낼 수 있습니다. javah를 사용하지 않는다면 JNI의 변환 규칙을 모두 알고 있어야할 겁니다.
다시 한번 같이 만들어 보겠습니다.
JniString 폴더 밑에 jni라고 하는 폴더를 생성합니다. 커멘드 창을 열어 폴더를 JniString\jni로 이동합니다. 아래 명령어를 실행합니다.
javah -classpath ../bin -o concatString.h ndk.jnistring.JniStringActivity
javah를 사용하기 위해서는 JDK가 설치되어 있는 폴더의 bin 폴더가 환경변수에 path로 등록이 되어있어야 합니다. 제 경우는
저는 윈도우즈에서 cygwin을 설치하여 사용하고 있습니다. 그러므로 여기서 이야기하는 커멘드 창이란 바로 cygwin 커멘드 창을 이야기하는 것입니다. |
함수 원형에 있는 JNIEXPORT와 JNICALL은 모두 jni.h에 정의되어있습니다. JNIEXPORT는 라이브러리를 생성시 이 메써드가 제대로 export가 되도록 해줍니다. JNICALL은 C 컴파일러가 적절하게 calling convention을 구성하도록 해 줍니다.
메써드의 이름을 만드는데도 규칙이 있습니다. 제일 앞에 Java_를 붙이고 다음에는 package 명을 포함한 클래스 이름이 붙습니다.
네이티브 메써드의 구현부에는 두개의 인자가 기본으로 들어가 있습니다. JNIEnv *env와 jobject obj가 바로 그것이죠. 이중 첫번째 인자인 JNIEnv 인터페이스 포인터는 Java 가상 머신의 펑션 테이블을 가르키는 포인터입니다. 네이티브 메써드 구현에서 Java 구현부의 데이터를 접근하기 위해서는 항상 이 펑션 테이블이 제공하는 함수를 사용해야합니다.
두번째 인자는 네이티브 메써드가 스태틱인지 인스턴스인지에따라 달라집니다. 위의 예와 같이 인스턴스 메써드라면 이 메써드가 호출된 오브젝트를 참조합니다. 하지만 스태틱 메써드라면 이 메써드가 정의된 그 클래스를 참조하게 됩니다.
Java에는 크게 두가지 데이터 타입이 있습니다. 기본형(Primitive Type)과 참조형(Reference Type)입니다. Java에서 기본형은 int, float, char이고 JNI에 매칭이 되는 타입이 jni.h에 정의되어 있습니다. int는 C/C++에서는 jint입니다. jni.h에는 signed 32bit integer로 정의되어 있습니다. 다른 기본형에 대해서는 아래 표를 참고 바랍니다.
Java Language Type |
||
boolean |
jboolean |
unsigned 8 bits |
byte |
jbyte |
signed 8 bits |
char |
jchar |
unsigned 16 bits |
short |
jshort |
signed 16 bits |
int |
jint |
signed 32 bits |
long |
jlong |
signed 64 bits |
float |
jfloat |
32 bits |
double |
jdouble |
64 bits |
JNI는 오브젝트를 전달할 때 Java 가상 머신의 내부 데이터 스트럭처를 가르키는 C 포인터를 전달합니다. 하지만 이 내부 데이터 스트럭쳐에 대한 정보는 제공하지 않습니다. 그래서 이 오브젝트의 데이터를 다루기 위해서는 JNI 함수를 사용해야만 합니다. 예를 들면 java.lang.String에 대응하는 JNI 데이터 타입은 jstring입니다. 하지만 네이티브 코드에서 jstring이 참조하는 내용을 바로 알지는 못 합니다. jstring이 참조하는 곳에 있는 실제 문자열을 얻어 오기 위해서는 JNI 함수인 GetStringUTFChars를 사용해야 합니다.
2. 문자열 얻어오기
네이티브 메써드의 구현부인 Java_ndk_JniString_JniStringActivity_concatString에서는 jstring 형의 what을 인자로 받아 옵니다. jstring은 Java 가상 머신에 있는 스트링을 가르키고 있지만 C에서 사용하는 char * 와 같지는 않습니다. 만일 아래와 같은 방식으로 what을 사용한다면 원하는 결과를 절대 얻지 못 할 겁니다.
JNIEXPORT jstring JNICALL Java_ndk_jnistring_JNIStringActivity_concatString ....... } |
네이티브 코드에서 jstring을 C/C++에서 사용하는 문자열로 변환하기 위해서는 적절한 JNI 함수를 사용해야합니다. JNI에서는 Unicode와 UTF-8 형태의 문자열을 모두 지원합니다.
Java_ndk_JniString_JniStringActivity_concatString에서 jstring what에서 원하는 문자열을 얻어 오려면 GetStringUTFChars 함수를 사용해야합니다. 이 함수는 JNI 함수로 JNIEnv 형의 포인터인 env를 통해서 접근할 수 있습니다. 아래 코드는 이 함수의 사용 예를 보여 줍니다.
#include <string.h>
JNIEXPORT jstring JNICALL Java_ndk_jnistring_JniStringActivity_concatString return (*env)->NewStringUTF(env, buf); |
위의 코드를 concatString.c로 concatString.h와 같은 폴더인 JniString\jni에 저장합니다.
str = (*env)->GetStringUTFChars(env, what, NULL);
GetStringUTFChars는 JNI 함수로서 env 포인터를 통해서 호출을 합니다. 이 함수는 what에 담겨 있는 문자열을 반환해 줍니다. 문자열을 리턴해 주기 위해서는 Java 가상 머신 내부에 메모리 공간을 할당 받아야 합니다. 하지만 내부 메모리가 부족하다면 문자열 대신 NULL을 리턴하고 익셉션을 발생시킵니다. 그러므로 이 함수를 사용할 때는 꼭 NULL이 리턴되지 않았는지 확인해야합니다.
(*env)->ReleaseStringUTFChars(env, what, str);
네이티브 코드에서 GetStringUTFChars로 얻어온 스트링을 더 이상 사용하지 않는다면 반드시 ReleaseStringUTFChars를 호출해 주어야합니다. 이 함수를 통해서 가상 머신 내부에 할당되어 있는 메모리를 해제시켜 주어야 불필요한 메모리 낭비를 방지할 수 있습니다.
(*env)->NewStringUTF(env, buf);
C/C++ 용 문자열을 java.lang.String 형으로 변환하기 위해서는 JNI 함수인 NewStringUTF를 사용해야합니다. 이 함수는 UTF-8 형태의 문자열을 인자로 받아서 java의 String형으로 변환을 해 줍니다.
3. 컴파일, 실행
앞에서 작성한 C 파일은 NDK를 사용해서 컴파일을 합니다. 컴파일을 하기위해서는 JniString\jni 폴더에 Android.mk를 생성해야 합니다. 파일의 내용은 아래와 같습니다.
LOCAL_PATH := $(call my-dir) |
폴더를 JniString로 이동하여 아래 명령어를 실행합니다. 이는 build.xml을 생성하기 위해서입니다.
android.bat update project -p . -s
ndk-build를 실행하여 컴파일을 합니다.
ndk-build
android.bat과 ndk-build를 실행하기 위해서는 이 파일이 있는 경로를 path에 포함시켜두어야 합니다. android.bat는 C:\android-sdk-windows\tools에 있습니다. C:\android-sdk-windows는 안드로이드 SDK가 설치되어 있는 경로입니다. ndk-build는 C:\android-ndk-r6에 있습니다. C:\android-ndk-r6는 NDK가 설치되어 있는 경로입니다. |
이클립스의 Package Explorer에서 JniString-6를 클릭한 후 F5눌러 변경된 내용을 반영합니다.
Run As -> android application을 해서 지금까지 작성한 코드를 실행합니다.
생각한대로 Hello Apple이 잘 디스플레이 되었습니다.
이번 장을 통하여서 JNI 네이티브 메써드에서 Java의 String 타입의 데이터에 접근하기 위해서 JNI 함수를 사용해야한다는 것을 알았습니다.