Let's start with an easy example, the kind you'll want to do on your own: create a C source file,
test1.c:
#include
#include
main()
{
struct timeval timevalue;
struct timezone timezone;
gettimeofday(&timevalue, &timezone);
printf("The current time is %s",
ctime((time_t *) &timevalue.tv_sec));
}
and make an executable program from it. Under Unix, you'd generally do that with a command-line invocation:
cc -o test1 test1.c
Now create the Perl/Tk source file,
test1.pl:
use Tk;
$mw = MainWindow->new;
$label = $mw->Label();
$label->pack;
$mw->Button(-text => "Show the time",
-command => sub{
$label->configure(-text => `./test1`)
})->pack;
MainLoop;
There! If you execute:
perl test1.pl
you'll see a small GUI panel with a button. Pushing the button launches the C-coded program, retrieves output, and displays it. The problem's solved, for both Unix and Windows.
>
There's more to the story
For many programmers, though, that is only the beginning. Let's look at the example in more detail.
To run the example, we launch a Perl program, which itself controls the legacy test1 as a separate process. With all the examples in this article, it doesn't matter that test1 is compiled from C source; it could be a Fortran, C++, Java, or even another Perl program. All that matters in that architecture is that it can be launched as a command-line application, which puts its results to standard output.
Most modern languages available for Unix and Win* have at least one way of invoking an external process and retrieving its result in that way. The gluing languages we discuss in this column are particularly rich in those facilities. Most accessible to beginners are the backtick operator or function used in test1.pl above, which is also available in much the same form in Ruby, Tcl, Bourne shell, and several other languages.
A simple control panel of that sort is a great way to wrap a legacy application with complex command-line arguments or inputs. A high-level scripting language validates all the flags, ensures that they're properly consistent, then invokes the legacy application correctly. That is particularly valuable because it means that the legacy application need not change. There's no need to relink it, port it, or do anything else that might introduce delays or errors. Scripting languages just translate between a more modern GUI appearance and the older command-line interface.
Both the legacy application and the scripting wrapper can be in nearly any language. The final element of this solution's universality is the choice of GUI toolkit. Most languages bind to at least a couple of GUI toolkits, and deciding between them is independent of process invocation -- it's a free choice. The architecture works the same whether you're using Perl with Tk to run a C application or Python with Qt to control a Pascal process.
Predictable sequence
Questioners are happy to see for themselves that test1.pl and its equivalents in other languages are easy to use. They usually come back the next day, though, complaining, "The GUI locks up until the subprocess returns! I can't have that; I need for it to stay responsive, so my users can do other things. In fact, it ought to show a progress bar while the subprocess is running, and..."
Most scripting languages have a good answer to that, too, and we'll look at an example in a moment. First, though, let's clarify a couple of new concepts.
When we want the GUI wrapper to be alive at the same time the legacy application is grinding through to a result, we're asking for concurrency or multitasking. Modern operating systems know about concurrency; in fact, many support more than one programming construct for concurrency.
Java and Microsoft development kits emphasize threading for multitasked operations. More suitable for the high-level wrapping of this column, though, is event-based programming. Last year, we wrote a couple of times about how different scripting languages support threads and events. Now let's look at an example that uses events to keep a GUI responsive, while managing a long-lasting subprocess.
Create a small, long-running command-line application such as:
long.tcl:
for {set i 0} {$i < 10} {incr i} {
puts $i
flush stdout
# This says, "pause 1000 milliseconds".
after 1000
}
Wrap it with
test2.tcl:
proc launch {} {
global fp
set subprocess "tclsh long.tcl"
set fp [open "|open $subprocess"]
fileevent $fp readable read_one_line
}
proc read_one_line {} {
global fp
if [eof $fp] {
close $fp
.t insert end Done
return
}
.t insert end [gets $fp]\n
}
pack [button .b -text "Push me" -command launch]
pack [text .t]
Notice that when you run that, the textbox methodically fills up as it counts from zero to nine, and the GUI remains responsive. If we'd done that with the commands of test1, the GUI would have frozen for 10 seconds, then shown all of its results at once.
That is the second major architecture for combining a legacy application with a GUI or other scripting wrapper. Again, the high-level language is in charge, and it runs the legacy application as a subprocess. The legacy application needs no changes and can be written in any language, plus you're free to choose any GUI toolkit you find convenient.
What makes that architecture different is its finer control over the subprocess. Perl and Tcl batch subprocesses with the backtick operator and the exec command, respectively. For concurrent operations, both languages use open. Our second example is often called opening a pipe to a subprocess (the | sign in the test2.tcl is called a pipe). Many languages can also open pipes.
Next month, we'll look at variations of the second architecture and introduce the third and final major architecture for combining legacy applications with scripted wrappers.
In the news
Important news about scripting languages breaks faster than we can write about it. Our particular congratulations, though, go to the Lua team for its recent release of version 4.0. This update is faster, improves error handling, is fully re-entrant, and includes several other important advances.