📜 ⬆️ ⬇️

Writing an extension for Adobe Air on PureBasic

In the wake of the growing popularity of PureBasic here, I propose to get acquainted with another area of ​​application of this language.

Starting with the third version of Air, it became possible to compensate for the limited SDK due to extensions (Flash Runtime Extensions). Extensions can be written in C / C ++ / Java (Android) and in any other language that allows you to create native libraries for appropriate platforms.

Computing speed, multithreading, interaction with the operating system are all available for extensions. Some time ago I wrote an extension for C for Mac and Windows - a wrapper for the cross-platform library HIDAPI . Despite some difficulties, the convenience was in the cross-platform library, which allowed writing the extension code once and assembling for each platform with minimal effort.

PureBasic, in turn, provides the ability to create applications (dll, so, drivers ) for several systems. There is the possibility of optimization, support for unicode, ready-made objects (List, Map), a set of cross-platform functions (compression algorithms, image processing, encryption), under Windows, most WINAPI, macros and even OOP are imported. All this significantly speeds up development (for those who do not have experience of C / C ++), compared to bare C.
')
Example - system modal dialog

Sources and a demo application for Windows are available at code.google.com .

The first step was to import functions from the FlashRuntimeExtensions.lib library (Windows, {AIR SDK} \ lib \ win \), this is done relatively simply, if you have FlashRuntimeExtensions.h ({AIR SDK} \ include \):

ImportC "../lib/FlashRuntimeExtensions.lib" ;returns FRE_OK ; FRE_WRONG_THREAD ; FRE_INVALID_ARGUMENT If nativeData is null. ;FREResult FREGetContextNativeData( FREContext ctx, void** nativeData ); ;-FREGetContextNativeData FREGetContextNativeData.l (ctx.l, nativeData.l) ... EndImport 

In the compiler settings, you must specify the Shared Dll format, enable Unicode support if necessary (+ in the File / Format menu, select the encoding of the source files UTF8), thread safety, or assembly language support.

The only inconvenience is the lack of unsigned long type in PureBasic, for this I had to write additional functions.

In the extension descriptor, we indicate the platform, the extension library, and 2 unique exported functions (initializer and finalizer):
 <?xml version="1.0" encoding="UTF-8"?> <extension xmlns="http://ns.adobe.com/air/extension/3.1"> <id>com.pure.Extension</id> <name>pureair</name> <copyright>compile4fun 2012</copyright> <description>Extenion for Adobe AIR</description> <versionNumber>1.0.0</versionNumber> <platforms> <platform name="Windows-x86"> <applicationDeployment> <nativeLibrary>pureair.dll</nativeLibrary> <initializer>initializer</initializer> <finalizer>finalizer</finalizer> </applicationDeployment> </platform> </platforms> </extension> 

Id must match the specified in the application manifest and the expansion pack (ActionScript), the application profile - extendedDesktop.

The mandatory part of the extension is AttachProcess (Instance), DetachProcess (Instance), AttachThread (Instance) and DetachThread (Instance) functions, and also:

 ... ;CDecl ProcedureC contextInitializer(extData.l, ctxType.s, ctx.l, *numFunctions.Long, *functions.Long) *log\info("create context: " + Str(ctx) + "=" + Utf8ToUnicode(ctxType)) Define result.l ;exported extension functions count: Define size.l = 1 ;Array of FRENamedFunction: Dim f.FRENamedFunction(size - 1) ;there is no unsigned long type in PB setULong(*numFunctions, size) ;If you want to return a string out of a DLL, the string has to be declared as Global before using it. ;method name f(0)\name = asGlobal("showDialog") ;function data example f(0)\functionData = asGlobal("showDialog") ;function pointer f(0)\function = @showDialog() *functions\l = @f() ;some additional data can be stored extData = #Null ;native data example result = FRESetContextNativeData(ctx, asGlobal("FRESetContextNativeData")) *log\Debug(ResultDescription(result, "FRESetContextNativeData")) *log\info("create context complete"); EndProcedure ;CDecl ProcedureC contextFinalizer(ctx.l) *log\info("dispose context: " + Str(ctx)) EndProcedure ;CDecl ProcedureCDLL initializer(extData.l, *ctxInitializer.Long, *ctxFinalizer.Long) *ctxInitializer\l = @contextInitializer() *ctxFinalizer\l = @contextFinalizer() EndProcedure ;CDecl ;this method is never called on Windows... ProcedureCDLL finalizer(extData.l) ;do nothing EndProcedure 

The FRENamedFunction array contains the methods of our extension (in this case, only one - showDialog), it is also possible to bind any data to a function or an extension instance. Attention should be paid to the type of call CDecl and the function asGlobal, which specifically allocates memory for the strings passed from the extension to the main program, this is mentioned in the PureBasic dll help.

Our extension will show a modal dialog with an arbitrary set of buttons and text and transmit the event of closing the dialog:
 Structure MessageParameters text.s title.s dwFlags.l ctx.l EndStructure Procedure ModalMessage(*params.MessageParameters) Define result.l, code.l code = MessageRequester(*params\title, *params\text, *params\dwFlags) result = FREDispatchStatusEventAsync(*params\ctx, asGlobal("showDialog"), asGlobal(Str(code))) *log\Debug (ResultDescription(result, "FREDispatchStatusEventAsync")) EndProcedure ;CDecl ProcedureC.l showDialog(ctx.l, funcData.l, argc.l, *argv.FREObjectArray) *log\info("Invoked showDialog") Define result.l ;ActionScriptData example Define actionScriptObject.l, actionScriptInt.l, type.l result = FREGetContextActionScriptData(ctx, @actionScriptObject) *log\Debug(ResultDescription(result, "FREGetContextActionScriptData")) result = FREGetObjectType(actionScriptObject, @type) *log\Debug("result=" + ResultDescription(result, "FREGetObjectType")) *log\info("ContextActionScriptData: type=" + TypeDescription(type)) result = FREGetObjectAsInt32(actionScriptObject, @actionScriptInt) *log\Debug("result=" + ResultDescription(result, "FREGetObjectAsInt32")) *log\info("ContextActionScriptData: actionScriptInt=" + Str(actionScriptInt)) ;function data example Define funcDataS.s funcDataS = PeekS(funcData, -1, #PB_Ascii) *log\info("FunctionData: " + funcDataS) *log\info("Method args size: " + Str(fromULong(argc))) Define resultObject.l, length.l, booleanArg.l, dwFlags.l, message.s, *string.Ascii result = FREGetObjectAsBool(*argv\object[0], @booleanArg) *log\Debug("result=" + ResultDescription(result, "FREGetObjectAsBool")) result = FREGetObjectAsInt32(*argv\object[1], @dwFlags) *log\Debug("result=" + ResultDescription(result, "FREGetObjectAsInt32")) result = FREGetObjectAsUTF8(*argv\object[2], @length, @*string) *log\Debug("result=" + ResultDescription(result, "FREGetObjectAsUTF8")) message = PeekS(*string, fromULong(length) + 1) *log\info("Argument: booleanArg=" + Str(fromULong(booleanArg))) *log\info("Argument: dwFlags=" + Str(dwFlags)) *log\info("Argument: message=" + Utf8ToUnicode(message)) ;native data example Define native.l, nativeData.s result = FREGetContextNativeData(ctx, @native) *log\Debug(ResultDescription(result, "FREGetContextNativeData")) nativeData = PeekS(native, -1, #PB_Ascii) *log\info("FREGetContextNativeData: " + nativeData) Define *params.MessageParameters = AllocateMemory(SizeOf(MessageParameters)) *params\ctx = ctx *params\title = "PureBasic" *params\text = Utf8ToUnicode(message) *params\dwFlags = dwFlags CreateThread(@ModalMessage(), *params) ;return Boolean.TRUE result = FRENewObjectFromBool(toULong(1), @resultObject) *log\Debug(ResultDescription(result, "FRENewObjectFromBool")) ProcedureReturn resultObject EndProcedure 

From the Air side, it looks like this:
 package com.pure { import flash.events.StatusEvent; import flash.external.ExtensionContext; import mx.logging.ILogger; import mx.logging.Log; /** * Wrapper for PureBasic extension */ public class Extension { /** * Extension id, must be specified in air-manifest.xml and extension.xml */ public static const CONTEXT:String = "com.pure.Extension"; private static const log:ILogger = Log.getLogger(CONTEXT); /** * @private */ private var _context:ExtensionContext; /** * @private */ private var contextType:String; /** * Creates context * @param contextType default value is "PureAir" * @param actionScriptData any number */ public function Extension(contextType:String = "PureAir", actionScriptData:int = 4) { //random type this.contextType = contextType + Math.round(Math.random() * 100000); try { log.debug("Creating context: {0}, contextType: {1}", CONTEXT, this.contextType); _context = ExtensionContext.createExtensionContext(CONTEXT, this.contextType); if (_context == null) { //creation failed log.error("Failed to create context: {0}, contextType: {1}", CONTEXT, this.contextType); } else { log.debug("Context was created successfully"); //listen for extension events _context.addEventListener(StatusEvent.STATUS, onStatusEvent); //set actionScript data _context.actionScriptData = actionScriptData; } } catch(e:Error) { log.error("Failed to create context: {0}, contextType: {1}, stacktrace: {2}", CONTEXT, this.contextType, e.getStackTrace()); } } private function get contextCreated():Boolean { return _context != null; } /** * Test method, shows YesNoCancel modal dialog * @param booleanArg boolean parameter * @param flags integer parameter, #PB_MessageRequester_YesNoCancel=3, #MB_APPLMODAL = 0 * @param message string parameter * @return */ public function showDialog(booleanArg:Boolean, flags:int, message:String):Boolean { if (!contextCreated) return false; var result:Boolean = false; try { result = _context.call('showDialog', booleanArg, flags, message) as Boolean; if (!result) { log.error("Invocation error: test({0}, {1}, {2})", booleanArg, flags, message); } } catch (e:Error) { log.error("Invocation error: test({0}, {1}, {2}), stacktrace: {3}", booleanArg, flags, message, e.getStackTrace()); } return result; } private function onStatusEvent(event:StatusEvent):void { log.info("Status event received: contextType={0} level={2}, code={1}", this.contextType, event.code, event.level); } /** * Performs clean-up */ public function dispose():void { if (_context) { _context.dispose(); //clean all references _context.removeEventListener(StatusEvent.STATUS, onStatusEvent); _context = null; log.info("Disposed {0}", this.contextType); } else { log.warn("Can not dispose {0}: Context is null", this.contextType); } } } } 

StatusEvent is the only event type that an extension can transmit.

The result of the work can be seen in the screenshot:
image

Thanks for attention.

Source: https://habr.com/ru/post/147089/


All Articles