iOS용 ANE 라이브러리 만들기

연재순서

  1. ANE 살펴보기
  2. iOS용 ANE 라이브러리 만들기
  3. Android용 ANE 라이브러리 만들기
  4. 연결 SWC 만들기
  5. ANE 빌드와 테스트 하기

작업환경

자 이제 본격적으로 ScreenWakeUp ANE 용 iOS 네이티브 라이브러리를 만들어 보도록 하겠습니다. 제 작업 환경은 아래와 같습니다.

  • Mac OS 10.7.4
  • Flex SDK 4.6.0 for AIR3.3
  • Xcode 4.3.3

저는 주로 모바일용을 만들고 있기 때문에 위와 같은 환경입니다. 사용자 마다 환경은 조금씩 다를 수 있는데 AIR3.3 이상의 Flex SDK만 있다면 ANE 를 만드는데 크게 상관은 없습니다. 일단 제가 가지고 있는 시스템이 맥이다 보니 맥을 기준으로 글을 작성 할 예정입니다.

소스 다운로드

Sources in GitHub

시작점 만들기

개발 폴더 구조

위의 개발 폴더 구조는 제가 다른 여러 ANE 제작자들의 폴더구조를 보고 만든 방법입니다. Android 와 Xcode 는 각 네이티브 라이브러리를 만들 프로젝트 폴더이고 SWC는 연결 SWC, Flash 폴더는 테스트 플래시 프로젝트 폴더입니다 마지막으로 build는 각 네이티브 라이브러리, SWC 그리고 extension.xml 을 넣어서 빌드를 하는 폴더 입니다. 위의 구조는 임의 이므로 편하신대로 다시 세팅하여도 무방합니다.

그럼 Xcode 프로젝트를 생성해 보도록 하겠습니다.

Cocoa Touch Static Library 프로젝트 생성

File -> New -> Project… 메뉴를 통해서 Cocoa Touch Static Library 프로젝트를 생성합니다.

프로젝트 이름 입력

프로젝트 이름을 입력하고 프로젝트 생성을 완료 합니다.

FlashRuntimeExtenshion.h 복사

플래시와 네이티브 라이브러리를 연결하기 위해 사용 할 각종 함수와 정의가 있는 FlashRuntimeExtenshion 헤더 파일을 복사합니다. 이 파일은 보통 {Flash Builder 설치 경로}/sdks/{SDK 버전}/include 폴더 안에 위치 합니다.

프로젝트 폴더로 복사

FlashRuntimeExtenshion.h 파일을 좀전에 만든 프로젝트 하위 폴더에 복사 합니다.

프로젝트에 헤더파일 추가

헤더파일 선택

Xcode 프로젝트는 폴더에 파일을 복사해도 프로젝트에서 자동으로 인식 하지 않으므로 위와 같은 방법을 프로젝트에 복사한 FlashRuntimeExtenshion.h 을 등록합니다.

코드작성

이제 iOS용 ANE 라이브러리를 만들 준비가 전부 되었으므로 실제 코드를 살펴 보도록 하겠습니다. 지난 시간에 광고한대로 이번 시간부터는 ScreenWakeUp 이라는 ANE를 직접 만들고 소스를 분석해 보도록 하겠습니다. ScreenWakeUp 은 스마트폰의 화면자동 잠금 기능을 무효화 하고 화면이 계속 켜져 있도록 해주는 ANE 입니다.

<br />
#import &lt;Foundation/Foundation.h&gt;<br />
#import &lt;UIKit/UIKit.h&gt;<br />
#import &quot;FlashRuntimeExtensions.h&quot;</p>
<p>@interface ScreenWakeUp : NSObject</p>
<p>@end<br />

Xcode 프로젝트를 생성하고 나면 자동으로 생성되는 ScreenWakeUp.h 파일에 FlashRuntimeExtensions.h 파일을 임포트 합니다. 이제 앞으로 ScreenWakeUp.h 를 임포트하는 ScreenWakeUp.m 파일에서 FlashRuntimeExtensions.h 에 정의된 모든 기능을 사용 할 수 있습니다.

그럼 바로 ScreenWakeUp.m 전체소스를 보고 부분씩 살펴 보겠습니다.

<br />
#import &quot;ScreenWakeUp.h&quot;</p>
<p>@implementation ScreenWakeUp</p>
<p>@end</p>
<p>FREObject isSupported(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[] ){<br />
    FREObject retVal;<br />
    if(FRENewObjectFromBool(YES, &amp;retVal) == FRE_OK){<br />
        return retVal;<br />
    }else{<br />
        return nil;<br />
    }<br />
}</p>
<p>FREObject lock(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[] ){<br />
    //Temporary values to hold our actionscript code.<br />
    uint32_t boolean;</p>
<p>    //Turn our actionscrpt code into native code.<br />
    FREGetObjectAsBool(argv[0], &amp;boolean);</p>
<p>    if(boolean){<br />
        [[UIApplication sharedApplication] setIdleTimerDisabled:YES];<br />
    } else {<br />
        [[UIApplication sharedApplication] setIdleTimerDisabled:NO];<br />
    }<br />
    return nil;<br />
}</p>
<p>void ContextInitializer(void* extData, const uint8_t * ctxType, FREContext ctx,<br />
                        uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet)<br />
{<br />
    int count=2;</p>
<p>    *numFunctionsToTest = count;<br />
    FRENamedFunction* func = (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * count);</p>
<p>    func[0].name = (const uint8_t *) &quot;isSupported&quot;;<br />
    func[0].functionData = NULL;<br />
    func[0].function = &amp;isSupported;</p>
<p>    func[1].name = (const uint8_t *) &quot;lock&quot;;<br />
    func[1].functionData = NULL;<br />
    func[1].function = &amp;lock;</p>
<p>    *functionsToSet = func;<br />
}</p>
<p>void ContextFinalizer(FREContext ctx) {<br />
	return;<br />
}</p>
<p>void ExtInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet,<br />
                    FREContextFinalizer* ctxFinalizerToSet) {<br />
    *extDataToSet = NULL;<br />
    *ctxInitializerToSet = &amp;ContextInitializer;<br />
    *ctxFinalizerToSet = &amp;ContextFinalizer;<br />
}</p>
<p>void ExtFinalizer(void* extData) {<br />
    return;<br />
}<br />

처음 보면 다소 복잡해 보이는 코드이지만 하나하나 나눠서 보면 그리 어렵지 않습니다. 먼저 확인해햐 하는 함수는 ExtInitializer 입니다.

<br />
void ExtInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet,<br />
                    FREContextFinalizer* ctxFinalizerToSet) {<br />
    *extDataToSet = NULL;<br />
    *ctxInitializerToSet = &amp;ContextInitializer;<br />
    *ctxFinalizerToSet = &amp;ContextFinalizer;<br />
}<br />

이는 extension.xml 에 명시되어진 함수로 처음 context 가 만들어지면서 호출 되는 함수 입니다. 인자로 extDataToSet, ctxInitializerToSet, ctxFinalizerToSet 의 포인터를 받습니다. 시작하자마자 멘붕이 오기 시작합니다. * 은 뭐고 & 는 뭐고 심지어 ** 은 뭔가요? *을 저리 많이 쓴걸 보니 뭔가 중요한건가 봅니다. ㅎㅎㅎ 이걸 제대로 이해하기 위해서는 직업을 c 개발자로 전향 해야 될지도 모르니 의미 만 간단히 살펴보면 걍 ContextInitializer 와 ContextFinalizer 를 참조하겠다 입니다. 그리고 당연하게 필요한 시기에 호출해서 사용하겠지요. 그 시기란 AIR런타임이 context 를 만드는 시점 일 겁니다. 여튼 이 함수를 통해서 AIR와 네이티브간의 첫 접선이 이루어졌고 우리는 자연스럽게 ContextInitializer 함수가 실행 될 것이라는 걸 추측 할 수 있습니다.

<br />
void ContextInitializer(void* extData, const uint8_t * ctxType, FREContext ctx,<br />
                        uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet)<br />
{<br />
    int count=2;</p>
<p>    *numFunctionsToTest = count;<br />
    FRENamedFunction* func = (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * count);</p>
<p>    func[0].name = (const uint8_t *) &quot;isSupported&quot;;<br />
    func[0].functionData = NULL;<br />
    func[0].function = &amp;isSupported;</p>
<p>    func[1].name = (const uint8_t *) &quot;lock&quot;;<br />
    func[1].functionData = NULL;<br />
    func[1].function = &amp;lock;</p>
<p>    *functionsToSet = func;<br />
}<br />

ContextInitializer 함수의 주기능은 실제로 사용하게 될 함수를 등록하는 일입니다. 이 시점에 우리는 네이티브 코드를 위해 context 를 변수에 저장 해 둘 수 도 있습니다.

위의 코드에서 주의깊게 봐야 할 부분은 먼저 int count=2 입니다. 소스를 잘 보시면 count는 numFunctionsToTest 와 functionsToSet 을 위한 func 생성시 메모리 크기 또한 지정해 주고 있습니다. 메모리 관리에 대한 개념이 별로 없는 플래시 개발자들이 C언어에서 가장 많이 힘들어 하는 부분입니다. count를 2로 생성했으면 이후에 오는 배열크기도 이와 반드시 동일 해야 합니다. 즉 func[3] 와 같이는 사용 할 수 없습니다.

func 구조체의 name 인자는 플래시에서 ExtensionContext call() 호출시 사용 하는 이름 입니다. functionData 는 function 실행시 전달되는 데이터 입니다. 그닥 사용 할 일은 많지 않을 것 같습니다. function은 실제로 호출될 함수에 대한 참조 입니다.

그리고 추가적으로 함수인자로 넘어오는 ctxType 은 연결 연결 SWC 에서 context를 생성하는 함수인 ExtensionContext.createExtensionContext([extensionId], [contextTyoe]); 실행시 contextType 에 들어가는 인자로 하나의 ANE 에서 다른 종류의 기능인 있는 context 를 만들 수 있게 해줍니다.

<br />
FREObject lock(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[] ){<br />
    //Temporary values to hold our actionscript code.<br />
    uint32_t boolean;</p>
<p>    //Turn our actionscrpt code into native code.<br />
    FREGetObjectAsBool(argv[0], &amp;boolean);</p>
<p>    if(boolean){<br />
        [[UIApplication sharedApplication] setIdleTimerDisabled:YES];<br />
    } else {<br />
        [[UIApplication sharedApplication] setIdleTimerDisabled:NO];<br />
    }<br />
    return nil;<br />
}<br />

실제로 우리가 원하는 기능을 구현할 함수부 입니다. 먼저 전달 인자 부터 보겠습니다. ctx는 계속 나오고 있는 context 입니다. funcData는 좀전에 ContextInitializer 에서 func 생성시 지정한 funcData 입니다. argc는 플래시로부터 넘어오는 인자 argv 배열의 갯수 입니다. argv는 플래시에서 실제로 넘어노는 인자값 배열 입니다.

<br />
    //Temporary values to hold our actionscript code.<br />
    uint32_t boolean;</p>
<p>    //Turn our actionscrpt code into native code.<br />
    FREGetObjectAsBool(argv[0], &amp;boolean);<br />

플래시에서 넘어온 인자를 네이티브에서 사용하기 위해서는 위의 코드와 같이 지정된 함수를 통해서 네이티브용 값으로 변환해 주어야 합니다. 현재 가져올 수 있는 데이터 타입은 아래의 표와 같습니다.

  • FREAcquireBitmapData()
  • FREAcquireBitmapData2()
  • FREAcquireByteArray()
  • FREGetArrayElementAt()
  • FREGetArrayLength()
  • FREGetObjectAsBool()
  • FREGetObjectAsDouble()
  • FREGetObjectAsInt32()
  • FREGetObjectAsUint32()
  • FREGetObjectAsUTF8()

타입에 따라 사용법은 조금씩 상이 합니다. 그리고 함수에서 결과 값을 반환 받는 방식이 아닌 인자로 들어간 포인터에 값이 저장 되는 형식이 특이해 보입니다. 여담으로 dieBuster 를 방문해 보시면 함수와 인자에 대한 좋을 글을 보실 수 있습니다.

<br />
    if(boolean){<br />
        [[UIApplication sharedApplication] setIdleTimerDisabled:YES];<br />
    } else {<br />
        [[UIApplication sharedApplication] setIdleTimerDisabled:NO];<br />
    }<br />

드디어 정말 우리가 필요로 하는 코드가 나왔습니다. 뭔가 거창하게 시작했는데 너무 싱겁나요? ㅎㅎㅎ iOS에서는 기본적으로 위와 같은 메소드를 제공 하고 있으므로 setIdleTimerDisabled 을 호출 하는 것 많으로 아주 간단하게 우리가 원하는 화면잠금 기능을 구현 할 수 있습니다. 다음 연제에 나올 Android 버전은 조금 더 복잡하니 너무 실망하지 마세요~^^

참고로 제가 테스트 해본결과 ANE 용 라이브러리를 만들때 C, C++ 그리고 Object-C가 위의 코드 처럼 섞여 있어도 문제없이 잘 작동하였습니다.

빌드

빌드경로 수정

간혹 빌드를 한 파일이 안보이는 경우가 있는데 제 경우에는 빌드 설정에서 빌드 패스를 위의 이미지 처럼 상대경로로 설정해 줍니다. 그럼 아래의 이미지 처럼 프로젝트 폴더의 한단계 상위에서 최종적으로 빌드된 파일을 확인 할 수 있습니다.

최종 결과물

정리

지금까지 iOS 용 ANE 네이티브 라이브러리를 만드는 과정에 대해서 알아보았습니다. 간단히 순서를 요약하면 Xcode 에서 Cocoa Touch Static Library 프로젝트를 만든다. FlashRuntimeExtenshion.h 를 프로젝트에 추가한다. ExtInitializer -> ContextInitializer -> 함수 순으로 등록하여 사용한다. 정도로 정리 할 수 있을 겁니다.

이제 ANE를 만들기 위한 첫발을 내딛었습니다. 다음 시간에는 iOS용과는 또다른 구조로 되어있는 Android 용 라이브러리를 만들어 보도록 하겠습니다.

– 참고 –
http://help.adobe.com/en_US/air/extensions/WSb464b1207c184b14-62b8e11f12937b86be4-8000.html

p.s. 우야꼬님도 ANE에 대한 연재를 시작하셨네요. 같이 보시면 더욱 좋을 것 같습니다. Native Extension for iOS 만들기 Part.1 – iOS 개발

Comments (1)

  1. Pingback: ANE 만들기 요약 – iOS |

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.