SWPy - синхронно-асинхронный Flash проектор для Python
Доброго времени суток, дорогая общественность!
Этот блог я хочу посвятить своему проекту SWPy. SWPy расшифровывается как ScreenWeaver for Python, и является портом довольно известного проекта ScreenWeaver HX, за авторством еще более известного Николя Канасье, который также является создателем языка haXe и виртуальной машины Neko.
Побуждающим фактором к началу работ над данным проектом послужил мой повышенный интерес к замечательному языку Python и связанность с ним моей рабочей деятельности, а так же мой интерес к Flash-технологиям и к становящейся все более популярной теме RIA приложений.
Не секрет, что при всех своих достоинствах Python до сих пор не имеет вменяемого GUI Toolkit-а, за исключением разве что PyQt. С мультимедиа возможностями там тоже дела обстоят не менее грустно.
Итак, как это все должно работать:
Как уже упоминалось, за основу берется уже упомянутый тут ScreenWeaver HX от француза, точнее только те модуля, которые отвечают за инициализацию/деинициализацию, отрисовку окон, загрузку флэша и т.д. Обертку над всем этим для интеграции в Питон я и делаю.
Python я решил использовать изначально 3-й, чтоб потом не пришлось заново все на него портировать.
Для отображения флэша используется обычный браузерный Flash Plugin, он же NPSWF32.dll в Винде, он же libflashplayer.so в Линухе. Никакого ActiveX - библиотека в будущем планируется кроссплатформенной.
Взаимодействие между флэшом и питоном осуществляется через External API. Для сериализации и десериализации я вначале планировал использовать Pickle, поскольку в питоне он есть нативный, то есть весьма быстрый, уже даже для AS написал аналоги Pickler и Unpickler, но потом впомнил про либу PyAMF, которую я раньше использовал для вэб-сервисов, но производительность оставляла желать лучшего, но оказалось что у нее есть и намного более быстрый нативный вариант. Таким образом процессы сериализации/десериализации на обеих сторонах будут происходить на полной скорости.
На данный момент уже реализован следующий функционал:
- импорт модуля
- инициализация (загрузка и запуск Flash Plugin-a)
- создание окошек
- загрузка SWF-ок
- проходят вызовы из флэша через ExternalInterface.call() в питон.
Последнюю неделю воюю с обратными вызовами из питона во флэш. Во флэше регистрирую метод через ExternalInterface.addCallback() а в модуле пытаюсь вызвать его через метод Invoke() скриптового объекта, который я запрашиваю у плагина. Но хоть кол на голове теши, он мне возвращает FALSE. Причем в оригинальном ScreenWeaver-е эта конструкция работает на ура.
Я уже весь код перекопал, везде все правильно инициализируется, структуры все заполняются, вызов производится из главного трэда, как это положено. А ничего не работает. Код функции, оставшийся без изменений от оригинального проекта:
Цитата:
char *flashp_call_in(flash *f, const char *s, const char *params) {
NPVariant p[2], r;
char *result;
if( f->object == NULL )
return NULL;
p[0].type = NPVariantType_String;
p[0].value.stringValue.utf8characters = s;
p[0].value.stringValue.utf8length = strlen(s);
p[1].type = NPVariantType_String;
p[1].value.stringValue.utf8characters = params;
p[1].value.stringValue.utf8length = strlen(params);
r.type = NPVariantType_Void;
if( !f->object->_class->invoke( f->object, (NPIdentifier)SPECIAL_IDENTIFIER, (NPVariant *)p, 2, &r ) ) <- здесь ошибка
return NULL;
if( r.type == NPVariantType_String )
result = strdup(r.value.stringValue.utf8characters);
DoReleaseVariant(&r);
return result;
}
NPVariant p[2], r;
char *result;
if( f->object == NULL )
return NULL;
p[0].type = NPVariantType_String;
p[0].value.stringValue.utf8characters = s;
p[0].value.stringValue.utf8length = strlen(s);
p[1].type = NPVariantType_String;
p[1].value.stringValue.utf8characters = params;
p[1].value.stringValue.utf8length = strlen(params);
r.type = NPVariantType_Void;
if( !f->object->_class->invoke( f->object, (NPIdentifier)SPECIAL_IDENTIFIER, (NPVariant *)p, 2, &r ) ) <- здесь ошибка
return NULL;
if( r.type == NPVariantType_String )
result = strdup(r.value.stringValue.utf8characters);
DoReleaseVariant(&r);
return result;
}
В общем, как разберусь с этой багой, продолжу

Всего комментариев 16
Комментарии
![]() ![]() |
|
по поводу де//сериализации можно посмотреть в сторону google protobuf
|
![]() ![]() |
|
alexcon314
Я тут гриппую, поэтому голова сейчас особо не работает, но попробую объяснить, как это работает, точнее - как я это понимаю. Каждый Netscape Plugin при загрузке хостом, помимо всего прочего, отдает ему структуру NPClass, которая описывает интерфейс взаимодействия со скрипт-объектом. В данной структуре имеется поле invoke, которое инициализируется указателем на соответвующую процедуру. Она используется для вызова методов скрипт-объекта, предоставляемого плагином. В свою очередь хост отдает плагину структуру NPNetscapeFuncs, поля invoke и evaluate которой заполняет указателями на процедуры, реализованные разработчиком хоста. evaluate используется для вызова методов хоста, а invoke - для вычисления результата обратных вызовов. Имея опыт работы с ActiveX-флэшом, я надеялся, что при приеме и выполнении вызовов через ExternalInterface мне также придется иметь дело с обычным XML и в плагине. Но не тут то было. Так как плагин нацелен на работу в первую очередь в браузере, и взаимодействие с хостом предполагается через JavaScript, оказалось, что при вызовах ИЗ флэша, в методе evaluate хоста, мы получаем не какой-то абстрактный XML, а строку в виде целой JavaScript-овой конструкции: Код:
try { __flash__toXML(method_name("param")); } catch (e) { "<undefined/>"; } Код:
ExternalInterface.call(method_name, "param") При вызове же в обратную сторону - из хоста во флэш - все намного интереснее. Сначала формируем массив параметров, совместимый с форматом вызова методов по NPAPI, потом вызываем метод invoke скриптабельного объекта, предоставляемого плагином(этот код присутствует в теле поста блога). А затем начинается самое интересное. Плагин вызывает метод invoke нашего хоста, и передает ему такую строку: Код:
__flash__request(method_name, param) Код:
function method_name(param:String):void {} Не спрашивайте меня, зачем такой изврат - и сам уже почти неделю в акуе. Естественно, что никаким AMF там и не пахнет, там просто строка передается. Но мне большего и не надо - если в ByteArray можно сериализовать в принципе все что угодно, то потом его достаточно по Base64 закодировать - и можно перегнать куда угодно в виде строки. |
|
Обновил(-а) Skyggedans 17.10.2009 в 19:33
|
![]() ![]() |
|
alexcon314
Парсится хостом, понятное дело, отдается в out-параметр result метода invoke хоста. Я ж говорю, как-то добился адекватной работоспособности, сам до конца не поняв как ![]() Во время инициализации модуля создаю невидимое окно со своим message loop-ом, который крутится в главном потоке, а из коллбэка в это окно посылаю SendMessage() с определенным id (WM_SYNC_CALL) у которого wParam это некий Python callable (функция, грубо говоря), а lParam - кортеж с параметрами. В цикле эта мессага обрабатывается, а результат вызова возвращается как указатель на PyObject (который может быть чем угодно, в чем главная вкусность Питона). В общем, если интересно, скачайте сорцы ScreenWeaver-a, а там по этой теме интерес представляют модули flash.c и np_host.c А за пожелание выздоровления - отдельное спасибо ![]() |
|
Обновил(-а) Skyggedans 18.10.2009 в 00:33
|
![]() ![]() |
|
Я тут параллельно занялся вопросом добавления в интерфейс такого модного эффекта, как стекло в висте/семерке, но не могу допереть как правильно отображать на стекле прозрачный флэш. Через UpdateLayeredWindow почему-то не получается.
А если заполнять буфер предварительно цветом, который выбран в качестве прозрачного через LWA_COLORKEY в SetLayeredWindowAttributes(), то участки с частичной прозрачностью просто смешиваются с данным цветом и естественно не отсекаются. |
|
Обновил(-а) Skyggedans 24.10.2009 в 18:41
|
![]() ![]() |
|
Под вистой появился расширенный вин-апи для этих фич вроде..
Однозначно через UpdateLayeredWindow() . Это сложнее, но иначе никак. ЗЫ. Эхх.. тоже этим интересовался. Жаль профукал исходнк один, на асме правда, но рисовал очень качественные полупрозрачные окна, причем, на основе то ли пнг, то ли джипега, не помню..да, с интеракивом еще... на русском форуме каком-то видел. Вот еще один исходник могу предложить. Там класс для отрисовки тени окна в стиле Виста Аэро. Посмотрите, должно помочь. думаю. http://mdm-zinc.narod.ru/pub/Shadow.zip |
![]() ![]() |
|
Вобщем, вычитал я о компоненте ActivePython от ActiveState. Его наличие в системе позволяет запускать скрипты в MSScriptControl. Типа так
Код:
'testpy.vbs Set ScrptCtrl = CreateObject("MSScriptControl.ScriptControl") ScrptCtrl.Language = "python" ScrptCtrl.AddCode "def test():" & vbCrLf & _ " import win32api" & vbCrLf & _ " win32api.Beep(440, 500)" & vbCrLf & _ " return u'Привет, MSScriptControl & Python!'" res = ScrptCtrl.Run("test") WScript.Echo res ![]() Занимаясь расширением флэш-проектора, я набрел на возможность создавать экземпляр MSScriptControl прямо в оболочке, ну и общаться с ним: генерить скрипты на лету в АС, отправлять на исполнение, получать результаты и т.д. Т.е. так же можно исполнять любой .ру скрипт, и не только .ру, но и перл, скажем, и даже подключать .net сборки. Кроме того, есть возможность закружать целые библиотеки классов в скрипт-машину, того же питона. Грузить можно из файла, из ресурсов длл, из сети и пр. Можно писать ком-серверы на скриптах, размещать их в сети, подключаться к ним и пр.. Конечно, нужен установленный ActivePython-компонент и т.п. (vbs в винде зашит по умолчанию). Никак не соображу, нужно это вообще или все эти возможности малопригодны для практического использования. По идее, написав либу на питоне, сунуть ее в ресурсы той же оболочки и можно подрубать ее к флэш-гую в проекторе... Можно использовать .net-классы ... блин голова кругом идет, повторяюсь). Вобщем, вот такая рабочая конструкция примерно получается в АС: PScript.initEngine("python"); var py:String = "class MyClass:\r"; py += " def test(self):\r"; py += " import win32api\r"; py += " win32api.Beep(440, 500)\r"; py += " return u'Привет, MSScriptControl & Python!'\r"; py += "x = MyClass()\r"; out(PScript.add(py)); out(PScript.call("x.test()\r"));// out: Привет, MSScriptControl & Python! Или так: PScript.initEngine("perlscript"); var pl:String ="use Win32;\r" pl += "sub MsgBox {\r"; pl += " my ($caption, $message, $icon_buttons) = @_;\r"; pl += " my @return = qw/- Ok Cancel Abort Retry Ignore Yes No/;\r"; pl += " my $result = Win32::MsgBox($message, $icon_buttons, $caption);\r"; pl += " return $return[$result];\r"; pl += "}"; o1(PScript.add(pl)); o1(PScript.call("MsgBox(\"Test\", \"This is a test\", 48);\r")); |
|
Обновил(-а) alexcon314 30.10.2009 в 16:41
|
![]() ![]() |
|
Отладил наконец свой экшскриптовый аналог Unpickler-а, и попробовал его на предмет быстродействия.
На стороне питона делаю select ~27000 записей из SQLite базы, конверчу в список словарей, где каждый словарь это одна запись, пикляю, зиплю через zlib, кодирую по Base64 и отдаю флэшу. Каждая запись это 2 int(4)-а и один char(512). На стороне флэша провожу обратные преобразования, анпиклю, получаю Array Object-ов и отдаю его DataGrid-y. Времени это занимает ~7 секунд, причем без компрессии почему-то получается секунд 11. Размер массива ~4.5 метра без компрессии, ~800 кил с компрессией. Не знаю, много это или мало, в старом проекторе, который работал нативно с XML-ем это занимало секунд 4-5. Но то, что это шустрее чем ненативный PyAMF это точно. |
|
Обновил(-а) Skyggedans 03.11.2009 в 20:11
|
![]() ![]() |
|
Хм, попробовал дома на трехлетнем Х2 3800+, отрабатывает за 3 секунды. Неплохо, в старом проекторе длительность этого процесса не зависела от платформы.
|
![]() ![]() |
|
По поводу UpdateLayeredWindow хочу добавить.
Я тут поэкспериментировал со своим проектором на основе ActiveX-контрола с целью отобразить прозрачный контрол в прозрачном окне, ну типа транспарент моде, как в цинке. Получилось сделать примерно так: -делаю layered окно; -аттачу контрол, выставляю ему режим транспарент; -гружу ролик; -делаю мемори-контекст для окна через CreatreCompatibleDC; -создаю в нем битмап через CreateDIBSection; -отрисовываю флэш-контент через интерфейс контрола IViewObject->Draw() в этот контекст; -делаю UpdateLayeredWindow(), отрисовываю мемори-контекст в конткест окна. Типа того. Без GDI+. GDI+ можно заюзать (не обязательно через нее, просто с GD+ писанины много меньше), скажем для отрисовки статического/динамического фона под флэш-контентом (или над ним, гы)), скажем, пнг-стекло. Результат - отображается прозрачное окно с прозрачным флэшем. Что не получилось: обновление содержимого (анимация, интерактив) идет криво, но, думаю это дожать можно, наверно надо копнуть метод SetAdvise IViewObject'а, который отвечает за рассылку события об обновлении контента. А может с контекстами помудрить надо... Вобщем, истина где-то рядом). ЗЫ. идея основана на вот этом. смотреть исходник, тот что на VB в архиве выложен. |
|
Обновил(-а) alexcon314 09.11.2009 в 11:46
|
Последние записи от Skyggedans