wxperl/

Одна из проблем интерпретируемых языков заключается в зависимости скриптов от наличия у пользователя соответствующего интерпретатора и модулей, используемых в коде скрипта. В мире 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 и говорим:

citrusperl-msw-x86-5-12-r6 -d d:coding-stuff

Здесь d:coding-stuff — это директория, куда вы хотите поставить Citrus Perl. После распаковки всех файлов говорим:

d:coding-stuffCitrusPerlx865-12bincitrusreloc

Затем идем в «Компьютер → Дополнительные параметры системы → Переменные среды» и дописываем «d:coding-stuffCitrusPerlx865-12bin» в переменную окружения PATH. Согласен, нормальный инсталятор не помешал бы, но мы с вами программисты или где? Наконец, запускаем новый экземпляр cmd.exe и проверяем, все ли работает:

perl -v
d:coding-stuffCitrusPerlx865-12binwxperl_demo.bat

Лично я также скачал Padre и библиотеку GD:

cpan -i Padre
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 .

Пример программы на wxPerl

Код скрипта:

#!/usr/bin/perl -w —

# ida2code.pl v 0.1
# (c) Alexandr A Alexeev 2011 | http://remontka.com/

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://remontka.com/» ) ;
$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:[^rn]+##sgi;
$text =~ s #; ——[^rn]+##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 тоже выглядит неплохо, но до нее у меня руки не дошли.

cpan -i PAR::Packer
pp -o test.exe test.pl

С обычными консольными программами он справляется на отлично, но при работе со скриптами, использующими wxPerl, нуждается в небольшой поддержке:

cpan -i Wx::Perl::Packager

Согласно документации к Wx::Perl::Packager , от нас требуется просто прописать в нужном месте

use Wx :: Perl :: Packager ;

… после чего команда:

wxpar -o ida2code.exe ida2code.pl

… магическим образом создаст exe-шник. К сожалению, на практике все оказалось не так просто. У меня программа, созданная с помощью wxpar, выдавала такую ошибку:

Can’t locate Wx/DND.pm in @INC (@INC contains: …

Решение проблемы удалось нагуглить . Оказывается, достаточно просто заменить в скрипте строчку:

use Wx 0.15 qw [ : allclasses ] ;

… на:

use Wx ;

И действительно, exe’шник, созданный из пропатченного скрипта, уже не выводит сообщений об ошибках. Он ничего не выводит. Вообще. В смысле — даже окон.

Поковыряв exe’шник и немного погуглив, я так и не нашел решения проблемы. Как я понял, она заключается в том, что упакованный скрипт не может найти модуль Win32.pm, хотя он присутствует в пакете. Ну, раз проблему не удается решить , попробуем ее обойти .

Оказывается, полученный exe’шник представляет собой самораспаковывающийся архив и его можно открыть в любом современном архиваторе. Распаковываем содержимое архива в отдельную директорию. Динамические библиотеки из подкаталога shlib кладем в «корень». Туда же копируем perl.exe, perl512.dll и libgcc_s_sjlj-1.dll из CitrusPerlx865-12bin. Открываем файл script/ida2code.pl и удаляем из него строчку:

use Wx :: Perl :: Packager ;

Затем создаем run.bat следующего содержания:

perl script/ida2code.pl

Запускаем и… оно работает! Несложно проверить, что теперь скрипт будет работать с флешки даже на компьютере без установленного Perl. Вообще-то мы убили сразу двух зайцев — скрипт теперь не только работает, но и запускается моментально, а не в течение нескольких секунд.

Для удобства пользователя вместо bat-файла можно положить специальную небольшую программку с красивой иконкой или сделать инсталятор . Что касается размера получившегося приложения, в zip-архиве оно весит 9 Мб, а в 7z — 5 Мб, что по нынешним меркам не так уж много. Я использовал параметры сжатия по умолчанию, на случай, если на компьютере пользователя мало оперативной памяти. Следует учесть, что мы можем распространять приложение в двух вариантах — «все в одном» и «поставь Perl и нужные CPAN модули сам».

***

PS. Стоит попробовать написать архиватор, заточенный для сжатия портабельных Perl-скриптов. Интересно, даст ли обфускация CPAN-модулей ощутимое улучшение коэффициента сжатия?

PPS. И кстати, с днем программиста!

EnglishRussianUkrainian