Native extensions for Adobe AIR是Adobe自AIR3引入的Native扩展接口,类似JNI一样,用于VM代码和Native代码的交互调用。通过ANE,可以突破FlexSDK本身功能限制,获得操作系统平台所支持的大量功能,以及将现有的大量Native代码资源在AS中重用。
这里以Windows平台为例介绍一下ANE代码的开发流程。其他几个平台的流程有相当大的不同,具体可以参考Adobe的文档:http://help.adobe.com/en_US/air/extensions/index.html
一个完整的ANE模块实际上分为2个部分:
- ActionScript接口模块
- Native模块
这是引用自Adobe文档中的架构图:
这两个模块开发完毕后,通过adt工具打包为ane文件后,才能供AS项目使用。
其中,ActionScript模块是一个swc项目,负责Native接口的创建、销毁以及调用Native代码和进行事件监听。
Native模块需要处理ANE环境的初始化、反初始化以及提供被调用的代码。在IOS平台上,Native代码以so形式提供;Android平台上,如果基于SDK编写,那么提供jar包,如果用NDK编译,那么提供so库;在Windows平台上,以DLL形式提供。
接口定义
这个示例中,我们将实现一个ANE接口,从API中获取当前PC的计算机名。这个函数我们命名为getComputerName。
Native代码被调用时,将会派发一个事件,通知AS这边自己被调用了。
Native代码实现
这里我们采用VS2012来实现,实际项目里面可以使用任意开发工具,只要能够按照规范编译Windows dll,并且可以调用FlexSDK中的lib库即可。如果采用其他特殊的开发工具,也可以直接调用adobe air.dll中的函数。
Dll的框架代码我们不再赘述,如果不熟悉,请参考Windows编程的资料。
ANE使用的DLL有四个重要的函数。一对用于整个ANE接口模块的初始化和反初始化,一对用于单个ANE Context的初始化和反初始化。一个ANE dll被加载起来之后,可以创建任意个context。而最后实际被调用的ANE函数,是和之前创建的context绑定的。
这四个函数的名字可以自己任意确定,在打包时提供的xml文件中需要加入该ANE的id、dll名字、平台以及两个全局初始化和反初始化函数的导出名。实际上,最终需要被导出的函数,也只有这两个。
4个函数的原型声明如下,必须以C语言名称进行导出:
1、
typedef void (*FREInitializer)(
void** extDataToSet ,
FREContextInitializer* ctxInitializerToSet,
FREContextFinalizer* ctxFinalizerToSet
);
这个函数用于全局初始化。该函数里面需要处理的事情很少,只需要通过参数返回Context的初始化和反初始化函数指针,即函数3、4的指针。同时可以返回一个void指针,在AIR回调其他函数时传入的extData,即这个指针。则可以用来进行某些数据绑定操作。实际使用中,这个回传参数可以作为数据绑定的媒介,例如用来将ANE的C语言接口绑定到C++对象上。
2、
typedef void (*FREFinalizer)(
void* extData
);
这个函数用于全局反初始化,我测试的时候没有发现被调用到。估计是在GC回收完所有context对象并卸载这个ANE模块时会调用。用于作最终的反初始化工作。
3、
typedef void (*FREContextInitializer)(
void* extData ,
const uint8_t* ctxType ,
FREContext ctx ,
uint32_t* numFunctionsToSet,
const FRENamedFunction** functionsToSet
);
这个函数当AS代码调用ExtensionContext.createExtensionContext时会被调用,用于创建一个ANE context。
extData由之前FREInitializer的调用中创建,AIR仅仅是回传过来。
ctxType是来自ExtensionContext.createExtensionContext的第二个参数,实际使用中可以用来通过单个createExtensionContext创建特性各异的接口。ctx由AIR生成,单个Context会绑定到特定的ctx指针,所以之后在接口函数被调用时传递过来的ctx可用来区分来自哪个Context。
numFunctionsToSet用于返回Context中创建的函数个数,它会影响到functionsToSet里面返回的数据个数。每个函数都需要有一个FRENamedFunction结构来表示。
functionsToSet用于返回一个FRENamedFunction结构的数组,用来描述这个Context中提供的所有ANE接口。其中包含接口的名字、数据绑定,以及FREFunction函数指针。这个数组的成员个数中必须和numFunctionsToSet指定的个数匹配。关于这个函数的说明,不得不吐槽一下Adobe的文档。没有写明应该由谁来负责释放。我测试的时候调试了一下,AIR不会负责释放这片内存!!喜欢new和malloc流的同志们要特别注意,Context反初始化的时候一定记得释放掉。或者也可以类似LUA里面常用的方式那样直接在全局变量中进行声明。
4、
typedef void (*FREContextFinalizer)(
FREContext ctx
);
这个函数用于在context对象的dispose方法被调用时回调。用于清理任何和该Context相关的资源。
实际供AS代码调用的函数原型如下:
typedef FREObject (*FREFunction)(
FREContext ctx,
void* functionData,
uint32_t argc,
FREObject argv[]
);
ctx传入绑定的Context指针,可以用来区分不同的Context
functionData传入在设置FRENamedFunction结构时指定的指针,也是用于绑定一些和特定函数相关的数据。
argc是参数个数
argv是参数数组,有argc个FREObject 对象。这些对象都是AS数据类型,需要通过FREGetObjectAs*系列的函数来转换为C数据类型。
在本例中,没有接受从AS过来的参数,所以我们不管参数。仅仅通过GetComputerNameW拿到计算机名,并且转换为UTF8字符串。最后通过FRENewObjectFromUTF8变为AS对象并返回。这里顺便说一句,所有ANE中使用的字符串都是UTF8编码。如果和本例一样,由平台获得的字符串格式不是UTF8的话,必须经过转换才能和AS进行交互。
和C数据类型的相互访问,由AIR提供了一系列C函数来实现。如果有过Alchemy使用经验的话,应该会很熟悉。主要分为基本数据类型、字符串、对象访问、ByteArray和Array访问这几类。在ANE中,还特别提供了BitmapData类型的访问接口。Adobe预计到相当部分的ANE扩展应该是用来处理图形数据的,而实际上确实也是。具体的使用方法,可以参见Adobe的文档,里面有详细描述。
以上就涵盖了ANE中Native代码和AS之间函数调用的所有内容。基本结构和Alchemy有很多类似的地方。但是Alchemy只是C/C++的交叉编译环境实现,实际上还是运行于VM层。ANE却是直接提供了更底层的接口。
另外,有时候我们需要从ANE代码中向AS代码派发事件,可以调用FREDispatchStatusEventAsync函数。该函数不会直接指定包含在event对象中的参数,而是指定了两个字符串参数。实际应用中应该由AS模块和Native模块自己来约定这两个字符串的内部格式和意义。ANE事件是针对Context派发的,AS那边监听也是在Context对象上。
特别指出一点的是,Adobe文档中写到FREFunction函数的调用是非线程安全的,即AIR可能在不同的线程中调用同一个FREFunction函数,也可能在不同线程中调用其他任何FREFunction函数。那么在这些函数中如果会用到临界资源,需要自己处理线程安全和同步。在Native模块中,可以启动自己的线程,但是也需要自己负责处理同步问题。
另外,使用VC编译器时,项目设置上面需要注意。C++代码生成上应选择/MT或者/MTD,而不应该选择dll中的CRT库。
AS代码实现
AS部分的代码非常简单。通过ExtensionContext.createExtensionContext创建出来Context对象,即可用这个对象来调用Native代码和监听Native代码过来的事件了。具体实现可参考示例代码,这里不再赘述。
AS模块的编译有个问题有必要说一句,我在测试时使用FlexSDK 4.6的adt打包最终的ane文件时,提示swf版本必须低于等于13。实际测试发现使用FlashBuilder编译的swc没有办法最终打包到ane中,必须在编译选项中加上-swf-version=13以下的版本才能打包成功。而Adobe文档中写的”SWF 11 for AIR 2.7, SWF 13 for AIR 3, SWF 14 for AIR 3.1, and so on”。最后发现是受到打包时编写的xml配置文件中xmlns指定的Extension namespace所影响。Adobe文档中提供了如下对应表:
兼容的AIR版本 |
ANE SWF版本 |
Extension namespace |
3.0+ |
10-13 |
ns.adobe.com/air/extension/2.5 |
3.1+ |
14 |
ns.adobe.com/air/extension/3.1 |
3.2+ |
15 |
ns.adobe.com/air/extension/3.2 |
3.3+ |
16 |
ns.adobe.com/air/extension/3.3 |
3.4+ |
17 |
ns.adobe.com/air/extension/3.4 |
3.5+ |
18 |
ns.adobe.com/air/extension/3.5 |
另外,swc的项目设置里面要包含AIR库才能编译成功。老鸟应该是不需要提醒这个问题的。
打包
打包的步骤比较恶心。Adobe居然没有提供直接的工具,而必须手动处理N个步骤,实在是偷懒至极,不得不吐槽一下。
- 首先需要生成一个用于代码签名的p12证书。可以通过类似如下的命令:
adt.bat -certificate -cn SelfSign -ou QE -o “Set your org name” -c US 2048-RSA test.p12 test
- 然后,要使用解压工具,将前面生成的SWC解压出来,提取里面的library.swf文件。
-
编写一个xml格式的扩展描述文件。详细说明可以参见http://help.adobe.com/en_US/air/extensions/WSf268776665d7970d-2e74ffb4130044f3619-8000.html,这里只提供我的示例文件内容:
<extension xmlns=”http://ns.adobe.com/air/extension/3.1″>
<id>ANETest</id>
<versionNumber>1.0.0</versionNumber>
<platforms>
<platform name=”Windows-x86″>
<applicationDeployment>
<nativeLibrary>ANEDll.dll</nativeLibrary>
<initializer>initializer</initializer>
<finalizer>finalizer</finalizer>
</applicationDeployment>
</platform>
</platforms>
</extension>
id的内容影响到ExtensionContext.createExtensionContext时需要传入的第一个参数,nativeLibrary是Native dll文件名,initializer是初始化函数导出名,finalizer是反初始化函数导出名。
如果还提供了其他平台的Native模块,可以添加多个platform小节。
-
最后使用adt命令打包,类似下面的格式:
adt.bat -package -storetype pkcs12 -keystore test.p12 -storepass test -target ane ANEtest.ane extension.xml -swc ANETestLib.swc -platform Windows-x86 library.swf ANEDll.dll
具体参数请参见adt的帮助或者Adobe的代码。打包时要注意文件都拷贝到当前目录下。
如果成功,则会生成可以在AIR项目中引用的ANEtest.ane文件。
ANE模块的调用
ANE模块的使用也很简单,和导入swc库类似。首先在构建路径中设置ANE文件搜索路径:
然后在构建打包设置中将需要的ANE勾上
使用的时候直接Import之前SWC模块的包即可使用。详见示例代码。
后记
ANE总体来说是一个很实用的功能。FlexSDK的功能说实在除了渲染之外,其他方面乏善可陈。通过ANE可以利用大量现有资源来扩展AS平台的应用范围,特别是在移动平台上,ANE扩展使得应用可以采用Flash方便的进行渲染,同时使用Native代码来和系统对接。
但是,ANE为Native模块几乎没有提供调试支持,在开发上对开发者有比较高的要求。就我目前测试的Windows平台上来看,发现Native模块中的异常无法由Native调试器捕捉,而加载时机又不确定,因此Native调试的切入点比较麻烦,没有办法利用DebugBreak之类的API启动调试。猜测应该可以使用Attach的方式附加上去调,但是总的来说是很不方便的,对调试字符串的分析就显得特别重要。实际开发上面,建议将AIR接口部分和功能代码进行剥离,将功能代码进行完善的单元测试之后再合并到代码中使用。另外,由于接口是采用C语言方式提供,项目规模大的时候代码管理会是一个问题,尽量将这些接口封装为C++方式,会比较利于大项目的管理。
代码下载:ANEDll