관계, Networking 그리고 Programming

NDK를 활용, 라이브러리 만들기 2 : 기본적인 동작 테스트

androbook 본캐 2014. 1. 20. 18:21

NDK를 위한 환경을 구성했으니, 아주 간단한 라이브러리를 만들어 동작을 테스트해 보기로 한다.


0. Base : 기본적으로 이클립스로 안드로이드 개발을 진행 중이며, 안드로이드 JNI 빌드를 위해 cygwin과 NDK를 환경을 구성했다.


1. 프로젝트 만들기.


JNI를 테스트 해 볼 안드로이드 프로젝트를 만들어보자. JNI를 사용하지 않을 때와 별다를 것이 없으므로 안드로이드 프로젝트 생성에 대해서는 따로 설명을 적지 않는다.


안드로이드 JNI 빌드를 검색해 보면서 ndk 루트 폴더 안에 프로젝트 폴더를 위치 시키라는 내용을 반복해서 봤는데, 왜 그런지는 모르겠다. 특별한 이유를 검색하지 못해서 그냥 기존에 안드로이드 프로젝트를 위해 사용하던 workspace 폴더를 사용했고, 결과적으로는 기본적인 빌드 및 동작에는 문제가 없었다. 특별히 환경구성이라던가 문제가 된 부분은 아직 없었으나 문제가 생긴다면 그 때 수정해도 늦지 않으리.


여튼 나는 com.androtest.Helloworld 라는 패키지 이름으로 프로젝트를 생성했다.



2. 프로젝트에 라이브러리 로드 클래스 만들기.


간단히 말해 안드로이드 JNI를 사용한다는 것은 안드로이드 프로젝트에서 C/C++로 빌드된 라이브러리를 불러다가 동작을 시킨다는 것이다. 따라서 C/C++ 라이브러리를 부르는 곳이 필요할 것이다. 라이브러리를 로드하고, 라이브러리의 함수들을 Java 소스에서 부를 수 있도록 정의해 두는 클래스를 만든다.


흔히 다른 사람들 하는대로 이름은 JNILoader로 했다.



별다를 것 없다. 원하는대로 이름을 정해서 클래스를 만들고, 라이브러리 함수 이름을 정의한다. 나는 스트링을 반환하는 greet라는 함수를 만들었다. 앞에 붙은 native는 이 함수가 라이브러리에 있는 함수라는 것을 알려준다. static으로 감싸인 System.loadLibrary("Hello");라는 구문은 JNILoader 클래스가 생성되는 순간 동작한다. 이 구문의 뜻은 Hello라는 이름을 가진 라이브러리를 로드하라는 것이다.


라이브러리의 이름은 원하는대로 정하면 되는데, 대부분 프로그램 공부를 시작할 때 습관처럼 "Hello"를 쓰기때문에 마찬가지로 Hello로 했다.


3. 라이브러리 함수 사용하는 부분 만들기


라이브러리가 로드되는 것을 확인하기 위해 JNILoader를 생성하고 greet함수를 부르는 구문을 작성한다.

private JNILoader jniInst = null;

TextView m_tv_display;


...


    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        jniInst = new JNILoader();

        m_tv_display = (TextView)findViewById(R.id.tv_display);

        m_tv_display.setText(jniInst.greet());

...


보이는 것 처럼 텍스트 뷰 하나를 두고 라이브러리에서 전달되는 문자열을 셋팅하도록 했다.


여기까지 하고 안드로이드 프로젝트를 빌드해보자. 빌드에는 아무 문제가 없을 것이다. ... 없을걸?.. 없을거야... 있다면 뭐가 잘못되었는지 찾아서 수정해야겠지. 여하든 빌드자체는 문제가 없겠지만 실제로는 Hello라는 라이브러리가 없기 때문에 실행시에는 에러가 발생할 것이다. 프로젝트 빌드까지만 확인하고 이제 라이브러리 작성하러 ㄱㄱ.


4. 라이브러리로 입장하는 헤더파일 작성


프로젝트 상에 Hello라는 라이브러리를 사용할 것이고, greet라는 함수를 부를 것이라고 명시했으니 이제는 실제 greet라는 함수를 가진 Hello라는 라이브러리를 만들어야 한다. Java 소스에서 사용하겠다고 명시한 함수들이 실제 이 라이브러리에 있어야 한다.


눈에서 빔나오게 뚫어져라 주의해서 라이브러리 헤더파일을 만드는 수고를 덜기 위해 javah라는 것을 사용할 것이다. 앞 서 NDK 환경 구성에서 빠진 것 없이 환경 변수를 설정했다면 별다른 문제가 없이 동작하겠지만, 혹시나 문제가 생긴다면 필요한 환경 변수를 모두 설정했는지 확인해 봐야한다.


먼저 cmd 창을 열어 앞서 작성한 안드로이드 프로젝트 폴더의 bin 폴더로 위치를 이동해야 한다.


나의 경우 다음의 경로로 이동했다.

C:\android\workspace\Hello\bin


이곳에서 다음과 같이 명령어를 동작시킨다.

javah -classpath ./classes/ -jni 패키지명.클래스명


나의 경우 패키지명과 클래스명을 조합하면 다음의 명령어가 된다.

javah -classpath ./classes/ -jni com.androtest.Helloworld.JNILoader


다른 문제가 없다면 다음과 같이 에러 문구 없이 정상동작이 되고 다시 커맨드 명령어 대기 상태가 된다.



이와 같이 되었다면 헤더파일이 작성된 것이다. 프로젝트의 bin 폴더 내에서 헤더파일을 찾을 수 있다. 패키지명과 클래스명이 자동적으로 조합되어 작성된 내 헤더파일의 이름은 com_androtest_Helloworld_JNILoader.h 다.


헤더파일을 열어보면 다음과 같이 내가 라이브러리에 있을 것이다라고 java 소스에 작성한 함수들이 JNI형식에 맞추어 자동적으로 저장되어 있다.



헤더파일을 가져다가 동작할 C/C++ 파일을 작성하기 전에 먼저 라이브러리를 위한 소스들이 정리될 폴더를 하나 만들자.


안드로이드 프로젝트 내에 "jni"라는 폴더를 생성하고, 방금 생성한 헤더파일을 이 곳에 옮겨 놓는다.




5. 헤더 파일에 맞는 C/C++ 작성


3,4 번의 과정을 통해 안드로이드 프로젝트에서 라이브러리로 접근하는 인터페이스(JNILoader.java), 라이브러리에서 안드로이드 소스를 향해 열어놓은 입구(com_androtest_Helloworld_JNILoader.h)를 만들었으므로 이제 할 일은 실제 라이브러리의 몸체를 만드는 일이다.


JNI 규칙에 따라 만들어진 함수 형식에 맞는 몸체를 만든다.

C파일을 만들던, C++ 파일을 만들던 상관은 없다. 다만 포인터 사용 방식이 달라져 코딩이 아주 약간 달라질 뿐. 나의 경우 C++ 파일을 만들어서 코딩했다.


main.cpp라는 파일을 만들어서 헤더파일과 같은 위치에 저장한다.

파일을 열어 다음과 같이 코딩했다. 

#include <jni.h>

#include "com_androtest_Helloworld_JNILoader.h"


JNIEXPORT jstring JNICALL Java_com_androtest_Helloworld_JNILoader_greet

  (JNIEnv * env, jobject javathis){

 return env->NewStringUTF("dll loaded");

}


java에서 greet 함수를 부르면 단순히 "dll loaded"라는 문자열을 리턴하도록 했다. eclipse로 열어서 코딩하든, 메모장을 열어서 코딩하든 상관 없지만 난 편의 상 visual studio를 열어서 코딩했다.


코딩 시에는 그저 C/C++ 문법에 맞는 코딩을 하면 그만이지만, 직접적으로 Java의 call을 받는 이 인터페이스 함수의 형식은 JNI 규칙을 따라야한다. 무슨말이냐면, jni.h 파일과 java와 연결되는 헤더파일의 include, 헤더파일에 정의된 함수 이름을 그대로 따라야 하고, 전달된 인자들(JNIEnv, jobject 등)의 사용법은 JNI 규칙을 따라야한다는 것이다. JNI 규칙들은 인터넷 상에서 검색해서 얼마든지 찾을 수 있다. (현재 검색한 결과로는 다음의 사이트를 뒤져보는 편이 빠를 듯 : http://journals.ecs.soton.ac.uk/java/tutorial/)


6. cygwin으로 빌드해서 라이브러리 만들기


6.1. Android.mk 파일 생성


Visual studio를 통해 만드는 라이브러리라면 프로젝트를 만들고 빌드 속성을 수정하고, 빌드 단축키를 누르겠으나, 여기서는 안드로이드 ndk를 활용해 안드로이드에서 사용할 수 있는 C/C++ 라이브러리를 만들어야 한다. 이를 위해 앞서 cygwin과 ndk 환경을 구성했다.


먼저 cygwin 빌드를 위해 Android.mk 파일을 만들어야 한다. Visual studio 같이 익숙한 GUI가 없기 때문에 조금 어렵게 느껴질 수 있지만, Android.mk라는 파일에 빌드되어야 할 소스 파일과 빌드 속성들을 텍스

트로 저장하는 것 뿐 어려울 것은 없다.(누구에게 하는말? 스스로에게?)


헤더파일과 소스파일이 있는 jni 폴더 아래에 Android.mk 파일을 생성한다.


Android.mk 파일에 적어야할 내용은 android ndk 폴더 내의 document.html을 확인하면 상세히 나와 있다. 일단 기본적으로는 다음과 같이  적으면 된다.

LOCAL_PATH := $(call my-dir)


include $(CLEAR_VARS)


LOCAL_MODULE    := Hello(라이브러리 이름)

LOCAL_SRC_FILES := main.cpp(빌드되어야 하는 소스파일)


include $(BUILD_SHARED_LIBRARY)




6.2. cygwin 창을 열어서 빌드


Android.mk 파일을 저장했으면, 이제 드디어 cygwin 창을 연다. 내 환경에서는 앞서 cygwin을 설치하면서 바탕화면에 생성된 cygwin64를 실행 시켰다. 먼저 위치를  현재 진행 중인 안드로이드 프로젝트의 jni 폴더로 이동한다. cmd 창에 입력하던 명령어와 다를바 없이 "cd path" 형식으로 이동하면 된다. 


jni 폴더로 이동 했다면 ls 명령어를 통해 현재 폴더에 있는 파일들을 확인해 보자. 헤더파일(com_androtest_Helloworld_JNILoader.h), 소스파일(main.cpp), make 파일(Android.mk) 파일이 모두 있는 것이 확인되었다면, 명령어 "ndk-build"를 입력하자. 환경설정도 모두 잘 했고, 소스 코딩도 문제가 없었다면 다음과 같이 라이브러리가 정상적으로 빌드되는 것이 표시된다.



빌드 마지막 부분에 보면 libs/armeabi/libHello.so 라고 해당 경로에 so 파일이 생성되었음이 보인다. 이 so 파일이 자바에서 사용하게 될 라이브러리이다.


이제 안드로이드 프로젝트를 다시 한 번 빌드하고 실행해 보면, 정상적으로 프로그램이 실행되고, TextView에 "dll loaded"라고 표시되는 것을 확인할 수 있다.








자 이제 조금 복잡한 라이브러리 만들어보러 ㄱㄱ~

 (>ㅁ< )일해야지~ 랄랏!