안드로이드 개발을 하면서 가장 흥미롭거나 유용하다고 생각하는것 중에 하나가 JNI 이다.
C로 개발된 소스를 그데로 라이브러리화 해서 java(android) 에서 사용 할 수 있다는 것은 개발자로서 축복 받은 일이다~~
작년에 프로젝트 도중에 어찌어찌 하다보니 내손에 c로 구현된 천지인 소스가 들어왔다~ 생각보다 소스량이 많아 놀랐지만, 왠만한것은 다 할수 있다.
역시나 저작권덕에 배포는 할 수 없고 혼자 조용히 가지고만 있다ㅠㅠ
시간이 남으면 해당 라이브러리에 카메라 소스붙여서 요새 인기좋다는 투명키패드(?) 어플리케이션을 만들어 볼까 생각중이다ㅋ
잠깐 시간이 남아 천지인 소스를 안드로이드 라이브러리로 포팅해 빌드를 해보았다. jni 포팅에 대한 내용을 아래에 정리해 본다~
우선 ndk 설치 후 c 파일을 빌드하기 위해
<JNI_HOME>/<PROJECT_NAME>/project/jni 경로에 있는 Android.mk 파일을 작성해야 한다.
자세한 작성법은 <JNI_HOME>/docs/ANDROID-MK.TXT (NDK
를 설치한 폴더) 에 설명되어 있다.
작성법에 따라 아래와 같이 Android.mk 파일을 작성해 보았다.
c 에서 일반적인 makefile 문법과 동일하다.
local_module 에 빌드명을 적는다. 이 이름은 빌드시에 필요한 이름이니, 적당하게 작명해서 적어준다.
bulid_shared_library
는 공유 라이브러리로 빌드한다는 뜻이다.
local_src_files 은 빌드에 필요한 c 파일들을 주루룩 나열해 주면 된다. 나열 순서는 상관 없다~
자세한 내용은 역시나 docs 폴더에 ANDROID-MK.txt 에 자세히 설명되어 있다. (영어로..)
ndk가 아닌 다른 빌더에서 빌드된 .so 파일은 아쉽게도 정식적인 방법으로는 물릴수 없다. 요새 검색도중 크로스 컴파일해서 물릴수 있다고 하던데..
그렇게 까지 하면서 물릴만한 라이브러리를 보유하고 있지 않아 패스~
(그러고 보니.. 프로젝트도중 암암리에 얻게된 문자인식 라이브러리가 있긴하다-_-;; 이건 나중에 시간되면 해보기로 하고~)
다음 단계로 넘어가 c로 구현된 소스들을 java에서 호출가능하게 하기 위해 약간의 포팅 작업을 거쳐야 한다.
C 구현
NaraAutoHandler.c, NaraAutomata.c, NaraCode_TKS.c 파일들은 automata 코드이고, NaraJni.c 파일은 c 코드와 java 코드를 연결하기 위한 포팅 레이어를 구현하였다.
나름 대범하게 원래 소스를 토씨하나 바꾸지 않고 그데로 사용하였다;; (배포하지 않을 것이기 때문에..)
포팅된 코드는 아래와 같다.
#include
<string.h>
#include
<jni.h>
#include
"NaraAutoDef.h"
#include
"NaraAutoHandler.h"
#include
"NaraAutomata.h"
#include
"NaraCode_TKS.h"
#define S_OK 0
#define E_ERROR -1
sNaraIMEState* ime =
NULL;
char* buf = NULL;
jbyteArray jbtbuf =
NULL;
jint
Java_com_example_keypad_KorKeypad_NaCInit( JNIEnv* env, jobject thiz , int
maxsize)
{
ime =
(sNaraIMEState*)malloc(sizeof(sNaraIMEState));
if(!ime)
return E_ERROR;
buf = (char *)malloc((sizeof(char) *
maxsize) + 1);
if(!buf)
return E_ERROR;
NaC_Init(ime, buf, maxsize);
return S_OK;
}
void
Java_com_example_keypad_KorKeypad_NaCSetCurrentMode( JNIEnv* env,
jobject thiz , int mode)
{
NaC_SetCurrentMode(ime, mode);
}
void
Java_com_example_keypad_KorKeypad_NaCKeyIn( JNIEnv* env,
jobject thiz , char input)
{
NaC_KeyIn(ime, input);
}
jbyteArray
Java_com_example_keypad_KorKeypad_NaCGetBuffer( JNIEnv* env,
jobject thiz)
{
if (jbtbuf != NULL)
{
(*env)->DeleteGlobalRef(env, jbtbuf);
}
char* src = NaC_GetBuffer(ime);
int len = strlen(src);
jbtbuf = (*env)->NewByteArray(env, len +
1);
(*env)->SetByteArrayRegion(env, jbtbuf,
0, len, src);
return jbtbuf;
}
간지나게 포팅 풀소스를 공개 했다.ㅋ
(라고 해바야 별거 없다. ㅠㅠ)
원래 함수들은 더 많은데.. 우선 필요한 함수들 몇개만 포팅했다.
프로젝트 진행할때, virtual keypad랑 full screen keypad 랑 input 관련된 interface 들이랑 모두 본인 일이었던 지라.. 포팅하며 오랜만에 옛 향수(??)를 느낄수 있었다 ㅠㅠ
딱 보아도 전형적인 c 스타일의 function pointer table 을 이용한 콜 방식이다~ cpp 로 작성하면 function 포인터가 아닌 클래스 형식으로 env->func(); 요런식으로 바뀐다. 그런데 본인은 cpp 로 빌드하면 자꾸 다른 빌더로 자동으로 바뀌는 바람에 c 로 작성해 컴파일 했다~
코드 작성 시 주의점은 함수명은 다음과 같은 방식을 따라야 한다.
JAVA_<패지키 명>_<클래스 명>_<함수명> (JNIEnv* env, jobject thiz,
<매개변수>)
<함수명> 에는 '_' 문자가 들어가서는 안된다. 처음에 못도 모르고 함수명에 언더바를 넣었다가.. java 에서 호출하는데, 자꾸 vm 죽길래 바보처럼 한참을 헤메기도 했다;;
JAVA_ 는 java 에서 c 로 jni 호출한다는 뜻이다. 반대로 c 에서 java로 된 소스를 콜 할 수도 있다.
패키지명과 클래스명이 일치해야 java 에서 call 이 가능하다.. 따라서 java 단에서도 jni 콜을 위한 전용 클래스를 하나 두어야 된다는 뜻이 된다.
VM 을 통해서 콜하기 때문에 속도도 왠만큼 보장된다고 어디서 본것 같다. 그래서 이미지 처리등에 관한 어마어마한 코어단 소스를 jni 로 만들어 호출한다고 하기도 한단다.
자바에는 포인터가 없기때문에 배열 포인터 같은 경우 jni.h 에 정의되있는 jbyteArray,
jcharArray, jintArray 등등을 사용해야 한다.
자바로 보내지는 객체는 C 단에서 메모리를 할당하기 때문에 메모리 관리에 신경 써줘야 한다.
포팅해주는 쪽에서 메모리를 관리하는게 본인에게는 편하기 때문에 이렇게 구현했다. 콜러쪽에서 메모리 관리를 해주어도 당연히 된다^^
포팅 레이어를 다 구현했다면, 다음은 바로 빌드를 하면 된다~
빌드
apps/<Project 명>/Application.mk 에 APP_MODULES 과 apps/<Project 명>/project/jni/Android.mk
에 LOCAL_MODULE 에 모듈명을 정확히 작성 후 빌드 작업은 간단하게 ndk 폴더에서 (apps, build, docs, out 폴더가 있는 곳) 아래와 같이 실행하면 된다.
빌드가 위와같이 성공했다면 project/libs/armeabi 폴더에 lib<모듈 명>.so 파일이 생긴다.
빌드가 완료 되었다면 이제 java 에서 호출하는 일만 남았다 +ㅁ+!!
Java 구현
위와 같이 라이브러리 파일을 생성하였다면 생성된 파일을 이클립스 프로젝트에 동일하게 libs/armeabi/ 폴더에 복사 후 해당 라이브러리 파일에 있는 함수들을 아래와 같이 정의 한다.
미리 구현된 소스가 아니라 처음부터 c를 구현한다면 java 프로젝트 자체를 해당 폴더에 import 시키는것을 추천한다. jni 빌드하고 .so 파일 복사하고 java 컴파일하는 단계를 한번에 처리할 수 있다~~
함수 정의 시에는 c 파일와 달리 함수명만 작성하면 된다.
함수를 클래스 내에 정의 했다면 라이브러리 파일이 호출 가능하다.
jni 호출 구조는 간단하게 아래와 같다고 한다.
어디서 퍼온 사진인데.. 시간이 좀 지나서 출처가 잘 기억나지 않는다. 안드로이드 dev 사이트 인가?? ㅡㅡㅋ;;
어쨋든 직관적으로 한눈에 호출구조를 알 수 있다. 사실 내부는 엄청 복잡하겠지만.. 단순하게 java call -> lib -> return 이라는 형식으로 단순화 하였다 ㅋ
우선 위에서 열심히 만든 .so 파일 로드 해주시고~ 바로 바로 호출해서 쓰면 된다~
static {
System.loadLibrary("keypad-kor");
}
자바 코드 구현 시 주의점은 byteArray를 리턴받았을 경우 한글 지원을 위해 아래와 같이 euc-kr을 지원 해야한다.
아직나에겐 복잡하기만한 망할 유니코드..!!
그냥 단순 간단하게 String 객체를 euc-kr로 하나 생성해 주었다~ 어차피 테스트용 이니~~ 메모리따위 가볍게 무시~~
포팅된 jni 오토마타 를 이용하여 다음의 "ㅇㅏㄴㄷㅡㄹㅗㅇㅣㄷㅡ" 한글을 조합한 결과는 아래와 같다.
출처 : http://blog.naver.com/oh4zzang/40111762322