在AIR 3.0里面,Adobe新增了一个叫做本机拓展的功能。这个功能允许你在任何AIR支持的平台上(iOS,Android,Windows,OSX等等)直接拓展AIR运行时的能力。在我给NitroLM的许可和安全项目担任顾问的时候,我获得了一个机会去深入钻研到底怎么用C++来开发出一个跨平台的本机拓展。
我在Windows上使用Microsoft Visual C++ Express 创建一个.DLL程序,并且也在OSX上利用XCode 4来创建了一个框架库。在这个例子中,我们将会从NitroLM里面一个名为getMessages()的函数开始分析。这个函数检索来自NitroLM的服务器上的由管理而生成的消息。例如,开发人员想在发布他们的软件产品的一段时间之后提示用户已有该软件的新版本。
AS3方面,你需要一个类来处理本机库的集合。对于NitroLM而言,这个类叫做LicenseClient。你可以在GitHub那里看到这个文件的所有内容,因为NitroLM库的AS3部分是开源的。
https://github.com/westbam/NitroLM/blob/master/nitrolm-air/src/com/nitrolm/LicenseClient.as
这个类的构造函数里面,我们创建了ExtensionContext的变量并且任意地调用了一个函数来开启本机代码的调试模式。
- 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");
- }
- }
复制代码
接下来,脚本方面,我们调用getMessages()函数来检索我们的消息列表。因为我们不想因为一个IO的服务请求而阻断我们的GUI线程,我们必须处理检索数据的事件。我们对LicenseClientEvent进行侦听,因为在LicenseClientEvent返回时它的data属性里面含有大量的消息。
- public function getMessages(email:String, version:String, days:int):void
- {
- context.addEventListener(StatusEvent.STATUS, handleStatusEvent);
- context.call("getMessages_air", email, version, days);
- }
复制代码
你会注意到我们现在正在处理StatusEvent的另一种类型。这是我们在内部侦听的一类消息,这类消息可以让我们知道数据正准备被本机代码检索。当我们在handleStatusEvent()中处理这个消息的时候,我们可以到我们的本机库中再进行一次调用,目的是重新获取消息的数据。进行两次调用避免了我们的AIR GUI因等待本地代码的完成而遭到锁定的情况的发生。
- 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);
- }
复制代码
进行两次调用的原因是我们同时使用了IO设备,如果我们不在本机为IO请求启动一个新的线程,那么AIR GUI将会在每次我们进行服务器请求的时候停滞。为了解决这个问题,进行第一次调用是为了将我们的AS3参数转换成本机数据类型并且启动一个新的线程来处理IO请求。我们的线程在收到NitroLM服务器的响应之后会立即发出一个StatusEvent事件。第二次调用是为了获取消息的数据,进而将本地响应的数据转换成我们可以使用的脚本类型。现在让我们看看本机代码吧。
我们首先必须在头文件中定义初始化模块,收尾模块以及数据函数。
- /*
- * 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_
复制代码
我们第一次创建拓展环境时调用初始化函数。我们需要告诉它哪些函数是已经定义了的。
- /*
- * 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
复制代码
在getMessages_air()函数中,我们分列出我们输入的参数并且将它们存入到我们的线程任务中,然后在线程中启动这个任务来处理这些数据。我正在使用的超线程库是由Walter Capers开发的,可以在CodeProject上的创建一个C++线程类这篇文章下面找到。
- 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;
- }
复制代码
asyncTask.initialize()方法从脚本的角度分列出我们输入的参数并且将它们存入本机对象类型,所以我们的线程在运行的时候可以用到它们。在启动线程之前执行这些代码很重要,因为你的线程没有权限从Adobe哪里访问到FRE 函数。你的线程唯一能从Adobe那里获得的功能是当线程结束的时候使用FREDispatchStatusEventAsync()函数发出一个StatusEvent的事件。你也将会发现你需要在每次FRE函数调用的时候进行很多的错误处理。
- 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;
- }
- }
复制代码
一旦CThread开始工作,则它会调用CAsyncTask的Task()方法。在这个函数里面,我们通过调用内部的getMessages()方法来获得发往NitroLM服务器上的同步IO请求,一旦这个过程结束,就会发出一个事件。
- 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;
- }
复制代码
最后,当AIR调用了getMessages_data()函数的时候我们把所有的数据封装成很多的对象。
- 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;
- }
复制代码
我希望,这些代码对你开始为Adobe AIR开发你自己的本机拓展会有帮助。你过你想知道更多有关NitroLM项目的信息,你可以浏览GitHub projects和 NitroLM documentation.
原文链接:http://bbs.9ria.com/viewthread.php?tid=107283