In AIR 3.0, Adobe released a capability called Native Extensions. This allows you to directly extend the capabilities of the AIR Runtime on any platform supported by AIR (iOS, Android, Windows, OSX, etc…). In my consulting work for the NitroLM licensing and security project, I’ve had a chance to really dig into what it takes to develop a cross-platform native extension in C++.

I’m using Microsoft Visual C++ Express on Windows to create a .DLL and XCode 4 on OSX to create a .framework library. In this example, we’ll follow the code of one function in NitroLM called getMessages(). This function retrieves admin-generated messages from the NitroLM servers. For example, a developer might want to send his app users a message about the availability of a new version the next time they launch the software product.

On the AS3 side, you’ll need a class to handle the integration with the native libraries. For NitroLM, this class is called LicenseClient. You can view the full contents of this file on GitHub as the AS3 portion of the NitroLM library is open-source.

https://github.com/westbam/NitroLM/blob/master/nitrolm-air/src/com/nitrolm/LicenseClient.as

In the constructor of this class, we create the ExtensionContext and optionally call a function to turn on debugging in the native code.

public static var context:ExtensionContext;

/**
 * Create a new LicenseClient instance
 *
 * @param debug If enabled, the native library will write a file called nitrolm_debug.out
 */

public function LicenseClient(debug:Boolean = false)
{
  if(!context)
  {
    context = ExtensionContext.createExtensionContext("com.nitrolm.NitroLM", "");
  }
 
  if(context && debug)
  {
    context.call("enableDebug_air");
  }
}

Next, on the actionscript side, we call the getMessages() function to retrieve our list of messages. Since we don’t want to block our GUI thread waiting on an IO server call, we have to handle an event to retrieve the data. We listen for a LicenseClientEvent whose .data property will contain an Array of messages once it comes back.

public function getMessages(email:String, version:String, days:int):void
{
  context.addEventListener(StatusEvent.STATUS, handleStatusEvent);
  context.call("getMessages_air", email, version, days);
}

You’ll notice that we are handling another type of StatusEvent here. This is a message we’ll listen for internally that will let us know that the data is ready to be retrieved on the native side. When we handle this message in handleStatusEvent(), we can then make a second call into our native library to retrieve the message data. This 2-call approach prevents us from ever locking our AIR GUI thread waiting on the native side to complete.

private function handleStatusEvent(event:StatusEvent):void
{
  context.removeEventListener(StatusEvent.STATUS, handleStatusEvent);
  var req_type:int = new int(event.code);
  var response:int = new int(event.level);
  var lce:LicenseClientEvent = new LicenseClientEvent(LicenseClientEvent.LICENSE_RESPONSE, req_type, response);
 
  switch(req_type)
  {
    case NLMConstants.REQUEST_MESSAGES:
      if(response == NLMConstants.RESPONSE_OK)
      {
        lce.data = context.call("getMessages_data");
      }
    break;
  }
 
  dispatchEvent(lce);
}

The reason for the 2-call approach is that we’re using synchronous IO on the native side. If we didn’t spin up a new thread for the IO call on the native side, the AIR GUI would freeze every time we made a server call. To solve this, the first call is made to unpackage our AS3 parameters into native data types and spin up a new Thread to handle the IO. Our thread then tosses up a StatusEvent once it’s done receiving a response from the NitroLM server. A second call is then made to getMessages_data to convert the native response data into actionscript types we can use. Let’s take a look at the native code now.

We first need to define our native initializer, finalizer, and data functions in a header file.

/*
 * AIR_COMMANDS.H
 * Native entry point for Adobe AIR
 *
 * Copyright 2011 Simplified Logic, Inc.  All Rights Reserved.
 * Author: Andrew Westberg
 */


#ifndef __AIR_COMMANDS_H_
#define __AIR_COMMANDS_H_

#include "nlm_constants.h"
#include "nlm_commands.h"
#include "FlashRuntimeExtensions.h"

#ifdef __cplusplus
extern "C" {
#endif

...
FREObject getMessages_air(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]);
FREObject getMessages_data(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]);

void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions);
void ContextFinalizer(FREContext ctx);
void ExtInitializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer);
void ExtFinalizer(void* extData);

#ifdef __cplusplus
} //extern "C"
#endif

#endif //__AIR_COMMANDS_H_

Our initializer function is called when we first create the extension context. We need to tell it which functions the context has defined.

/*
 * AIR_COMMANDS.C
 * Exported functions for Adobe AIR (and related non-exported functions)
 *
 * Copyright 2011 Simplified Logic, Inc.  All Rights Reserved.
 *
 * Written by Andrew Westberg
 */


#include <stdlib.h>
#include "air_commands.h"
#include "CAsyncTask.h"
#include "storage.h"
#include "Debug.h"

#ifdef __cplusplus
extern "C" {
#endif

static CThread asyncThread;
static CAsyncTask asyncTask;

void ExtInitializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer)
{
  *ctxInitializer = &ContextInitializer;
  *ctxFinalizer = &ContextFinalizer;
}

void ExtFinalizer(void* extData)
{
}

void ContextFinalizer(FREContext ctx)
{
  gracefulShutdown();
}

void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions)
{
  asyncThread.m_gracefulShutdown = FALSE; //terminate thread hard at shutdown because we're inside a .DLL

  FRENamedFunction *func;

  *numFunctions = 50;
 
  func = (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * (*numFunctions));

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

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

  ...
 
  *functions = func;
}

#ifdef __cplusplus
} //extern "C"
#endif

In the function getMessages_air(), we parse out our input parameters and store them in our thread task and then spin up the task on our thread for processing. The threading library I’m using was developed by Walter Capers and can be found under the CodeProject article Creating a C++ Thread Class.

FREObject getMessages_air(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[])
{
    while(asyncTask.Status() == TaskStatusWaitingOnQueue || asyncTask.Status() == TaskStatusBeingProcessed)
    {
        //wait until our task is ready to process something new
        Sleep(10);
    }

    asyncTask.initialize(ctx, REQUEST_MESSAGES, argc, argv);

    if(asyncThread.Event(&asyncTask))
        Debug::logDebug("submitted getMessagesTask for background processing");
    else
        Debug::logDebug("ERROR submitting getMessagesTask for background processing");

    return NULL;
}

The asyncTask.initialize() method parses out our input parameters from actionscript and stores them in native object types so our thread can work with them. It’s important to run this code _before_ spinning up the thread because your thread WILL NOT have access to the FRE functions from Adobe. The only capabilities your thread has from Adobe is to toss up a StatusEvent using FREDispatchStatusEventAsync() when it’s done. You’ll also notice that you have to do a lot of error handling on just about every FRE function you call.

void CAsyncTask::initialize(FREContext ctx, const int requestType, int argc, FREObject *argv)
{
  //reset the task
  ThreadId_t id = 0;
  SetTaskStatus(TaskStatusNotSubmitted);
  Thread(&id);

  CAsyncTask::ctx = ctx;
  CAsyncTask::requestType = requestType;

  uint32_t len = -1;
  const uint8_t *email = 0;
  const uint8_t *version = 0;
  int32_t days = 0;
 
  switch(requestType)
  {
    case REQUEST_MESSAGES:
      if((res=FREGetObjectAsUTF8(argv[0], &len, &email)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FREGetObjectAsUTF8(argv[0], &len, &email) = %d", res);
        dispatchEvent(REQUEST_MESSAGES, RESPONSE_INTERNAL_CLIENT_ERROR);
        return;
      }
      strcpy(CAsyncTask::email, (const char*)email);

      if(argv[1] != NULL)
      {
        if((res=FREGetObjectAsUTF8(argv[1], &len, &version)) != FRE_OK)
        {
          Debug::logDebug("ERROR: FREGetObjectAsUTF8(argv[1], &len, &version) = %d", res);
          dispatchEvent(REQUEST_MESSAGES, RESPONSE_INTERNAL_CLIENT_ERROR);
          return;
        }
        strcpy(CAsyncTask::version, (const char*)version);
      }
      else
        strcpy(CAsyncTask::version, "");

      if((res=FREGetObjectAsInt32(argv[2], &days)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FREGetObjectAsUTF8(argv[2], &days) = %d", res);
        dispatchEvent(REQUEST_MESSAGES, RESPONSE_INTERNAL_CLIENT_ERROR);
        return;
      }
      CAsyncTask::days = days;
    break;
  }
}

Our CThread will call the Task() method of CAsyncTask once it starts processing. In this function, we make our synchronous IO call to the NitroLM server by calling the internal getMessages() method and dispatch an event once it’s complete.

void CAsyncTask::dispatchEvent(const int req_type, const int response)
{
  FREResult res;
  char code[5];
  char level[5];

  sprintf(code, "%d", req_type);
  sprintf(level, "%d", response);
  if((res=FREDispatchStatusEventAsync(ctx, (const uint8_t*)code, (const uint8_t*)level)) != FRE_OK)
  {
    Debug::logDebug("ERROR: FREDispatchStatusEventAsync(ctx, (const uint8_t*)code, (const uint8_t*)level) = %d", res);
    return;
  }
}

BOOL CAsyncTask::Task()
{
  ThreadId_t id = 0;
  Thread(&id);

  int ret;

  switch(CAsyncTask::requestType)
  {
    case REQUEST_MESSAGES:
      outMsgs = NULL;
      ret = getMessages(email, version, days, &outMsgs);
      dispatchEvent(REQUEST_MESSAGES, ret);
    break;
  }

  return TRUE;
}

Finally, we package the data as an Array of Objects when getMessages_data() is called from AIR.

FREObject getMessages_data(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[])
{
  int i;
  FREResult res;
  FREObject result;
  FREObject subject;
  FREObject body;
  FREObject message;
  FREObject thrownException;
  FREObject stackTrace;
  const uint8_t *stack;
  uint32_t len;

  if(asyncTask.outMsgs != NULL)
  {
    if((res=FRENewObject((const uint8_t*)"Array", 0, NULL, &result, &thrownException)) != FRE_OK)
    {
      Debug::logDebug("ERROR: FRENewObject((const uint8_t*)\"Array\", 0, NULL, &result, &thrownException) = %d", res);
      freeMessages(asyncTask.outMsgs);
      FRECallObjectMethod(thrownException, (const uint8_t*)"getStackTrace", 0, NULL, &stackTrace, NULL);
      FREGetObjectAsUTF8(stackTrace, &len, &stack);
      Debug::logDebug((const char*)stack);
      return NULL;
    }

    FRESetArrayLength(result, asyncTask.outMsgs->size);

    for(i=0;i<asyncTask.outMsgs->size;i++)
    {
      subject = 0;
      if(asyncTask.outMsgs->list[i].subject != NULL)
      {
        if((res=FRENewObjectFromUTF8(strlen(asyncTask.outMsgs->list[i].subject), (const uint8_t*)asyncTask.outMsgs->list[i].subject, &subject)) != FRE_OK)
        {
          Debug::logDebug("ERROR: FRENewObjectFromUTF8(strlen(asyncTask.outMsgs->list[i].subject), (const uint8_t*)asyncTask.outMsgs->list[i].subject, &subject) = %d", res);
          freeMessages(asyncTask.outMsgs);
          return NULL;
        }
      }
      if((res=FRENewObjectFromUTF8(strlen(asyncTask.outMsgs->list[i].msg), (const uint8_t*)asyncTask.outMsgs->list[i].msg, &body)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FRENewObjectFromUTF8(strlen(asyncTask.outMsgs->list[i].msg), (const uint8_t*)asyncTask.outMsgs->list[i].msg, &body) = %d", res);
        freeMessages(asyncTask.outMsgs);
        return NULL;
      }

      if((res=FRENewObject((const uint8_t*)"Object", 0, NULL, &message, &thrownException)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FRENewObject((const uint8_t*)\"Object\", 0, NULL, &message, &thrownException) = %d", res);
        freeMessages(asyncTask.outMsgs);
        FRECallObjectMethod(thrownException, (const uint8_t*)"getStackTrace", 0, NULL, &stackTrace, NULL);
        FREGetObjectAsUTF8(stackTrace, &len, &stack);
        Debug::logDebug((const char*)stack);
        return NULL;
      }

      if((res=FRESetObjectProperty(message, (const uint8_t*)"subject", subject, &thrownException)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FRESetObjectProperty(message, (const uint8_t*)\"subject\", subject, &thrownException) = %d", res);
        freeMessages(asyncTask.outMsgs);
        FRECallObjectMethod(thrownException, (const uint8_t*)"getStackTrace", 0, NULL, &stackTrace, NULL);
        FREGetObjectAsUTF8(stackTrace, &len, &stack);
        Debug::logDebug((const char*)stack);
        return NULL;
      }
      if((res=FRESetObjectProperty(message, (const uint8_t*)"body", body, &thrownException)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FRESetObjectProperty(message, (const uint8_t*)\"body\", body, &thrownException) = %d", res);
        freeMessages(asyncTask.outMsgs);
        FRECallObjectMethod(thrownException, (const uint8_t*)"getStackTrace", 0, NULL, &stackTrace, NULL);
        FREGetObjectAsUTF8(stackTrace, &len, &stack);
        Debug::logDebug((const char*)stack);
        return NULL;
      }

      if((res=FRESetArrayElementAt(result, i, message)) != FRE_OK)
      {
        Debug::logDebug("ERROR: FRESetArrayElementAt(result, i, message) = %d", res);
        freeMessages(asyncTask.outMsgs);
        return NULL;
      }
    }

    freeMessages(asyncTask.outMsgs);
    return result;
  }

  return NULL;
}

Hopefully, this code helps you get a start on writing your own NativeExtensions for Adobe AIR. If you’d like more information about this particular NitroLM project, you can browse the GitHub projects and the NitroLM documentation.

Post to Twitter

Posted by Andrew, filed under AIR, as3, C++, security. Date: November 30, 2011, 1:48 pm | 10 Comments »

Update: We’re in beta! Contact me to get on the beta list: andrew [at] simplifiedlogic.com subject: NitroScreenCap Beta. Also, include your development OS platform.


I’ve been working on a project for Simplified Logic that requires screen capture capabilities. Since this type of feature would be hugely useful to others developing AIR applications, I’ve convinced them to break it out and sell it as a component. This component has two parts. The first is a .swc file you include in your AIR application. The second is a set of native executables that the AIR app will utilize to handle the native part of the screen capture.

So far, I have it working only on Windows. Next on my list will be OSX support followed closely by Linux. Pricing for this component is undetermined at this time.

The code on the AIR side is pretty straightforward for users. You’ll need to use the extendedDesktop AIR profile and package your app as an executable.

protected function screenshotButton_clickHandler(event:MouseEvent):void
{
    nitroScreenCap.takeScreenshot(
        function screenshotResult(bitmapData:BitmapData):void
        {
            screenshotImage.source = bitmapData;
        },
        function screenshotError(error:Error):void
        {
            trace(error.message);
        }
    );
}

Watch the video to get an idea of what it’s capable of.

Unable to display content. Adobe Flash is required.

Post to Twitter

Posted by Andrew, filed under AIR, C++. Date: February 24, 2011, 6:02 pm | 9 Comments »

04  Feb
RIA Rockstars

For those of you who haven’t heard, the Inside RIA blog is no more. Rich Tretola has been kind enough to round up former and new authors to work on a new blog site called RIA RockStars.

My first contribution to the site is Efficient NativeProcess call in Adobe AIR. Enjoy!

Post to Twitter

Posted by Andrew, filed under AIR, C++, encryption. Date: February 4, 2011, 4:09 pm | 1 Comment »

swift_gps_bank_trip

I’ve worked out the majority of the kinks with Swift GPS, so I’m calling it in Alpha right now.  To give you a bit more information about the vision for this product, it will be a GPS track data logging device.  You stick a USB thumb drive in it, and it will track the movement of your car, wife’s car, dog, RC aircraft, whatever you want to attach it to.  The target market will be developers or GPS hobbyists, or geocachers who want a real challenge.  It outputs a GPX data file which is nothing more than a special XML file format for GPS data.  This should be useful for developers since it’s an open standard.

Click the image above for a screencast of my magnificently exciting trip to the bank. (Warning, screen recordings of Google Earth are HUGE).

I still have a few features to add such as adding a little screen so geocachers and others can view the raw lat/lon data in real-time.  I ordered a book from amazon that should help me with writing the microcontroller code necessary for that piece.

This will probably be my last GPS post for quite awhile.  I plan on going back to your regularly scheduled “Flex Junk” in the near future.

This blog’s comment section has been pretty quiet for some time.  Any comments on this project would be appreciated.  Even if it’s just to say, “Go back to posting on Flex topics dude!”

Post to Twitter

Posted by Andrew, filed under C++, GPS. Date: March 10, 2009, 8:55 pm | No Comments »

I haven’t posted a whole lot to this blog lately because my recent consulting has been focused on C++ and C# .NET work.  I’ve been working on updating to Phasor Professional to version 2.1.1.  Phasor Professional is a Microsoft Visual C++ app originally written in 1998.  Ten years later, and it’s still going strong.  The recent updates are a slight modification to calculating AM Radio tower impedances to meet the new FCC regulations that go into effect in February, 2009.

Along with this update, I’ve enhanced the software to use Nitro-LM for license management (I’ve believe I’ve been pirated all over Mexico and Brazil but I have no way of knowing).  I also added the ability to contact tech support directly from the software through the Nitro-LM API.

The most important enhancement, however was inspired by the AIR Update Framework.  My new AIR-based software WCAP uses the framework to keep my users up-to-date with all the latest patches, fixes, and enhancements.  I wanted that same level of functionality for my legacy C++ application.  I scoured the Internet and found AutoUpdate+ out of Australia.  Their software is an easy to use GUI application that builds a client updater executable.  When you call it from your software, it checks your server to see if updates are available.  The GUI also takes care of doing FTP uploads of updated software to your server in one seamless end-to-end process.  I first tried out their fully-functional demo and liked what it did for me.  I then contacted them to get a free license since they have a deal for single-person companies like myself.  After working with the software for another hour or so, I went ahead and purchased a license from them.  Their software worked so well that I couldn’t really justify paying nothing for it.  I like being paid for software I develop so I assume others do too.  If you have a need for this level of functionality for your own applications, check them out.

Post to Twitter

Posted by Andrew, filed under AIR, C++, WCAP. Date: December 27, 2008, 10:26 am | No Comments »

The last two weeks, I’ve been working on an AIR project called WCAP Pro.  It stands for Westberg Circuit Analysis Program and has been around in some form since the 80s.  It started out as an MS-DOS program written in Fortran 77 by my father.  He has a background in electrical engineering and mathematics.  There was no such thing as a computer science degree at most universities at the time, so it was a nice big bowl of spaghetti code with goto statements aplenty.  To run it, you create a specially formatted text file that describes the Resistors, Capacitors, Inductors, and Current Sources along with the connections between them.  When you run the DOS program, it spits out a text file with the results of the circuit analysis.  The DOS program still has a small but loyal community using it to this day.

Since that time, the algorithm for WCAP has been incorporated in two software applications written in Microsoft Visual C++.  Phasor Professional allows the user to design a circuit used in AM Radio that directionalizes the radio signal.  This circuit sits between the transmitter and the towers of an AM Radio station.  The second was called Diplex Professional and helped the user design a circuit that allowed two radio stations to share the same tower.  The WCAP algorithm was converted to C++ and used internally by both of these applications.

The most recent evolution for WCAP is again as a standalone application.  It has been completely rewritten using Adobe AIR 1.1 technology.  Much of the work has been focused on the user-interface for this application.  Many tools in the electrical engineering arena are heavy on algorithms and complexity.  I wanted to make this application’s UI so smart that it would be truly FUN to use.

I started with two open-source projects, Flexlib, and Degrafa.  I used the DragScrollingCanvas component from flexlib because I wanted the user to be able to pull the schematic around the screen as if they were manipulating a piece of drafting paper.  I submitted an enhancement to DragScrollingCanvas to allow me to exclude certain children from causing canvas dragging.  Doug McCune cleaned up my code and incorporated this concept into Flexlib.  Degrafa was used to draw all of my schematic components.

I also incorporated a commercial product called Nitro-LM to handle the licensing of my application.  This was kind of a no-brainer for me since I consult for Simplified Logic working on the Nitro-LM licensing product.  It will allow me to keep track of who is using the software and also have enough control to shut someone off if they don’t pay their maintenance.

Most schematic-building applications have a “snap-to-grid” feature that lines up components.  To me, this always seemed a bit abrupt from a UI standpoint.  Any component placed in my schematic slides into place using standard Flex move effects.  Another feature I disliked about other programs is how they connect components together.  You have to draw each line segment between components and if you move a component, you need to re-route.  I created a pathing algorithm that keeps the connector lines attached even if you move a component.  Also, if you drop a component so it’s touching another, they are automatically connected up.

Rotating components in standard schematic tools usually involves selecting the part and then hunting for some type of rotate toolbar button.  I provided a toolbar button to do it, but I also allow the user to hover over a component and mouse-wheel down to swing the component down, or wheel-up to rotate the component back to horizontal.

Another unique UI feature I designed for this application was an Eraser tool.  It works just like a pencil eraser and you drag it back and forth across lines and components to highlight them for deletion.  A confirmation box (dummy warning) then confirms the action.

The final UI feature I created was the Probe tool (seen in the screenshot above).  I took my voltage tester and placed it on a piece of green paper in my driveway and snapped a photo of it with my wife’s digitial camera.  The green contrasted nicely with the black and red of the voltage probes so I could make the image transparent fairly easily.  Whenever the probe tool is dragged over connecting wires or parts, the outputs from the WCAP algorithm are displayed at the center frequency for analysis.  I wanted this to have a similar familiarity to an actual engineer diagnosing a circuit in real life.  If the probe is double-clicked on a connecting wire or part, all the details about that part at ALL frequencies analyzed are displayed in a data grid.

The application is still beta quality and is lacking about 2 key features.  I hope to have everything complete before unveiling the application at the NAB AM Antenna Computer Modeling Seminar on November 20th.

This video shows some of the UI behaviors I’ve described above.

Post to Twitter

Posted by Andrew, filed under AIR, C++, Degrafa, Flex, WCAP. Date: October 5, 2008, 8:16 pm | 4 Comments »