안드로이드 프레임워크 프로그래밍(8) [JNI에서 작성된 C++ 코드에서 C 코드 함수 호출하기]

안드로이드/프레임워크 2015. 2. 16. 23:31

 안드로이드 소스코드를 추가하는 과정에서 기존에 작성된 소스코드를 수정하거나 새로 작성된 소스코드를 추가하는 등의 작업을 하는 경우가 있습니다. 기존에 작성된 코드를 수정하는 경우 약간의 응용만 거치면 되니 상당히 쉬운 일입니다만 새로 작성된 소스코드를 추가하는 경우 예상치 못한 경우들이 발생하는 사태가 벌어집니다. 특히 C언어에 익숙하신 분이 C로 구성된 JNI 코드를 안드로이드 프레임워크의 Native System에 적용하려 했더니 C++로 작성된 코드들 사이에 끼워 넣는 과정이 상당히 복잡할 겁니다.


 이번 포스팅에서는 안드로이드 Native 프레임워크에 작성된 C++ 코드들 사이에 C로 작성된 새로운 System Service의 Native 코드를 작성한 후 이  C코드를 기존에 C++로 작성된 코드에서 호출할 수 있는 환경을 구성해 보도록 하겠습니다.


※본 포스팅은 이전에 작성된 JNI 코드를 활용한 예제를 확장하여 다루어 볼 예정입니다. 안드로이드 JNI에 대해 자세히 알고 싶으신 분이나 이전에 작성된 코드에 대해 알고 싶으신 분은 아래 링크를 참조해 주시기 바랍니다.

http://elecs.tistory.com/71


 

 Framework에 Java로 작성된 System Service에 대응하는 Native 코드를 작성한 후 이를 JNI 폴더에 저장만 한다고 해서 해당 Native 코드를 곧바로 사용할 수 없습니다. 해당 폴더 내에 있는 onload.cpp에 해당 코드 내에 있는 함수를 등록해야 사용할 수 있는데 만약 자신이 작성한 코드가 C일 경우 바로 연동할 수 없어 조금은 난감한 일이 벌어집니다.


아래 소스코드는 기존에 JNI로 만든 안드로이드 Native Service 코드입니다.


/frameworks/base/services/jni/com_android_server_TestService.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "jni.h"
#include "JNIHelp.h"
#include <string.h>
#include <stdio.h>
 
static void android_server_TestService_nativeCallJava(JNIEnv* env, jobject thiz){
 
    jclass jCallJava = (*env)->FindClass(env, "android/view/InputEventReceiver");
    jmethodID callJavaMethod = (*env)->GetStaticMethodID(env, jCallJava, "callJavaMethod""()V");
    (*env)->CallStaticVoidMethod(env, jCallJava, callJavaMethod);
 
}
 
static jstring android_server_TestService_stringFromJNI(JNIEnv* env, jobject thiz){
    return (*env)->NewStringUTF(env, "Hello, JNI World!");
}
cs



아래 소스코드는 Native의 함수를 시스템에 등록하게 해주는 소스코드인 onload.cpp입니다.
/frameworks/base/services/jni/onload.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"
 
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputApplicationHandle(JNIEnv* env);
int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_AssetAtlasService(JNIEnv* env);
};
 
using namespace android;
 
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;
 
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("GetEnv failed!");
        return result;
    }
    ALOG_ASSERT(env, "Could not retrieve the env!");
 
    register_android_server_PowerManagerService(env);
    register_android_server_SerialService(env);
    register_android_server_InputApplicationHandle(env);
    register_android_server_InputWindowHandle(env);
    register_android_server_InputManager(env);
    register_android_server_LightsService(env);
    register_android_server_AlarmManagerService(env);
    register_android_server_UsbDeviceManager(env);
    register_android_server_UsbHostManager(env);
    register_android_server_VibratorService(env);
    register_android_server_SystemServer(env);
    register_android_server_location_GpsLocationProvider(env);
    register_android_server_location_FlpHardwareProvider(env);
    register_android_server_connectivity_Vpn(env);
    register_android_server_AssetAtlasService(env);
    register_android_server_ConsumerIrService(env);
 
 
    return JNI_VERSION_1_4;
}
 
cs


 위 두 코드를 통해 현재 C와 C++ 소스코드의 차이를 확연히 아실 수 있을 겁니다. C로 작성되었다면 extern을 활용해 외부의 함수를 호출할 수 있는 방법이 있겠습니다만, C++의 경우 namespace 라는 개념까지 가지고 있기 때문에 C++ 코드를 C코드 짜듯이 작성했다간 error가 작렬하는 상황이 벌어집니다.


 C++에서 C코드를 호출하는 것을 구현하기 위해 우선 두 소스코드가 공유하는 header 파일을 작성합니다.


/frameworks/base/services/jni/com_android_server_TestService.h

1
2
3
4
5
6
7
8
9
#include "jni.h"
#include "JNIHelp.h"
 
#ifndef _ANDROID_SERVER_TESTSERVICE_H
#define _ANDROID_SERVER_TESTSERVICE_H
 
int register_android_server_TestService(JNIEnv* env);
 
#endif
cs


작성된 header 파일을 각 소스코드에 include 해 주신 후 C++ 코드로부터 호출될 C 코드를 구현해 봅니다.


/frameworks/base/services/jni/onload.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "jni.h"
#include "JNIHelp.h"
#include "com_android_server_TestService.h"
#include <string.h>
#include <stdio.h>
#include <utils/Log.h>
 
static void android_server_TestService_nativeCallJava(JNIEnv* env, jobject thiz){
    jclass jCallJava = (*env)->FindClass(env, "android/view/InputEventReceiver");
    jmethodID callJavaMethod = (*env)->GetStaticMethodID(env, jCallJava, "callJavaMethod""()V");
    (*env)->CallStaticVoidMethod(env, jCallJava, callJavaMethod);
 
}
 
static jstring android_server_TestService_stringFromJNI(JNIEnv* env, jobject thiz){
    return (*env)->NewStringUTF(env, "Hello, JNI World!");
}
 
/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "nativeCallJava""()V", (void*)android_server_TestService_nativeCallJava },
    { "stringFromJNI""()Ljava/lang/String;", (void*)android_server_TestService_stringFromJNI },
};
 
int register_android_server_TestService(JNIEnv* env)
{
    int res = jniRegisterNativeMethods(env, "com/android/server/TestService",
            gMethods, NELEM(gMethods));
 
    return 0;
}
cs

C 코드 내부에서 C++ 코드에서 호출될 int register_android_server_TestService(JNIEnv* env) 함수를 구현하였습니다. 이제 해당 함수를 C++에서 불러들이는 방법을 알아보도록 하겠습니다.


 C와 C++이 서로의 함수를 바로 사용할 수 없는 가장 큰 이유는 코드를 컴파일 할 때 C와 C++이 자신의 함수를 Linking 하는 방식이 다르기 때문입니다. C++의 경우 Overriding이 가능하여 같은 이름을 가진 함수 여러 개를 만들 수 있으나 C의 경우 단 하나의 유일한 이름의 함수를 가질 수 밖에 없습니다. 이렇게 기본적인 개념에 대한 차이가 크기 때문에 C++에서 C로 작성된 코드를 사용하기 위해서는 extern "C"를 사용해야 합니다.


1
2
3
extern "C" {
#include "com_android_server_TestService.h"
}
cs

 위의 경우 헤더파일이 C로 작성되어 있는데 해당 헤더파일이 C++에 적용되면 C++ 문법에 기반하여 컴파일이 동작하기 때문에 C로 작성된 함수를 호출할 수 없습니다. C++ 코드 내에 extern "C"라 작성된 부분은 해당 부분은 C언어에서 처럼 동작시키겠다는 의미로 동작하게 되므로 코드 내부에 해당 Header 파일을 위와 같은 방식으로 적용해 주시면 되겠습니다.


아래는 위에 작성된 코드를 적용한 후 C 소스코드를 추가하여 호출이 가능하도록 짜여진 코드입니다.

/frameworks/base/services/jni/com_android_server_TestService.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include "JNIHelp.h"
#include "jni.h"
extern "C" {
#include "com_android_server_TestService.h"
}
 
#include "utils/Log.h"
#include "utils/misc.h"
 
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputApplicationHandle(JNIEnv* env);
int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_AssetAtlasService(JNIEnv* env);
};
 
using namespace android;
 
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;
 
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("GetEnv failed!");
        return result;
    }
    ALOG_ASSERT(env, "Could not retrieve the env!");
 
    register_android_server_PowerManagerService(env);
    register_android_server_SerialService(env);
    register_android_server_InputApplicationHandle(env);
    register_android_server_InputWindowHandle(env);
    register_android_server_InputManager(env);
    register_android_server_LightsService(env);
    register_android_server_AlarmManagerService(env);
    register_android_server_TestService(env);
    register_android_server_UsbDeviceManager(env);
    register_android_server_UsbHostManager(env);
    register_android_server_VibratorService(env);
    register_android_server_SystemServer(env);
    register_android_server_location_GpsLocationProvider(env);
    register_android_server_location_FlpHardwareProvider(env);
    register_android_server_connectivity_Vpn(env);
    register_android_server_AssetAtlasService(env);
    register_android_server_ConsumerIrService(env);
 
 
    return JNI_VERSION_1_4;
}
cs




위와 같이 코드를 작성하시면 C++에서 C로 작성된 코드를 손쉽게 사용할 수 있도록 구성하실 수 있습니다.

300x250