Как создать портабельное GUI приложение на wxPerl
13 сентября 2011
Одна из проблем интерпретируемых языков заключается в зависимости скриптов от наличия у пользователя соответствующего интерпретатора и модулей, используемых в коде скрипта. В мире UNIX эти проблемы никого не беспокоят благодаря менеджерам пакетов. К сожалению, 90% наших потенциальных пользователей сидят не под UNIX.
***
Спрашивается — есть ли способ писать на скриптовых языках программы, рассчитанные на широкую аудиторию? То есть, аудиторию, большая часть которой не в состоянии скачать Strawberry Perl и поставить необходимые модули из CPAN (если речь о Perl). Очевидно, есть как минимум два возможных решения.
Первое решение заключается в том, чтобы транслировать скрипт в программу на каком-нибудь C/C++, а затем собрать ее с помощью GCC. Только в этом случае придется отказаться от некоторых возможностей языка. В Perl, например, этими возможностями будут eval(), а также модули типа Moose или Inline::*. Однако возникает вопрос — раз мы все равно не используем всей гибкости скриптовых языков, может тогда лучше сразу писать на C++ или, скажем, Haskell? У последнего, кстати, есть и компилятор, и интерпретатор.
Насколько мне известно, описанный выше подход в Perl вообще не используется. В Python напротив, соответствующих проектов в изобилии — см PyPy, Shedskin и Cython. Каждый из них заслуживает отдельной заметки, так что в рамках данного поста они не рассматриваются.
Второе возможное решение проблемы — таскать вместе с программой интерпретатор и все необходимые модули. Несмотря на свою топорность, это решение является довольно популярным и вполне рабочим. В частности, программы типа perl2exe и py2exe используют именного его. Вот об этом решении в контексте языка Perl и будет рассказано далее по тексту. Забегая вперед, скажу, что программа получится вполне приемлемого размера и будет стартовать безо всяких задержек.
***
Как нам с вами хорошо известно, пользователи Windows не сильно жалуют консольные программы. В связи с этим нам понадобятся биндинги какой-нибудь библиотеки для создания GUI. Вопросу выбора GUI библиотеки в свое время я уделил много внимания и остановился на wxWidgets. Ее и будем использовать.
Соответствующая библиотека для Perl называется wxPerl. Где ее взять? Самый простой способ — это использовать сборку Perl под названием Citrus Perl. Эта сборка отличается от Active Perl и Strawberry Perl тем, что она «из коробки» имеет очень много готовых модулей, включая wxPerl. Я пользуюсь Citrus Perl уже не первый месяц и полностью им доволен.
Устанавливается Citrus Perl не совсем обычным образом. Скачиваем установщик отсюда, запускаем cmd.exe и говорим:
Здесь d:\coding-stuff\ — это директория, куда вы хотите поставить Citrus Perl. После распаковки всех файлов говорим:
Затем идем в «Компьютер → Дополнительные параметры системы → Переменные среды» и дописываем «d:\coding-stuff\CitrusPerl\x86\5-12\bin» в переменную окружения PATH. Согласен, нормальный инсталятор не помешал бы, но мы с вами программисты или где? Наконец, запускаем новый экземпляр cmd.exe и проверяем, все ли работает:
d:\coding-stuff\CitrusPerl\x86\5-12\bin\wxperl_demo.bat
Лично я также скачал Padre и библиотеку GD:
cpan -i PPM
ppm install http://www.bribes.org/perl/ppm/GD.ppd
Дополнение: В недавно вышедшем Citrus Perl Release 8 библиотеки GD и GD::Graph идут «из коробки».
Чтобы не писать код GUI вручную, можно воспользоваться wxGlade или wxFormBuilder. Последний не умеет генерировать Perl-код, но на CPAN доступен модуль FBP::Perl, компенсирующий этот недостаток. В wxGlade есть генератор Perl-кода, правда сама программа требует наличия Python.
***
Для этой заметки я написал небольшой скрипт, убирающий «мусор» из дизассемблерного листинга, получаемого с помощью IDA.
Код скрипта:
# ida2code.pl v 0.1
# (c) Alexandr A Alexeev 2011 | http://eax.me/
use Wx 0.15 qw[:allclasses];
use strict;
package MyFrame;
use Wx qw[:everything];
use base qw(Wx::Frame);
use strict;
sub new {
my( $self, $parent, $id, $title, $pos, $size, $style, $name ) = @_;
$parent = undef unless defined $parent;
$id = -1 unless defined $id;
$title = "" unless defined $title;
$pos = wxDefaultPosition unless defined $pos;
$size = wxDefaultSize unless defined $size;
$name = "" unless defined $name;
# begin wxGlade: MyFrame::new
$style = wxDEFAULT_FRAME_STYLE
unless defined $style;
$self = $self->SUPER::new( $parent, $id, $title, $pos, $size, $style, $name );
$self->{textedit} = Wx::TextCtrl->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxHSCROLL);
$self->{button_1} = Wx::Button->new($self, -1, "Remove /.text:[0-9a-f]+/");
$self->__set_properties();
$self->__do_layout();
Wx::Event::EVT_BUTTON($self, $self->{button_1}->GetId, \&onPressButton);
# end wxGlade
return $self;
}
sub __set_properties {
my $self = shift;
# begin wxGlade: MyFrame::__set_properties
$self->SetTitle("Ida2code v 0.1 | http://eax.me/");
$self->SetSize(Wx::Size->new(400, 300));
# end wxGlade
}
sub __do_layout {
my $self = shift;
# begin wxGlade: MyFrame::__do_layout
$self->{sizer_1} = Wx::BoxSizer->new(wxVERTICAL);
$self->{sizer_1}->Add($self->{textedit}, 1, wxEXPAND, 0);
$self->{sizer_1}->Add($self->{button_1}, 0, wxALIGN_CENTER_HORIZONTAL, 0);
$self->SetSizer($self->{sizer_1});
$self->Layout();
# end wxGlade
}
sub onPressButton {
my ($self, $event) = @_;
# wxGlade: MyFrame::onPressButton <event_handler>
# warn "Event handler (onPressButton) not implemented";
my $text = $self->{textedit}->GetValue();
$text =~ s#\.text:[0-9a-f]+\s##gi;
$text =~ s#\; CODE XREF\:[^\r\n]+##sgi;
$text =~ s#\; \-\-\-\-\-[^\r\n]+##sgi;
$self->{textedit}->SetValue($text);
$event->Skip;
# end wxGlade
}
# end of class MyFrame
1;
1;
package main;
unless(caller){
local *Wx::App::OnInit = sub{1};
my $app = Wx::App->new();
Wx::InitAllImageHandlers();
my $frame_1 = MyFrame->new();
$app->SetTopWindow($frame_1);
$frame_1->Show(1);
$app->MainLoop();
}
Весь функционал сосредоточен в функции onPressButton — взять текст из поля ввода, прогнать через регулярные выражения и запихнуть обратно. Остальной код был сгенерирован в wxGlade.
Демонстрационный скрипт, созданный с помощью FBP::Perl, можно скачать здесь. Его я выложил так, на всякий случай.
***
У меня не нашлось лишних 150$ на perl2exe и тем более 300$ на PerlApp, так что я решил попытать счастье с модулем PAR::Packer. Программа Cava Packager тоже выглядит неплохо, но до нее у меня руки не дошли.
pp -o test.exe test.pl
С обычными консольными программами он справляется на отлично, но при работе со скриптами, использующими wxPerl, нуждается в небольшой поддержке:
Согласно документации к Wx::Perl::Packager, от нас требуется просто прописать в нужном месте
… после чего команда:
… магическим образом создаст exe-шник. К сожалению, на практике все оказалось не так просто. У меня программа, созданная с помощью wxpar, выдавала такую ошибку:
Решение проблемы удалось нагуглить. Оказывается, достаточно просто заменить в скрипте строчку:
… на:
И действительно, exe’шник, созданный из пропатченного скрипта, уже не выводит сообщений об ошибках. Он ничего не выводит. Вообще. В смысле — даже окон.
Поковыряв exe’шник и немного погуглив, я так и не нашел решения проблемы. Как я понял, она заключается в том, что упакованный скрипт не может найти модуль Win32.pm, хотя он присутствует в пакете. Ну, раз проблему не удается решить, попробуем ее обойти.
Оказывается, полученный exe’шник представляет собой самораспаковывающийся архив и его можно открыть в любом современном архиваторе. Распаковываем содержимое архива в отдельную директорию. Динамические библиотеки из подкаталога shlib кладем в «корень». Туда же копируем perl.exe, perl512.dll и libgcc_s_sjlj-1.dll из CitrusPerl\x86\5-12\bin. Открываем файл script/ida2code.pl и удаляем из него строчку:
Затем создаем run.bat следующего содержания:
Запускаем и… оно работает! Несложно проверить, что теперь скрипт будет работать с флешки даже на компьютере без установленного Perl. Вообще-то мы убили сразу двух зайцев — скрипт теперь не только работает, но и запускается моментально, а не в течение нескольких секунд.
Для удобства пользователя вместо bat-файла можно положить специальную небольшую программку с красивой иконкой или сделать инсталятор. Что касается размера получившегося приложения, в zip-архиве оно весит 9 Мб, а в 7z — 5 Мб, что по нынешним меркам не так уж много. Я использовал параметры сжатия по умолчанию, на случай, если на компьютере пользователя мало оперативной памяти. Следует учесть, что мы можем распространять приложение в двух вариантах — «все в одном» и «поставь Perl и нужные CPAN модули сам».
***
PS. Стоит попробовать написать архиватор, заточенный для сжатия портабельных Perl-скриптов. Интересно, даст ли обфускация CPAN-модулей ощутимое улучшение коэффициента сжатия?
PPS. И кстати, с днем программиста!
Метки: GUI, Perl, Кроссплатформенность.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.