Channels ▼
RSS

Web Development

Creating Self-Contained Perl Executables, Part I


Julius is a systems administrator for Ayala Systems Technology. He can be contacted at jcduque@lycos.com.


Being a Perl programmer, one of my biggest frustrations with using Windows is the absence of a Perl interpreter. On many occasions, I had to install ActivePerl (http://www .activestate.com/) just so I could use my Perl scripts. A number of times, I have asked myself, "Wouldn't it be nice to have a Perl script compiler to convert my scripts into self-contained executables so that I won't have to worry about a Perl interpreter?" Fortunately for me, I discovered LibZip.

This month, I'll discuss LibZip. Next month, I'll follow up with a discussion of PAR, the Perl Archive Toolkit, which is another way to create Perl executables. Obviously, you'll need a working C compiler to convert your Perl scripts into self-contained executables, as discussed here.

LibZip

LibZip is the brainchild of GracilianoMonteiroPassos and is available from http://search.cpan.org/CPAN/authors/id/G/GM/ GMPASSOS. In Graciliano's own words, the goal of LibZip is to "create very low weight executables" out of Perl scripts. To achieve this, LibZip bundles all modules needed by a Perl script into one big file, called lib.zip, and creates a native code equivalent of the script. When the native code is run, it uncompresses lib.zip into a temporary folder and uses the files in this folder, thereby eliminating the need for a Perl interpreter. The downside, of course, is that the native code won't run without lib.zip.

Using LibZip

LibZip needs three other modules before it can be useful: Pod::Stripper, Compress::Zlib, and Archive::Zip, all available from CPAN. Assuming you have successfully installed LibZip, using it is a piece of cake. To use LibZip, follow these simple steps. Let's convert dos2unix.pl (Listing 1) into a Windows native executable, dos2unix.exe:

  1. Scan all modules that dos2unix.pl needs, including dependencies:
  2. perl -MLibZip::Scan dos2unix.pl
    

  3. This will build libzip.modules. LibZip will use this file to create the EXE file in the next step.
  4. Build the library, lib.zip, and executable, dos2unix.exe:
libzip -o dos2unix.pl -lzw -striplib

The -o option tells the batch file, libzip, to create an EXE file. Note also that the name of the Perl script must immediately follow the option -o. The -lzw option tells libzip to use LZW compression. An interesting benefit of compression, albeit a trivial one, is that you can now obfuscate your code with it! -striplib tells libzip to remove PODs from lib.zip.

That's it! One more thing, though. After the last step, LibZip will import perl56.dll and perl58.dll from your Perl interpreter's installation folder into the folder where you created lib.zip and dos2unix.exe. So, go ahead, run your shiny new executable! Rename your Perl installation folder to something else and see if dos2unix.exe will run without a Perl interpreter.

A Word Of Warning

I usually use diagnostics in my scripts to make debugging easier. But, for some reason, I can't get a functioning executable (both in Windows and Linux) if I use diagnostics. Not using diagnostics solves the problem.

Compiling Perl/Tk Scripts

Compiling console Perl scripts are straightforward, but compiling Perl/Tk scripts is a bit problematic. If you compile perl-tk.pl (Listing 2), for example:

perl -MLibZip::Scan perl-tk.pl

libzip -o perl-tk.pl -lzw -striplib

the generated executable won't run. Apparently, Tk performs some magic that LibZip fails to see. Specifically, utf8_heavy.pl and the whole unicore directory were not included in libzip.modules during the scan. So when lib.zip was finally built, some files were missing. The fix, therefore, is to edit libzip.modules manually and add utf8_heavy.pl and unicore. Recompiling perl-tk.pl should now be a breeze.

You may also use the -gui option, aside from -lzw and -striplib, to create a nonconsole executable.

libzip -o perl-tk.pl -lzw -striplib -gui

You are encouraged to use -gui if you're compiling a Perl/Tk script.

UPX

LibZip can also use what is known as UPX, short for "Ultimate Packer for executables" to compress lib.zip and executables even more. UPX's inventors, Markus F.X.J. Oberhumer and László Molnár, claim that UPX is a "free, portable, extendable, high-performance executable packer for several different executable formats. It achieves an excellent compression ratio and offers very fast decompression." http://upx.sourceforge.net/ is UPX's official web site where you'll find versions of UPX for different platforms.

Using UPX within LibZip is also painless:

libzip -o perl-tk.pl -lzw -striplib -gui -upx best  				       -upxlib best

Option -upx will compress the executable, while -upxlib will compress lib.zip. The best argument to -upx and -upxlib tells libzip to call UPX using the best possible compression.

Because UPX is also a standalone program, you may also use it independently from LibZip. For example, you may type Step 2 as:

libzip -o perl-tk.pl -lzw -striplib -gui

and then follow it up with:

upx —best perl-tk.exe perl56.dll perl58.dll

perlcc—Perl's Own Compiler

How about Perl's own compiler, perlcc? Based from my personal experience, I have yet to produce a working executable that is compiled with perlcc. Indeed, the Perl documentation says that perlcc's output is not guaranteed to work. The man page further says "the whole codegen suite (perlcc included) should be considered very experimental. Use for production purposes is strongly discouraged." Aside from that, perlcc takes several minutes to compile even a simple script.

Compiling Scripts for Linux

You may ask, "Can LibZip also create executables for Linux?" The answer is a resounding "Yes." The steps are the same, but without perl56.dll and perl58.dll being generated (for the obvious reason).

PAR—Another Executable Maker

What if you dislike having to bundle your executables with a separate library and prefer, instead, to have just a single executable? The LibZip man page mentions a similar tool, PAR, or the Perl Archive Toolkit, from Autrijus Tang. I'll discuss PAR next month.

TPJ



Listing 1

#!/usr/local/bin/perl

# Julius C. Duque

use strict;
use warnings;
use Getopt::Long;

my ($format, $help) = ();

GetOptions(
    "format=s"  => \$format,   # =s -> takes mandatory string argument
    "help"      => \$help      # optional switch
);

if (!$format or $help) {
    if ($^O eq "MSWin32") {
        $0 =~ s/.*\\//g;    # Windows
    } else {
        $0 =~ s/.*\///g;
    }

    print "Usage: $0 --format=unix file1 [file2] [file3] ...\n";
    print "       $0 -f=unix file1 [file2] [file3] ...\n";
    print "\n";
    print "       $0 --format=dos file1 [file2] [file3] ...\n";
    print "       $0 -f=dos file1 [file2] [file3] ...\n";
    print "\nOriginal file(s) will be overwritten\n";
    exit 1;
}

foreach my $infile (@ARGV) {
    print "Converting $infile to $format format...\n";
    open INFILE, $infile;
    open OUTFILE, ">temp.$infile";
    binmode INFILE;
    binmode OUTFILE;
    while (<INFILE>) {
        $_ =~ s/\015\012/\012/g if ($format =~ /u/);
        $_ =~ s/\012/\015\012/g if ($format =~ /d/);
        print OUTFILE;
    }

    close INFILE;
    close OUTFILE;
    rename "temp.$infile", "$infile";
}
Back to article


Listing 2
#!/usr/local/bin/perl

use strict;
use warnings;
use Tk;
use Tk::Balloon;

my $INDENT_DEF = 0;
my $LWIDTH_DEF = 70;
my $VERSION = "1.4.3";
my $TITLE = "Perl/Tk Example $VERSION";
my $AUTHOR = "Julius C. Duque";
my $indent = $INDENT_DEF;
my $newline = 1;
my $hyphenate = 1;
my $width = $LWIDTH_DEF;
my ($BOTH, $LEFT, $RIGHT, $CENTERED) = (1, 2, 3, 4);
my $format_choice = $BOTH;
my ($infile, $outfile) = ();

my $mw = new MainWindow();
drawButtons();
Tk::MainLoop();

sub processfile
{
  open INFILE, $infile;
  open OUTFILE, "> $outfile";
  while (<INFILE>) {
    print OUTFILE;
  }

  close INFILE;
  close OUTFILE;

  printMessage("info", "OK", "File was successfully saved.");
}

sub drawButtons
{
  $mw->title($TITLE);

  # Status bar widget
  my $status = $mw->Label(-width => 70, -relief => "sunken",
    -anchor => "w")->pack(-side => "bottom", -padx => 1, -pady => 1,
    -fill => "x");

  # Create balloon widget
  my $b = $mw->Balloon(-statusbar => $status);

  # Create menu bar frame
  my $menubar = $mw->Frame(-borderwidth => 4, -relief => "ridge")->
    pack(-side => "top", -fill => "x"); 

  # Create Open File button
  my $openfilebutton = $menubar->
    Button(-text => "Open File", -relief => "raised", -width => 10,
      -command => [\&fileDialog, $mw, "open"])->pack(-side => "left");

  $b->attach($openfilebutton, -msg => "Open a file");

  # Create Save File button
  my $savefilebutton = $menubar->
    Button(-text => "Save To File", -relief => "raised", -width => 10,
      -command => sub {
          if (defined $infile and $infile ne "") {
            fileDialog($mw, "save");
          } else {
              printMessage("warning", "OK",
                "You must open a file first");
          }
        })->pack(-side => "left");

  $b->attach($savefilebutton,
    -msg => "Proceed with saving a file");

  # Create About button
  my $aboutbutton = $menubar->Button(-text => "About",
    -relief => "raised", -width => 10,
    -command => [\&printMessage, "info", "OK",
      "A Perl/Tk script created by $AUTHOR"])->pack(-side => "left");

  $b->attach($aboutbutton,
    -msg => "$TITLE created by $AUTHOR");

  # Create Quit button
  my $quitbutton = $menubar->Button(-text => "Dismiss",
    -relief => "raised", -width => 10, -command => sub { exit })->
    pack(-side => "right");

  $b->attach($quitbutton, -msg => "Quit this Perl/Tk script");

  my $both = $mw->Radiobutton(-variable => \$format_choice,
    -value => $BOTH, -text => "Radio Button 1")->
    pack(-side => "top", -anchor => "w");

  $b->attach($both, -msg => "Radio Button 1");

  my $left = $mw->Radiobutton(-variable => \$format_choice,
    -value => $LEFT, -text => "Radio Button 2")->pack(-side => "top",
    -anchor => "w");

  $b->attach($left, -msg => "Radio Button 2");

  my $right = $mw->Radiobutton(-variable => \$format_choice,
    -value => $RIGHT, -text => "Radio Button 3")->pack(-side => "top",
    -anchor => "w");

  $b->attach($right, -msg => "Radio Button 3");

  my $centered = $mw->Radiobutton(-variable => \$format_choice,
    -value => $CENTERED, -text => "Radio Button 4")->pack(-side => "top",
    -anchor => "w");

  $b->attach($centered, -msg => "Radio Button 4");

  $both->select;   # Set default to $both

  my $chknewline = $mw->Checkbutton(-variable => \$newline,
    -text => "Check Box 1")->
    pack(-side => "top", -anchor => "w");

  $b->attach($chknewline,
    -msg => "Check Box 2");

  $chknewline->select;   # Set default to $newline

  my $chkhyphen = $mw->Checkbutton(-variable => \$hyphenate,
    -text => "Check Box 2")->pack(-side => "top", -anchor => "w");

  $b->attach($chkhyphen, -msg => "Check Button 2");

  $chknewline->select;   # Set default to $hyphenate

  my $f = $mw->Frame->pack(-side => "left");

  my $l = $f->Label(-text => "Indention: ", -justify => "left");

  $b->attach($l, -msg => "Blah blah blah...");

  Tk::grid($l, -row => 0, -column => 0);

  my $tindent = $f->Entry(-width => 2, -textvariable => \$indent,
    -justify => "right");

  $b->attach($tindent,
    -msg => "Number of spaces at the start of every paragraph");

  Tk::grid($tindent, -row => 0, -column => 1);

  $l = $f->Label(-text => "characters (default: $INDENT_DEF) ",
    -justify => "left");

  $b->attach($l,
    -msg => "Number of spaces at the beginning of each paragraph");

  Tk::grid($l, -row => 0, -column => 2);

  $l = $f->Label(-text => "Line width: ", -justify => "left");
  Tk::grid($l, -row => 1, -column => 0);
  $b->attach($l, -msg => "Maximum length of every line");

  my $tlwidth = $f->Entry(-width => 2, -textvariable => \$width,
    -justify => "right");

  $b->attach($tlwidth, -msg => "Maximum length of every line");
  Tk::grid($tlwidth, -row => 1, -column => 1);

  $l = $f->Label(-text => "characters (default: $LWIDTH_DEF)",
    -justify => "left");

  $b->attach($l, -msg => "Maximum length of every line");
  Tk::grid($l, -row => 1, -column => 2);
}

sub printMessage
{
  my ($icon, $type, $outputmsg) = @_;
  my $msg = $mw->messageBox(-icon => $icon, -type => $type,
  -title => $TITLE, -message => $outputmsg);
}

sub fileDialog {
  my ($w, $operation) = @_;
  my @types = (["Text files", [qw/.txt .doc/]],
    ["Text files", "", "TEXT"],
    ["All files", "*"]
  );

  if ($operation eq "open") {
    $infile = $w->getOpenFile(-filetypes => \@types);
  }

  if ($operation eq "save") {
      $outfile = $w->getSaveFile(-filetypes => \@types,
        -initialfile => "Untitled",
        -defaultextension => ".txt");

    processfile() if (defined $outfile and $outfile ne "");
  }
}
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 
Dr. Dobb's TV