- PVSM.RU - https://www.pvsm.ru -
На волне растущей здесь популярности PureBasic [1], предлагаю ознакомится с еще одной областью применения этого языка.
Начиная с третьей версии Air, появилась возможность [2] компенсировать ограниченность SDK за счет расширений (Flash Runtime Extensions). Расширения можно писать на С/С++/Java(Android) и на любом другом языке, позволяющем создавать нативные библиотеки под соответствующие платформы.
Скорость вычислений, многопоточность, взаимодействие с операционной системой — все это доступно для расширений. Некоторое время назад я написал раcширение на С для Mac и Windows — обертку для кроссплатформенной библиотеки HIDAPI [3]. Несмотря на некоторые сложности, удобство заключалось в кроссплатформенности библиотеки, что позволило написать код расширения один раз и собрать под каждую платформу с минимальными усилиями.
PureBasic, в свою очередь, предоставляет возможность создавать приложения (dll, so, драйвера [4]) для нескольких систем. Присутствует возможность оптимизации, поддержка юникода, готовые объекты (List, Map), набор кроссплатформенных функций (алгоритмы сжатия, обработки изображений, шифрование), под Windows импортирована большая часть WINAPI, макросы и даже ООП. Все это существенно ускоряет разработку (для тех, у кого нет за плечами опыта С/С++), по сравнению с голым С.
Исходники и демо-приложение для Windows доступны на code.google.com [5].
Первым шагом стал импорт [6] функций из билиотеки FlashRuntimeExtensions.lib (Windows, {AIR SDK}libwin), это делается сравнительно просто, при наличии 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
В настройках компилятора необходимо указать формат Shared Dll, включить при необходимости поддержку юникода (в меню File/Format выбрать кодировку исходных файлов UTF8), потокобезопасность или поддрежку ассемблера.
Единственное неудобство — отсутствие типа unsigned long в PureBasic, для это пришлось написать дополнительные функции.
В дескрипторе расширения указываем платформу, библиотеку расширения и 2 единственные экспортируемые функции (initializer и 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 должен соответствовать указанному в манифесте приложения и пакету расширения(ActionScript), профиль приложения — extendedDesktop.
Обязательная часть расширения это функции AttachProcess(Instance), DetachProcess(Instance), AttachThread(Instance) and DetachThread(Instance), а также:
...
;CDecl
ProcedureC contextInitializer(extData.l, ctxType.s, ctx.l, *numFunctions.Long, *functions.Long)
*loginfo("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()
*functionsl = @f()
;some additional data can be stored
extData = #Null
;native data example
result = FRESetContextNativeData(ctx, asGlobal("FRESetContextNativeData"))
*logDebug(ResultDescription(result, "FRESetContextNativeData"))
*loginfo("create context complete");
EndProcedure
;CDecl
ProcedureC contextFinalizer(ctx.l)
*loginfo("dispose context: " + Str(ctx))
EndProcedure
;CDecl
ProcedureCDLL initializer(extData.l, *ctxInitializer.Long, *ctxFinalizer.Long)
*ctxInitializerl = @contextInitializer()
*ctxFinalizerl = @contextFinalizer()
EndProcedure
;CDecl
;this method is never called on Windows...
ProcedureCDLL finalizer(extData.l)
;do nothing
EndProcedure
Массив FRENamedFunction содержит методы нашего расширения (в данном случае всего один — showDialog), также есть возможность привязать к функции или экземпляру расширения какие-либо данные. Следует обратить внимание на тип вызова CDecl и функцию asGlobal, которая специально выделяет память для строк передаваемых из асширения в основную программу, это упоминается в справке PureBasic по работе с dll.
Наше расширение будет показывать модальный диалог с произвольным набором кнопок и текстом и передавать событие закрытия диалога:
Structure MessageParameters
text.s
title.s
dwFlags.l
ctx.l
EndStructure
Procedure ModalMessage(*params.MessageParameters)
Define result.l, code.l
code = MessageRequester(*paramstitle, *paramstext, *paramsdwFlags)
result = FREDispatchStatusEventAsync(*paramsctx, asGlobal("showDialog"), asGlobal(Str(code)))
*logDebug (ResultDescription(result, "FREDispatchStatusEventAsync"))
EndProcedure
;CDecl
ProcedureC.l showDialog(ctx.l, funcData.l, argc.l, *argv.FREObjectArray)
*loginfo("Invoked showDialog")
Define result.l
;ActionScriptData example
Define actionScriptObject.l, actionScriptInt.l, type.l
result = FREGetContextActionScriptData(ctx, @actionScriptObject)
*logDebug(ResultDescription(result, "FREGetContextActionScriptData"))
result = FREGetObjectType(actionScriptObject, @type)
*logDebug("result=" + ResultDescription(result, "FREGetObjectType"))
*loginfo("ContextActionScriptData: type=" + TypeDescription(type))
result = FREGetObjectAsInt32(actionScriptObject, @actionScriptInt)
*logDebug("result=" + ResultDescription(result, "FREGetObjectAsInt32"))
*loginfo("ContextActionScriptData: actionScriptInt=" + Str(actionScriptInt))
;function data example
Define funcDataS.s
funcDataS = PeekS(funcData, -1, #PB_Ascii)
*loginfo("FunctionData: " + funcDataS)
*loginfo("Method args size: " + Str(fromULong(argc)))
Define resultObject.l, length.l, booleanArg.l, dwFlags.l, message.s, *string.Ascii
result = FREGetObjectAsBool(*argvobject[0], @booleanArg)
*logDebug("result=" + ResultDescription(result, "FREGetObjectAsBool"))
result = FREGetObjectAsInt32(*argvobject[1], @dwFlags)
*logDebug("result=" + ResultDescription(result, "FREGetObjectAsInt32"))
result = FREGetObjectAsUTF8(*argvobject[2], @length, @*string)
*logDebug("result=" + ResultDescription(result, "FREGetObjectAsUTF8"))
message = PeekS(*string, fromULong(length) + 1)
*loginfo("Argument: booleanArg=" + Str(fromULong(booleanArg)))
*loginfo("Argument: dwFlags=" + Str(dwFlags))
*loginfo("Argument: message=" + Utf8ToUnicode(message))
;native data example
Define native.l, nativeData.s
result = FREGetContextNativeData(ctx, @native)
*logDebug(ResultDescription(result, "FREGetContextNativeData"))
nativeData = PeekS(native, -1, #PB_Ascii)
*loginfo("FREGetContextNativeData: " + nativeData)
Define *params.MessageParameters = AllocateMemory(SizeOf(MessageParameters))
*paramsctx = ctx
*paramstitle = "PureBasic"
*paramstext = Utf8ToUnicode(message)
*paramsdwFlags = dwFlags
CreateThread(@ModalMessage(), *params)
;return Boolean.TRUE
result = FRENewObjectFromBool(toULong(1), @resultObject)
*logDebug(ResultDescription(result, "FRENewObjectFromBool"))
ProcedureReturn resultObject
EndProcedure
Со стороны Air это выглядит так:
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 единственный тип события, которое может передавать расширение.
Результат работы можно увидеть на скриншоте:
Спасибо за внимание.
Автор: kemsky
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/10763
Ссылки в тексте:
[1] PureBasic: http://habrahabr.ru/post/64457/
[2] возможность: http://www.adobe.com/devnet/air/articles/extending-air.html
[3] HIDAPI: http://www.signal11.us/oss/hidapi/
[4] драйвера: http://habrahabr.ru/post/145926/
[5] code.google.com: http://code.google.com/p/purebasic-extension-for-adobe-air/
[6] импорт: http://code.google.com/p/purebasic-extension-for-adobe-air/source/browse/trunk/pureair/native/src/FlashRuntimeExtensions.pb
Нажмите здесь для печати.