standwally

Java에서 C++함수를 호출 본문

프로그래밍/Cocos2d-x

Java에서 C++함수를 호출

standwally 2013. 12. 6. 10:36

Java에서 C++함수를 호출하는 방법에 대해 알아보자.

먼저, 임의의 Test.java파일에서 호출하고자하는 C++에 있는 함수를 아래와 같이 선언해준다.

Test.java
1
2
3
4
5
package com.hyonga.hello;
  
public class Test extends Cocos2dxActivity {
    private native boolean setContentType(int a);
}

라인 4에서, 메서드 앞에 native라는 키워드는 NDK로 빌드할 때, 해당 메서드는 C++에 정의되어 있는 함수라는 것을 컴파일러에게 알려준다.

 

임의의 Hello.cpp 파일에서는 호출하고자 하는 함수를 아래와 같이 정의해준다.

Hello.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "platform/android/jni/JniHelper.h"
  
#ifdef __cplusplus
extern "C" {                                                                                         
#endif
    jboolean Java_com_hyonga_hello_Test_setContentType(JNIEnv *env, jobject obj, jint a)
    {
        CCLog("Test : Java에서 호출한 .so 라이브러리 안에 있는 함수");
        return true;
    }
#ifdef __cplusplus
}
#endif
#endif

라인 1, 2에서, 타켓 플랫폼이 Android일 경우에만 Cocos2d-x에서 제공하는 JniHelper.h 파일을 include 해줌.

라인 4, 5에서, C++ 컴파일시, 컴파일러에게 연결하고자하는 함수를 C형식 심볼 이름으로 해석하게끔 extern "C"를 사용해줌.

라인 7에서, 함수 작성 방식은

[리턴타입] Java_[패키지명]_[클래스명]_[함수명](JNIEnv *env, jobject obj, [매개변수])

와 같이 해준다. 여기서 함수명 안에는 '_' 문자가 들어가서는 안된다.

그리고, 패키지명과 클래스명이 일치해야 Java에서 호출이 가능하다.

 

여기까지, Java에서 C++함수 호출에 대한 인터페이스이다.

 

그렇다면, Java에서 Cocos2d-x의 임의의 Scene을 제어해야 하는 경우를 생각해보자.

예를 들어, 이런 경우가 있을 것이다.

메인 UI를 안드로이드로 개발하고, 메인 UI에서 임의의 메뉴 버튼을 클릭하여 Cocos2d-x를 로딩할때, 로딩되는 Scene의 두번째 화면을 뿌려달라는 요청을 보내는 경우를 가정해보자.

 

아래는 위와 같은 경우를 테스트하기 위해 구현된 소스이다.

Test.java
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
package com.hyonga.hello;
  
public class Test extends Cocos2dxActivity {
 
    public static String Tag = "Java Activity Log";
 
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
    }
    static {
        Log.d(Tag, String.format("Test : load .so file"));
        System.loadLibrary("game");                       // .so 라이브러리 파일 불러옴.
    }
 
    private native boolean setContentType(int a);       // 호출할 native 함수 선언
    protected void onResume() {
        super.onResume();
        Log.d(Tag, String.format("Test : onResume"));
 
        boolean bSuccess = setContentType(2);           // Jni 함수 호출하는데, 매개변수 integer값 2를 전달해줌.
        if(bSuccess) {
            Log.d(Tag, String.format("Test : C++'s Method 호출 성공"));
        }
    }
}

라인 12에서, 현재 액티비티에서 사용할 Scene의 .so파일을 불러온다.

라인 20에서, native 함수를 호출하는데, 현재 액티비티의 화면이 그려지고 나서 호출되는 onResume 메서드 안에서 호출해준다.

 

.cpp에서는 해당 Scene이 화면에 그려지기 바로 직전에 호출되는 onEnter 함수에서 Java에서 넘겨준 값이 제대로 찍히는지 확인해보기 위해 아래와 같이 구현하였다.

Hello.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
static Hello *g_pHello = NULL;
 
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "platform/android/jni/JniHelper.h"
  
#ifdef __cplusplus
extern "C" {                                                                                         
#endif
    jboolean Java_com_hyonga_hello_Test_setContentType(JNIEnv *env, jobject obj, jint a)
    {
        CCLog("Test : Java에서 호출한 .so 라이브러리 안에 있는 함수");
 
        if (g_pMainMenu != NULL) {
            g_pMainMenu->setTypeContent(a);
        }
 
        return true;
    }
#ifdef __cplusplus
}
#endif
#endif
 
Hello::Hello()
{
    g_pHello = this;
}
 
Hello::~Hello()
{
    g_pHello = NULL;
}
 
/*...중략...*/
 
void Hello::setTypeContent(unsigned int value)
{
    m_uTypeContent = value;
}
 
void Hello::onEnter()
{
    CCLayer::onEnter();
 
    CCLog("Test : onEnter(Java로부터 넘겨받은 값 : %d)", m_uTypeContent);
}

 

라인 1에서, 현재 클래스 인스턴스의 포인터를 저장할 전역변수를 선언해주고, 생성자에서 해당 포인터를 받아온다.

라인 14에서, 클래스 인스턴스의 멤버변수 값을 변경해주는 함수 setTypeContent를 호출해준다.

 

그러나, 로그에 찍힌 값을 확인한 결과, Java에서 전달했던 원래 값 2가 아닌 쓰레기 값이 출력되었다.

아무래도, g_pHello 변수에 포인터가 제대로 저장되지 않은듯 하여, Java 액티비티에서 .so 파일을 로드하고 Jni 호출이 발생하기까지의 lifecycle을 확인해보았다.

로그를 확인해보면,

.so파일을 로드하고나서 클래스 인스턴스의 생성자를 호출하기전에 액티비티의 onResume에서 Jni 호출이 먼저 발생하는 것을 확인할 수 있다.

그렇기 때문에, 멤버변수 값 변경 함수를 호출을 실패한 것이다.

이를 해결하기 위해서는,

 - 별도의 전역변수를 사용하거나

 - onResume 메서드에서 Jni 호출하는 시점을 변경하는

방법들이 있겠다.

 

이와 같은 문제는, In App Purchase 구매여부를 판단하여, UI를 그에 맞게 화면에 표시해주는데해도 동일하게 적용되는 문제이기 때문에,

이러한 lifecycle의 이해가 중요할꺼란 생각이 든다.