메모형식으로 작성한 글입니다..


안드로이드 스튜디오에서 NDK로 C++11과 STL을 사용한 소스를 빌드하는 법.

C++가 아닌 C를 사용할 때는 makefile과 소스코드에서 약간의 차이가 있다. (본문 내에서 설명)



테스트 환경

Android Studio : 2.2.2

Gradle : 2.2.2

buildToolsVersion : 24.0.0




테스트 소스 코드

https://github.com/tibyte/practice-unclassified/tree/master/Android/NDKTest3




NDK 설치

File - Settings - Appearance & Behavior - System Settings - Android SDK

에서 SDK Tools로 들어가면 NDK를 설치할 수 있다.






javah 추가

File - Settings - Tools - External Tools에서 추가를 눌러 javah를 추가한다.

하단의 Tool settings에 들어갈 내용은 아래와 같다.

Program : jdk에 포함되어 있는 javah의 경로

Parameters : -v -jni -d $ModuleFileDir$/src/main/jni $FileClass$

Working directory : $SourcepathEntry$





java코드에서 네이티브 함수 사용

System.loadLibrary()로 사용할 라이브러리를 로드한다.  라이브러리 이름은 임의로 정해도 되는데, 이 글에서는 main.cpp를 만들 것이므로 여기에서는 main으로 정한다.

그리고 사용할 네이티브 함수명을 native키워드를 통해 선언한다.

package kr.tibyte.ndktest3;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

static {
System.loadLibrary("main");
}
public native String getNativeText();


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TextView tv = (TextView)findViewById(R.id.text_view);

tv.setText(getNativeText());
}

} 





JNI용 헤더파일 생성


프로젝트 탭에서 보기 설정을 Project로 한다.




네이티브 함수를 사용한 Java파일을 우클릭하여 External Tools - javah를 클릭한다.

그러면 src/main/jni 경로에 프로젝트_경로.h파일이 생성된다. 






C++ 소스코드 작성

JNI디렉토리 내에 cpp파일을 만들고 소스코드를 작성하는데,

함수 이름에 패키지 경로가 들어있어야 한다. 이 부분이 실제 패키지 경로와 다르면 빌드가 되지 않는다. 아래 예제에서는 테스트 목적으로 STL과 C++11 기능을 사용했다. 

#include <jni.h>

#include <vector>

#include <string>

#include "kr_tibyte_ndktest3_MainActivity.h"


using namespace std;


extern "C" {


JNIEXPORT jstring JNICALL Java_kr_tibyte_ndktest3_MainActivity_getNativeText(JNIEnv *env, jobject obj)

{

    string str = "";

    vector<char> vec;

    vec.push_back('a');

    vec.push_back('b');

    vec.push_back('c');


    for(auto& x : vec) {

        x ^= 32;

        str += x;

    }


    return env->NewStringUTF(str.c_str());


}



이 부분에서 C와 C++의 차이는 다음과 같다.

- 소스코드 확장자 (.c, .cpp)
- C++문법 사용여부
- C++에서는 함수 형식이 JNIEXPORT jstring JNICALL이지만, C에서는 jstring
- JNI함수를 호출할 때 C++에서는 env->NewStringUTF("string") 형식을 사용하지만,
 C에서는 (*env)->NewStringUTF(env, "string"); 형식으로 리턴.

- C++에서 extern으로 네임 맹글링을 어떤 규칙으로 할 것인지 지정(참고링크)





Makefile 설정


jni디렉토리 안에 Android.mk와 Application.mk 파일을 생성하고 각각 아래와 같은 내용을 적는다.


Android.mk

LOCAL_PATH := $(call my-dir)


include $(CLEAR_VARS)


LOCAL_DEFAULT_CPP_EXTENSION := cpp

LOCAL_MODULE    := main

LOCAL_SRC_FILES := main.cpp

LOCAL_LDLIBS    := -llog -latomic

LOCAL_C_INCLUDES := $(LOCAL_PATH)/include-all


include $(BUILD_STATIC_LIBRARY)

C로 빌드할 때에는 LOCAL_DEFAULT_CPP_EXTENSION := cpp가 필요하지 않다.

LOCAL_MODULE과 LOCAL_SRC_FILES는 작성한 C/C++네이티브 소스파일과 관련된 내용을 적는다.


Application.mk

NDK_TOOLCHAIN_VERSION := 4.9

APP_STL := gnustl_static

APP_MODULED := main

APP_ABI := armeabi-v7a

APP_CPPFLAGS += -std=c++11

LOCAL_C_INCLUDES += ${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.9/include

NDK_TOOLCHAIN_VERSION : NDK설치경로/sources/cxx-stl/gnu-libstdc++ 에 있는 툴체인 버전을 적는다.

APP_STL : 정적 라이브러리를 사용하고 싶을 경우 gnustl_static를, 공유라이브러리를 쓸 대는 gnustl_shared를 적을 수 있다.

APP_ABI : 빌드할 대상 아키텍쳐를 적는다. all로 적으면 모든 대상으로 빌드할 수 있다.

APP_CPPFLAGS : C++11로 빌드하려면 -std=c++11 플래그를 넣는다.

LOCAL_C_INCLUDES : 자신이 가진 버전에 맞는 경로를 적는다. (NDK설치경로 확인)





build.gradle ndk 정보 추가

src디렉토리 내에 있는 defaultConfig안에 다음과 같은 내용을 추가한다.

 ndk {

stl "gnustl_static"

moduleName "main"

}

stl에서 gnustl_static은 Application.mk에서 설정했던 것과 같은 것이고,

moduleName은 Android.mk, Application.mk, java파일에서 썼던 것과 같다.

만약 STL을 사용하지 않을 것이면 stl은 작성하지 않아도 된다..




실행화면

STL vector와 C++11의 auto를 사용한 코드가 정상적으로 빌드된다. 







+ Recent posts