Building a Native Extension | Part 3 – The iOS Code

The iOS project is the part that, in my opinion, is the hardest.  There are plenty of mistakes that are very easy to accidentally make.  Hopefully this post will help everyone else avoid the same mistakes that I made.

The iOS Library

1.  Open Xcode and make a new project.  In the New Project dialog, select iOS -> Framework & Library project option, then select “Cocoa Touch Static Library”.

2.  You’ll see a .h file and a .m file named after your project.  You don’t need the .h so you can delete that.  After you delete the .h you should empty out the .m file since the default code isn’t needed anymore.  You’ll also want to click on the Project in the bar on the left side and search for “prototype” in the Build Settings.  Toggle the value of “Missing Function Prototypes” to no.  This is just to suppress warnings.

3.  Download the AIR sdk if you haven’t already.  Navigate to “AIR_SDK_DIR/include”.  There is a file named “FlashRuntimeExtensions.h”.  Drag that file into Xcode and drop it onto your project in the left side bar.  This includes the file in your project.  (It doesn’t actually copy the file though, so if you delete/move the original Xcode won’t find it anymore.)

4.  Open the main .m file and add #import "FlashRuntimeExtensions.h" to the top of the file.  Now you can access the AIR runtime classes and get the architecture for the extension set up.

5.  In the main .m file create a method called ContextInitializer.  This method is called when the extension context is created in AIR.  In this method you need to create a collection of functions that the main ActionScript library will call.  Here’s an example:

[sourcecode language=”objc”]
void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet)
{
*numFunctionsToTest = 2;
FRENamedFunction* func = (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * 2);

func[0].name = (const uint8_t*) "init";
func[0].functionData = NULL;
func[0].function = &init;

func[1].name = (const uint8_t*) "setVolume";
func[1].functionData = NULL;
func[1].function = &setVolume;

*functionsToSet = func;
}
[/sourcecode]

You can see that two functions are set up, init and setVolume.  We invoked these methods earlier in the VolumeController class.

6.  In the main .m file create a method called ContextFinalizer.  This is called when the extension context is being garbage collected.  Here you should clean up anything that is used by your native code.

[sourcecode language=”objc”]
void ContextFinalizer(FREContext ctx)
{
return;
}
[/sourcecode]

7.  Create two more methods.  These methods should match the values in the initializer and finalizer nodes in the iPhone-ARM platform of the extension.xml file.  The initializer method will set which methods to call when doing the actual initialization.

[sourcecode language=”objc”]
void VolumeExtensionInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet)
{
*extDataToSet = NULL;
*ctxInitializerToSet = &ContextInitializer;
*ctxFinalizerToSet = &ContextFinalizer;
}

void VolumeExtensionFinalizer(void* extData)
{
return;
}
[/sourcecode]

8.  Create the methods reference in the collection made in step #5.  These methods are where you should add the native code to do whatever it is that your native extension does.

Each method will accept the same parameters.  The main properties you’re worried about are the FREContext, which is extension context, and the collection of FREObject instances, which are the parameters passed to the native function.  Each FREObject is a C representation of an ActionScript object.  The FREObject class is a wrapper, so you have to pull the value out using methods on FREObject.  See the below code for an example:

[sourcecode language=”objc”]
FREObject setVolume(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[])
{
double newVolume;
FREGetObjectAsDouble(argv[0], &newVolume);

[[MPMusicPlayerController applicationMusicPlayer] setVolume: newVolume];

return NULL;
}
[/sourcecode]

9. Generating the compiled library.

Click on the project on the left hand side of the screen.  Click on the “Build Settings” tab.

Do a search for “products path”.  The directory specified in the “Per-configurations Build Projects Path” is where the compiled library will be put.

To change the directory just double click the current path and enter in the one you’d prefer.

After that’s done, press “command + b” which will make Xcode compile the library.  You’ll find a .a file in the directory that was specified above.

A note about debugging:

Debugging in native extensions isn’t the easiest thing, but both IDEs for iOS and Android provide a way to log out to a console.  You won’t be able to hit breakpoints but you can see log statements from the native code.

When using NSLog(NSString) statements the output will show up in the console available through the Xcode Organizer when the AIR application using the extension is running on the device and the device is plugged in via usb.

How to open the console: While Xcode is running, expand the “Window” menu option and select “Organizer”. Find your device in the left-hand list and hit the arrow to expand its contents. Select the “Console” option. The NSLog statements will show up here.

iOS Problems and Trap Doors

 

Making sure you support iOS 4.* devices.

By default Xcode sets the deployment target to match the SDK you are using.  Xcode now only comes with the iOS 5.0 SDK which means that by default your library project targets 5.0.

Click on the project name in bar on the left hand side.  In Build Setting do a search for “deployment” and change the “iOS Deployment Target” to be 4.0.

You’ll have to take this one step further by adding an xml file specifying the minimum required iOS version when packaging the ANE.  See the item “Using External Frameworks and Libraries” for more information.

Always Debug on a Mac

I do my development on a Windows PC.  So, naturally, after I built the native extension on the Mac (since Xcode is only available for OSX), I tried to compile a test application using an ANE on my PC.  This would generally crash and burn.  Sometimes compiling would fail.  Oftentimes the application just didn’t work as expected, or would crash for no apparent reason.  I had tried this prior to creating the Volume extension and the test extension was pretty basic, but it just didn’t work when compiling from a Windows PC.

I don’t know exactly why this happens, but it was something that multiple people in my office had trouble with.  Especially coupled with the next few pitfalls, you’ll find it’s impossible not to compile the builds on a Mac.

Using More Than One Native Extension

Using more than one native extension in the same application can lead to some unnecessary headaches if you aren’t careful. If the ContextInitializer and ContextFinalizer functions that are specified in the extensions initializer function (the one specified in the extension.xml initializer node) share the same name as the ContextInitializer and ContextFinalizer of a different native extension, only first native extension loaded will work. I think this is because Objective C isn’t letting the new functions be created within the scope of the extension because they already exist from the previous extension. I’m assuming functions are stored in some sort of global scope; I’m not an expert on Objective C though, so that’s just my initial guess.

That being said, you should name the internal initializer and finalizer functions uniquely. I’m guilty of not doing that in this extension, which is why I’m writing this note right now. Here’s an example of what I mean.

[sourcecode language=”objc”]
void MySuperUniqueContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet)
{

return;
}

void MySuperUniqueContextFinalizer(FREContext ctx)
{
return;
}

void VolumeExtensionInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet)
{
*extDataToSet = NULL;
*ctxInitializerToSet = &MySuperUniqueContextInitializer;
*ctxFinalizerToSet = &MySuperUniqueContextFinalizer;
}
[/sourcecode]

Use the iOS 5.0 SDK when packaging the IPA

By default the AIR SDK compiles the IPA using the iOS 4.0 SDK.  If you want to use iOS 5.0 SDK features, or if you want to link in external libraries (see next item) you’ll have to tell Flash Builder where the iOS 5.0 SDK is located.  Also, as mentioned in the previous item, an application using an iOS native extension will just work better if you link in the SDK.

Go into the properties of your flex project.  Navigate to “Flex Build Packaging -> Apple iOS”.  Click the “Native Extensions” tab.  In the “Apple iOS SDK” input type the location of the SDK.  The default location is “/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk”.

If you are compiling via the command line, you can use the –platformsdk option to specify the location.

adt -package -target ipa-app-store -provisioning-profile ./myProfile.mobileprovision -storetype pkcs12 -keystore ./Certificates.p12 -storepass XXX myApp.ipa myApp-app.xml Main.swf -extdir ext/ -platformsdk /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/

See here for more information.

Using External Frameworks and Libraries

AIR doesn’t link in shared iOS frameworks or libraries, even some of the ones included with the iOS SDK.  Here is a list of frameworks that AIR links in by default:

  • CoreFoundation
  • UIKit
  • MobileCoreServices
  • CoreGraphics
  • SystemConfiguration
  • AudioToolbox
  • CFNetwork
  • QuartzCore
  • CoreLocation
  • CoreMedia
  • CoreVideo
  • AVFoundation
  • Foundation
  • OpenGLES
  • Security

If you want to use any other library or framework you have to list them in an XML which gets compiled with the ANE.  One reason this is required is because Xcode links in external libraries at compile time of the main application.  So when you build a library project in Xcode the resulting .a file doesn’t contain any code from referenced libraries/frameworks.

If the frameworks are not included with the iOS SDK by default, you must copy them into the iOS SDK folder.  (You can also specify custom directories to search for frameworks in the XML file, however you have to specify the path as relative to the iOS SDK directory, so I find it easier to just copy the libraries/frameworks into the iOS SDK directory.)

Inside of the iOS SDK directory, copy the library/framework files into either the System/Library/Frameworks folder or the usr/lib folder.

Create an XML file with the following structure.

[sourcecode class=”xml”]
<platform xmlns="http://ns.adobe.com/air/extension/3.1">
<sdkVersion>4.0</sdkVersion>
<linkerOptions>
<option>-ios_version_min 4.0</option>
<option>-framework Twitter</option>
<option>-liconv</option>
</linkerOptions>
</platform>
[/sourcecode]

This file does two important things.  First it specifies the version of iOS that you support.  Secondly, it tells the compiler which frameworks it should look for to compile into the ANE.  If referencing a framework (you can tell it’s a framework because the folder will be Name.framework) use the option value –framework name.  If it is a library (you can tell it’s a library because the file should be a *.dylib) then use the option value –name.

When compiling the ANE, use the –platformoptions option to specify the location of the xml file.

adt -package -target ane myextension.ane extension.xml -swc mySwc.swc -platform iPhone-ARM library.swf libmylib.a -platformoptions myplatformoptions.xml

See here for more information.

Using Entitlements

As of AIR 3.1 you can specify custom entitlements for your application by specifying them in the app descriptor file.

[sourcecode class=”xml”]
<iPhone>
<!– A list of plist key/value pairs to be added to the application Info.plist –>
<InfoAdditions>
<![CDATA[

]]>
</InfoAdditions>
<!– A list of plist key/value pairs to be added to the application entitlements –>
<Entitlements>
<![CDATA[
<key>keychain-access-groups</key>
<array>
<string>…</string>

</array>

]]>
</Entitlements>
</iPhone>
[/sourcecode]

See here for more information.

Using Attached Devices

When using an attached device, such as a barcode scanner, you have to specify an entitlement in the app descriptor file.

Here’s the information from the iOS documentation:

To declare the protocols your app supports, you must include the UISupportedExternalAccessoryProtocols key in your app’s Info.plist file. This key contains an array of strings that identify the communications protocols that your app supports. Your app can include any number of protocols in this list and the protocols can be in any order. The system does not use this list to determine which protocol your app should choose; it uses it only to determine if your app is capable of communicating with the accessory. It is up to your code to choose an appropriate communications protocol when it begins talking to the accessory.

As the last section detailed, you can add the UISupportedExternalAccessoryProtocols entitlement to your application descriptor file.

Continue to part 4

15 Comments

    Hi Nathan,

    Great article. Quick question?
    How to implement another protocol (third party device protocol) within the .m file. Appreciate your answer.

    Thanks,
    Edward

    • by Edward
    • 1:14 pm, September 13, 2012
    • Reply

      I don’t understand your question. Do you mean using a third party attached device?

      • by Nathan Weber
      • 1:17 pm, September 13, 2012
      • Reply

    Yes, like a barcode device. I have to implement a protocol provided by the barcode device sdk, to get the scanned code. Do I do that in a separate implementation other than in the .m file?

    • by Edward
    • 1:26 pm, September 13, 2012
    • Reply

      You’ll need to do a couple of things to get it working.
      Follow the directions under the section “Using External Frameworks and Libraries” to put the barcode SDK library into the right place and to have the ANE see it (via platformoptions.xml).
      After that, you need to tell Xcode about the library project. I believe you can simply drag the library from finder onto the project in Xcode. I believe another way of doing this is to go to the properties of the project, click on Build Phases, and then expand the “Link Binary with Libraries” item, then add the barcode device SDK library.
      Once all of that is done, follow the directions under the section “Using Attached Devices” to enable the iOS device to see the barcode scanner.

      • by Nathan Weber
      • 1:34 pm, September 13, 2012
      • Reply

        Hi Nathan,

        Thanks for your prompt help. I have done all that you have said. In-fact I have followed each and every line of your articles. I am having issues combining both the FREObject code and the barcode sdk code. Is it possible that you can send me an email to my email address and I can explain the issue clearly. Once again, I really appreciate your help. Thanks

        • by Edward
        • 1:42 pm, September 13, 2012
        • Reply

          Hi Nathan,
          Where can I find the info.plist file. I do not see that in my xcode library project? Do I create one myself?

          Thanks,.

          • by Edward
          • 2:01 pm, September 14, 2012
          • Reply

    Hi Nathan,
    Excellent article.
    I am using FlashBuilder 4.7 Beta. on a Mountain Lion machine with Xcode SDK 6.0. When trying to debug launch, I’m receiving the error: “[Fatal Error] :1:1: Content is not allowed in prolog.”.
    I poked around and originally fixed the error by overlaying AIR 3.5 onto my Flex 4.6.0 sdk.
    However, now the error is back and I think it may be happening because FB can’t find a framework or library that my native code is using. My native code uses a 3rd party library (libXXX.a). How do I configure to make sure it is included?
    Thanks.

    • by BIll
    • 10:02 am, October 17, 2012
    • Reply

        Thanks. I had 2 issues. First, I needed to NOT specify my SDK location in the FlashBuilder Packaging properties for iOS NativeExtension. (using AIR 3.5 works with SDK 6.0) Then I received an error: “Can’t find library -libiconv”.
        It turns out my platformoptions.xml was wrong.
        I had:
        -libiconv
        -libstdc++
        I noticed the option should be
        -liconv
        -lstdc++

        The documentation for the 3rd party .a file I am using says it is not objectiveC and is actually STL C++, hence why I needed the -libstdc++.dylib. I’m not exactly sure how it all linked together, but it is now working ok.
        Thanks again for the great tutorial!
        Next on my wish list: If you have a blog on using ant to build the ANE instead of command line, I’d be very interested.

        • by BIll
        • 9:47 am, October 18, 2012
        • Reply

    I loooooooooove you so much !! I be working on this problem for 4 hours~ And finally fixed after read your words “Using More Than One Native Extension”,Thanks a lot~

    Thank you very much for the article. I was able to create an ane, but I have a problem with “Using Attached Devices”.

    My ANE has to implement the UISupportedExternalAccessoryProtocols, but I don’t understand how I should put it on the app descriptor file.

    I put this in xxx-app.xml:

    <![CDATA[
    UISupportedExternalAccessoryProtocols

    com.xxxx.xx
    com.xxx.xx

    ]]>

    Where com.xxx.xx are a different protocols that I have to support. I export release build the app, and when I’ve tried to test it on device, I can’t install it because: “The executable was signed with invalid entitlements”

    Do you know what I’m doing wrong?

    Thanks in advance

    • by Almudena
    • 6:38 am, February 12, 2013
    • Reply

      Sorry, the last code wasn’t copy correctly. I put it inside Entitlements:

      <![CDATA[
      UISupportedExternalAccessoryProtocols

      com.xxxx.xx
      com.xxx.xx

      ]]>

      Update:

      Instead of put it inside Entitlements, I put it inside InfoAdditions like a key and string. I able to install the app on device, but I have this error: ERROR – opening session failed as protocol com.xxx.xx is not declared in Info.plist

      • by Almudena
      • 8:15 am, February 12, 2013
      • Reply

        Here’s some sample code from a project I have that is working. Please replace [ and ] with , respectively. I had to change them to get the blog to post it. Hopefully this helps.

        [iPhone]
        [InfoAdditions]
        [![CDATA[
        [key]UISupportedExternalAccessoryProtocols[/key]
        [array]
        [string]tw.com.xxxx.xx[/string]
        [string]tw.com.xxxx.xx[/string]
        [/array]
        ]]]
        [/InfoAdditions]
        [requestedDisplayResolution]high[/requestedDisplayResolution]
        [/iPhone]

        • by Nathan Weber
        • 8:46 am, February 12, 2013
        • Reply

          Thanks thanks thanks!!! It works!

          • by Almudena
          • 9:34 am, February 12, 2013
          • Reply

            No problem!

            • by Nathan Weber
            • 9:37 am, February 12, 2013

Leave a Reply to BIll Cancel reply