Perl API

The Perl API for reading and writing tpl is nearly identical to the C API. This document will briefly explain the Perl API and provide examples. The chief motivation for having a Perl API is to communicate with C programs that use tpl.

Tip
Start with the C API
This document assumes familiarity with the C API. The concepts of using tpl are not explained here. For an introduction to tpl and its C API, see the User Guide.

Tpl.pm

The Tpl.pm file (in the lang/perl) directory contains the Perl module. You can copy it to another directory if you wish. Your Perl program may need to include a use lib statement to find the module.

#!/usr/bin/perl
use lib "/some/directory";
use Tpl;

tpl_map

This function resembles the C version, except that it’s invoked via the Tpl module, and it takes references to Perl variables after the format string.

my $i;
my $tpl = Tpl->tpl_map("A(i)",\$i);

The return value is a tpl object; all other API calls are object methods. Incidentally, there is no tpl_free() method corresponding to the C API.

Fixed-length arrays

Format strings such as i# denote a fixed-length array. In the Perl API, fixed-length arrays require two arguments: a list reference, and the fixed length. For example:

my @x;
my $tpl = Tpl->tpl_map("i#", \@x, 10);

When fixed-length arrays are packed or unpacked, the specified number of elements will be copied from (or placed into) the designated list.

Structures

Format strings containing S(...) are handled in the Perl API as if only the interior, parenthesized part was present. (It does not work like the C API). So simply ignore the S(...) and consider only its interior format characters when constructing the argument list:

my ($str, $int);
my $tpl = Tpl->tpl_map("S(si)", \$str, \$int);

It really only makes sense to use S(...) in a format string in the Perl API if you are communicating with a C program that uses structures.

tpl_pack

This is nearly identical to the C version. The only argument is the index number to pack.

$tpl->tpl_pack(1);

tpl_dump

This method is a little different than the C version. Given no arguments, it returns the tpl image; given one argument it writes a file with that name.

$tpl->tpl_dump("demo.tpl");   # writes demo.tpl

Or,

my $img = $tpl->tpl_dump();

The tpl image is a binary buffer. You can do whatever you want with it, such as write it to a socket or pipe (probably to C program listening on the other end), or save it somewhere and later re-load it using tpl_load().

tpl_load

This method loads a tpl image from a file or from a Perl variable. It takes one argument. If it’s not a reference, it’s assumed to be a filename to load.

$tpl->tpl_load("demo.tpl");

Otherwise, if the argument is a Perl reference, it’s construed as a variable containing the tpl image:

$tpl->tpl_load(\$img);

The method will die if the image is invalid or the file doesn’t exist. You can wrap it with eval to catch such errors:

eval { $tpl->tpl_load(\$img); };
print "failed to load\n" if $@;

tpl_unpack

This is nearly identical to the C version. The only argument is the index number to unpack.

$tpl->tpl_unpack(1);

Examples

Integer array

Packing A(i) to file
#!/usr/bin/perl

use strict;
use warnings;

use Tpl;

my $i;
my $tpl = Tpl->tpl_map("A(i)",\$i);
for($i=0; $i<10; $i++) {
    $tpl->tpl_pack(1);
}
$tpl->tpl_dump("demo.tpl");
Unpacking A(i) from file
#!/usr/bin/perl

use strict;
use warnings;

use Tpl;

my $j;
my $tpl2 = Tpl->tpl_map("A(i)",\$j);
$tpl2->tpl_load("demo.tpl");
while($tpl2->tpl_unpack(1) > 0) {
    print "$j\n";
}

Message-passing

While the bulk of this example is socket handling, it demonstrates how you can use tpl as a message-passing format. In the real-world, you might have a C server and a Perl client, for example. In this example, we’ll code both a client and a server in Perl.

A server that sums integers

Programming literature is rife with contrived examples so we will follow in that tradition. Our server will do no more than sum a list of integers. But in doing so it will demonstrate message passing adequately. Both its input (the integer array) and its output (an integer) are tpl images, passed over a TCP/IP socket.

Server

The server waits for a connection from a client. When it gets one, it accepts the connection and immediately forks a child process to handle it. Then it goes back to waiting for another new connection.

The server child process handles the client by loading and unpacking the tpl image sent by the client (containing an array of integers). It calculates their sum and constructs a new tpl image containing the sum, which it sends back to the client.

Server
#!/usr/bin/perl

use strict;
use warnings;

use IO::Socket::INET;
use Tpl;

$SIG{CHLD} = "IGNORE"; # don't create zombies

our $port = 2000;

sub handle_client {
    my $client = shift;

    undef $/;
    my $request = <$client>; # get request (slurp)

    # read input array, and calculate total
    my ($i,$total);
    my $tpl = Tpl->tpl_map("A(i)", \$i);
    eval { $tpl->tpl_load(\$request); };
    die "received invalid tpl" if $@;
    $total += $i while $tpl->tpl_unpack(1) > 0;

    # formulate response and send
    my $tpl2 = Tpl->tpl_map("i", \$total);
    $tpl2->tpl_pack(0);
    my $response = $tpl2->tpl_dump();
    print $client $response;
    close $client;
}

my $server = IO::Socket::INET->new(LocalPort => $port,
                   Type => SOCK_STREAM,
                   Reuse => 1,
                   Listen => 10 )
        or die "Can't listen on port $port: $!\n";

while (1) {
    my $client = $server->accept();
    next unless $client;
    # new connection
    my $pid = fork;
    die "can't fork: $!\n" unless defined $pid;
    if ($pid > 0) {
        # parent
        close $client;
    } elsif ($pid == 0) {
        # child
        handle_client($client);
        exit(0);
    }
}
close ($server);

Client

The client is a simpler program. It constructs the tpl image containing the integer array (taken from its command-line arguments), connects to the server and sends the tpl image to it, and then awaits the response tpl. The response containing the sum is loaded, unpacked and printed.

Client
#!/usr/bin/perl

use strict;
use warnings;

use IO::Socket::INET;
use Tpl;

our $port = 2000;

# construct tpl
my $i;
my $tpl = Tpl->tpl_map("A(i)",\$i);
$tpl->tpl_pack(1) while ($i=shift @ARGV);
my $request = $tpl->tpl_dump();

# send to server, get response
my $socket = IO::Socket::INET->new("localhost:$port") or die "can't connect";
print $socket $request;
shutdown($socket,1);        # done writing (half-close)
undef $/;
my $response = <$socket>;       # get reply (slurp)

# decode response (or print error)
my $total;
my $tpl2 = Tpl->tpl_map("i", \$total);
eval { $tpl2->tpl_load(\$response); };
die "invalid response\n" if $@;
$tpl2->tpl_unpack(0);
print "total is $total\n";

Running thise example

If the client and server programs are in client.pl and server.pl, then you can run the example by starting the server in one window:

./server.pl

Then run the client in another window. E.g.,

./client.pl 1 2 3 4 5

The client runs and then exits, printing:

total is 15

You can re-run the client with different arguments. When done, type Ctrl-C in the server window to terminate it.