tpl Perl API ============ Troy D. Hanson v1.1, April 2007 include::sflogo.txt[] include::topnav.txt[] Perl API -------- include::toc.txt[] 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: