안녕하세요.

안드로이드 포스팅을 오래간만에 진행해보겠습니다.

 

안드로이드(JAVA) 환경에서 C혹은 C++(네이티브) 소스를 연동/연결하여 상호 호환하는 기술을 주제로 시작하겠습니다.

 

사용 환경

+ 안드로이드 스튜디오 4.1.3 (JAVA)

+ NDK 22.1.7171670

+ JDK 8_121

 

NDK & JNI

NDK는 네이티브 개발 키트 (Native Development Kit)를 의미합니다.

간단히 말해서 Android에서 C 및 C++ 코드를 사용할 수 있게 해주는 도구 모음입니다.

네이티브 소스코드를 사용함으로써 효과는 속도, 연산, 호환성 등의 SDK 보다 빠른 작업 속도를 확인할 수 있습니다.

 

JNI는 자바 네이티브 인터페이스 (Java Native Interface)를 의미합니다.

간단히 말해서 JAVA(JVM)과 C 및 C++를 연결시켜주는 인터페이스를 말합니다.

사용하고자 하는 목표에 대해 이야기하자면 자바 메서드 호출로 C 및 C++로 작성된 코드 및 함수를 실행할 수 있습니다.

 

Hello, World 예제를 시작하기 전..

상세하고 쉽게 알려드리기 위해 스크린샷과 설명을 최대한 잘하려고 노력하는 편입니다.

먼저 자료를 정리한 후에 포스팅하는 편인데 스크린샷이 상당히 많이 나와 포스팅하기 앞서 걱정이 드네요.

 

처음에는 NDK, JNI 관련 프로젝트를 진행하게 될 것 같아 여럿 찾아보다가, 자료가 너무 중구난방이라 정리하게 되었습니다.

 

1. 프로젝트 생성

 

프로젝트는 '안드로이드 스튜디오'를 이용하여 SDK는 '마쉬멜로우 (안드로이드 6.0)' 언어는 '자바'로 선택하였습니다.

그 이외에 Acitivity는 'Empty'로 보통 일반적인 앱을 작성하는 환경으로 시작하였습니다.

 

2. NDK 설정

 

NDK의 시작과 끝이라고도 할 수 있는 NDK설정을 시작해보도록 하겠습니다.

 

Tools -> SDK Manager

먼저 생성된 프로젝트에서 Tools -> SDK Manager로 들어가 주세요.

 

NDK가 설치되었다면 상관이 없지만, 설치가 되지 않으셨다면 '체크' 후 Apply -> OK 하고 설치를 완료해주세요.

 

NDK 설치과정

 

위의 설치 과정을 모두 마치셨다면, Gradle Scripts 하위의 'local.properties'에 ndk.dir를 추가해주세요.

일반적으로 설치하셨다면 C -> Users -> [사용자] -> AppData -> Local -> Android -> Sdk -> ndk -> [리비전]

해당 경로에 위치해있습니다.

 

사진이 작아서 잘 안보이 실수 있지만, build.gradle에 들어가셔서 android 하위에 'ndkVersion "22.1.7171670"'를 추가해주세요.

NDK VERSION은 사용자마다 다르실 수 있으니 참고 바랍니다.

 

File -> Project Structure

그리고, File -> Project Structure에 들어갑니다.

 

해당 경로가 잡혀있다면 프로젝트의 NDK설정이 끝이 났습니다.

해당 경로가 잡혀있지 않다면, 아까 말씀드린 일반적인 NDK설치경로를 찾아서 지정해주시면 됩니다.

(C -> Users -> [사용자] -> AppData -> Local -> Android -> Sdk -> ndk -> [리비전])

 

이렇게 NDK 설정은 마무리됩니다.

 

3. 안드로이드 앱 환경 설정

 

JNI로 CPP소스코드를 통해 'Hello_ndk'를 출력시켜보도록 하기 위해, 레이아웃을 설정하였습니다.

 

activity_main.xml

activity_main.xml에 code환경으로 진입해주세요.

 

기존 환경에 존재하는 TextView에 ID값만 부여해주었습니다.

android:id="@+id/text"

 

그리고 기본 생성된 MainActivity에 TextView값을 선언 및 사용 설정하였습니다.

 

1
2
text = (TextView) findViewById(R.id.text);
text.setText("텍스트  설정);
cs

 

4. NDK 메서드 설정

 

이제 기본적인 환경설정은 모두 끝이 났습니다.

이제 본격적으로 NDK 관련된 설정을 진행해 보도록 하겠습니다.

 

기본적으로 NDK는 자바 소스를 기반으로 자바에서 C를 읽을 수 있게 도와주는 헤더 파일을 생성합니다.

헤더 파일을 생성하려면 내가 어떠한 메서드를 사용할 것인지를 알아야 하고, 미리 작성을 해두어야 합니다.

 

저는 간단하게, CPP에 String 값을 넘겨 TextView를 찍어보는 소스코드를 작성해 보았습니다.

기본적으로 자바가 CPP 연동할 JNI헤더 파일을 만들 때 public native 함수명을 파악하고 생성을 합니다.

위 스크린샷과 아래 스크립트 코드를 참고해보시면 알 수 있습니다.

 

1
2
3
4
5
6
static {
        System.loadLibrary("helloNdk");
    }
public native String print_ndk(String text);
 
String print = print_ndk("hello_ndk");
cs

 

System.loadLivrary libName은 직접 자바에 연결할 CPP의 라이브러리 이름을 정하게 되어있습니다.

임의로 작성하셔도 상관없습니다.

 

5. JNI 헤더 생성 툴 설정

 

이제 자바와 C 및 C++를 연결해주는 헤더 파일을 생성해보겠습니다.

기본적으로 JDK를 설치하게 되면 가지고 있습니다.

javah.exe -> 해당 파일이 헤더 파일을 생성해주는 것을 도와줍니다.

 

jni 폴더 생성

먼저, 패키지 단위를 Projcet로 변경하여, main 및에 jni라는 폴더를 생성해줍니다.

 

File -> Settings

jni 폴더 생성이 끝이 났다면 File -> Settings에 들어갑니다.

 

Tools -> External Tools에 들어가셔서 '+' 버튼을 눌러서 헤더를 생성할 수 있는 외부 툴을 만들어 보겠습니다.

 

Name : javah

Program : JDK경로 하위의 bin폴더 및 javah.exe를 찾아 설정해줍니다.

Arguments : -classpath "$Classpath$" -v -jni $FileClass$

Working directory : 앱의 방금 만든 jni폴더를 지정해줍니다.

 

(직접 해당 툴을 사용할 때 말씀을 드리겠지만, 결과적으로 해당 javah 외부 툴을 생성 후 호출하게 되면 클래스 경로를 파악하여 native 메서드를 기반한 헤더 파일을 jni폴더에 생성하게 됩니다.)

 

생성이 완료되었다면, Apply -> OK 처리해주세요.

JNI연결 헤더 파일을 완성할 툴 설정을 완료하였습니다.

 

6. JNI 헤더 생성 하기

 

이제 생성한 툴을 이용하여 C/C++ 헤더 파일을 생성해보겠습니다.

MainActivity에 오른쪽 마우스 External Tools -> javah

JNI폴더에 헤더 파일이 생성이 완료됩니다.

 

[에러] 팁

간혹 한 번에 헤더 파일 생성에 안되시는 분이 있을 겁니다.

보통 위와 같은 에러를 출력합니다.

 

그렇다면, 당황하지 마시고 프로젝트 리빌드 후에 다시 시도하시면 정상적으로 생성됩니다.

 

정상적인 로그 출력과 생성된 모습을 확인할 수 있습니다.

 

7. C 및 C++ 소스 코드 작성

 

먼저 C/C++ 소스코드를 작성하기 앞서서, 헤더 파일을 잠깐 열어서 확인해보겠습니다.

파일 하단에 MainActivity.java파일에 작성된 native코드 기반으로 메서드가 생성된 것을 확인할 수 있습니다.

(해당 문법은 JNI문법입니다.)

 

이제 헤더에 생성된 메서드를 기반한 코드를 완성시켜줘야 합니다.

(Method Overriding 느낌의..)

 

먼저 jni폴더에서 New -> C/C++ Source File을 눌러 파일명은 'hello_ndk'로 생성하였습니다.

 

 

1
2
3
4
5
6
#include<com_example_hello_ndk_MainActivity.h>
 
JNIEXPORT jstring JNICALL Java_com_example_hello_1ndk_MainActivity_print_1ndk
  (JNIEnv *env, jobject thiz, jstring text) {
    return text;
}
cs

 

소스코드를 헤더 파일에 기반하여 작성하였습니다.

먼저 javah툴로 생성한 헤더 파일을 include 해주었고, 헤더 하단의 코드를 기반하여 작성하였습니다.

 

저는 바로 jstring text를 변수를 지정하여 리턴 처리하였습니다.

 

여기서 간단히 JNIEnv, jobject에 관하여 간단히 말씀을 드리겠습니다.

 

JNIEnv*와 jobject는 JNI로부터 받은 변수이므로 함수에 꼭 포함시켜서 사용하여야 합니다.

env는 JNI에 가장 중요한 구조체 포인터로 VM에 대한 인터페이스를 포함합니다.

jobject는 클래스의 정보들이 들어가 있습니다. 예를 들어 현재 소스로 판단한다면 print_ndk라는 함수를 포함하는 클래스를 참조하고 있습니다.

 

그 외 jstring은 제가 MainActivity.java를 보셔서 아시겠지만 매개변수로 지정한 값입니다.

 

이제 모든 준비는 끝이 났습니다.

마무리로 빌드 과정과 구동으로 마무리하겠습니다.

 

8. NDK빌드 툴 생성 및 Android Native Gradle 연결 후 실행하기

 

먼저, 최종 작성한 네이티브 소스를 빌드를 빌드하여야 합니다.

이 과정을 앱 빌드할 때 같이 자동 빌드 하기 위한 설정 방법을 가이드해보겠습니다.

 

위 사진과 같이 5번 목차에서 헤더 파일 생성 툴을 만들었던 메뉴인 File -> Settings -> External Tools에 들어갑니다.

그리고 똑같이 '+' 버튼을 눌러서 빌드 툴을 생성합니다.

 

이번에는 헤더 생성 툴에선 JDK를 사용했던 것과 달리 NDK를 사용합니다.

(NDK빌드니깐요.)

 

Name: 저는 ndk-build로 지정하였습니다.

Program: NDK경로 -> ndk-build.cmd를 지정해주세요.

Working directory: app소스 main으로 지정해주시면 됩니다.

 

그리고 OK를 눌러주세요.

 

이제 ndk-build를 그대로 복사해서 clean도 만들어줍니다.

 

clean은 ndk-build와 설정값은 동일하지만,

Arguments: clean 입력해주세요.

ndk-build는 네이티브 파일을 빌드하는 역할을 한다면, clean은 project clean과 비슷한 개념으로 빌드된 파일을 정리한다고 생각하시면 됩니다.

 

이렇게 외부 툴 2가지 생성을 마치셨다면, 거의 마지막 단계인 앱 빌드 Gradle과 연동을 시켜줄 준비를 하겠습니다.

 

 

먼저 연동을 위해서 Android.mk라는 파일과 해당 파일의 설정값이 필요합니다.

jni폴더 내부에 생성하겠습니다.

 

New -> File -> Android.mk

 

Android.mk 내부 파일에 위 사진과 같이 작성해주세요.

 

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := helloNdk
FILES := hello_ndk.cpp
LOCAL_SRC_FILES := $(FILES)
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)

 

여기서 빨간색으로 표시한 부분만 본인이 설정한 값과 맞게 설정하면 됩니다.

*LOCAL_MODULE은 4번 목차에 libName으로 지정한 이름을 지정하셔야 합니다.

그리고 FILES는 연동할 C/C++파일명을 입력해주세요.

 

Android.mk는 jni하위 디렉터리에 존재해야 하고, 빌드 시스템의 소스 및 공유 라이브러리를 설명합니다.

실제로 빌드할 때 한번 이상 파싱 하는 작은 GNU makefile 플래그먼트입니다.

 

더 상세한 설명 및 설정을 하실분은 아래 링크를 확인해주세요.

*developers NDK Android.mk*

 

 

Android.mk까지 생성에 마쳤다면, Gradle과 연결해줍니다.

MainActivity 우클릭 후 'Link C++ Project with Gradle'을 선택해줍니다.

 

해당 설정값이 나오게 되고,

Build System은 기존 만들었던 ndk-build 툴을 설정하시고

Project path는 Android.mk를 지정해주세요.

 

그리고, OK를 눌러주세요.

 

 

OK를 누르면 Gradle에 해당 코드가 작성이 됩니다.

그리고, 실행해줍니다.

 

저는 가상 모듈로 실행하였습니다.

 

빌드 과정 중 NativeBuild도 진행된 것을 확인할 수 있었습니다.

 

MainActivity에서 리턴했던 'hello_ndk'를 볼 수 있었습니다.

 

추가적으로, 자동화 빌드 말고 수동 빌드를 하게 되는 경우에 대해 말씀드리겠습니다.

 

수동으로 External Tools -> ndk-build를 하게 된다면, 해당 네이티브 빌드만 진행이 됩니다.

 

그리고, Working Directory로 지정했던 경로에. so파일이 생성된 것을 볼 수 있습니다.

또한, SharedLibrary에도 함께 올라간 것을 확인할 수 있습니다.

 

이상으로, 안드로이드 NDK&JNI C/C++ 사용하기를 마치겠습니다.

 

사진 무단 도용은 금지합니다!

도움이 되셨다면 아래 '공감', '하트' 버튼을 눌러주세요!

감사합니다.


반응형

+ Recent posts