mirror of
https://github.com/troydhanson/tpl.git
synced 2024-12-27 16:11:02 +08:00
299 lines
8.3 KiB
Plaintext
299 lines
8.3 KiB
Plaintext
tpl Perl API
|
|
============
|
|
Troy D. Hanson <tdh@tkhanson.net>
|
|
v1.1, April 2007
|
|
|
|
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
|
|
link:userguide.html[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.
|
|
|
|
// vim: set tw=80 wm=2 syntax=asciidoc:
|
|
|