Fresco
Dish: A Dynamic Invocation Shell for Fresco

Douglas Pan, Mark Linton

Abstract
Introduction
Background
Using Dish
Implementation
Conclusions
Bibliography
Authors

Abstract

Dish is a command interpreter that allows the user to create and manipulate Fresco objects interactively or with a script. Dish uses the Tcl[3] language and interpreter for the basic script control flow and object naming. Because Fresco operations are specified in the CORBA Interface Definition Language (IDL), Dish can automatically translate a Tcl command line into the appropriate call using the CORBA dynamic invocation mechanism. Unlike Tk[4], Dish requires no special registration of commands or explicit argument parsing, and commands in Dish scripts can easily be moved into compiled code.

Introduction

Scripting is a popular form of implementing portions of an interactive application. A command interpreter can either read commands interactively or from a script, and is generally more responsive and easier to use than a compiled environment. Furthermore, scripting languages tend to be simpler than traditional programming languages and therefore appeal to users who do not wish to learn all the details associated with a language such as C or C++.

Scripts are not typically self-contained; some statements may call functions implemented in a traditional programming language for greater efficiency and robustness. Unfortunately, integrating scripts and program functions is often awkward. The program code typically must register the functions to be available from the scripting interface, check the types of parameters, and convert parameters from the scripting representation (typically strings) to the program form (binary).

Fresco is an API for graphical user interfaces in which all operations are specified using the CORBA Interface Definition Language (IDL). To make a scripting interface available automatically, we leverage the dynamic invocation mechanism defined by CORBA. This mechanism allows one to create a request object at run-time and fill in the operation name and parameter list. CORBA also defines a type repository so that an interpreter can check the types of parameters and perform conversions automatically, instead of requiring that each operation do so.

To test the validity of our approach, we have implemented a script interface to Fresco called Dish (Dynamic Invocation SHell) using the Tcl language and interpreter. Dish is a relatively small application (about 1,000 lines of C++ code) that uses dynamic invocation to evaluate commands that create and manipulate Fresco objects.

Background

Fresco covers a broad range of functionality-buttons, menus, rectangles, white space, text editors, and movie players all can be Fresco objects. For the purposes of Dish, what matters most is that Fresco is specified by IDL interfaces. Connecting Dish to other IDL interfaces would require very minor changes. We therefore do not present here an overview of Fresco. The overall goals and the motivation for using CORBA are described in [2].

Tcl (Tool Command Language) is an interpretative language with strings as the common data type. Tcl commands are generally of the form "command arg1 arg2 ..." where the command and arguments are (or evaluate to) strings. Tcl also provides variables, procedures, and allows recursive evaluation of procedures or commands.

The common usage of the Tcl interpreter is as a library bound into a program. Application-specific commands can be bound to program functions, allowing the user to write scripts that access the application functions.

Tk is a set of widgets that define Tcl commands for the creation, customization, and manipulation of user interface objects. Tk widgets and applications must manually register the Tcl commands, check the parameter types for each command, and perform conversion on the parameter strings to the appropriate program binary form. The Tk button command, for example, must process about 20 argument options.

A second problem with the manual approach used by Tk is that there is no guaranteed correspondence between a command available from a Tcl script and a function available from a programming language. Thus, one may not easily be able to rewrite portions of a script in C or C++ to improve performance.

In contrast, Dish is able to provide the desirable features of Tcl without the problems associated with manual registration and command parsing. The combination of IDL and dynamic invocation gives Dish automatic access to all the Fresco operations, and any operation accessible from Dish is also accessible directly from a programming language using a function call.

Using Dish

When a user starts Dish interactively, a "%" prompt will appear, and the user can start entering Dish commands. A newline normally terminates a command. To end a Dish session, one uses the "exit" command.

Any Fresco object may be used as a Dish command. An object reference may be bound to a Tcl variable by using the Tcl"set" command. This approach allows commands in the following format:

    ObjectRef operation argument-list
ObjectRef refers to the target object, which is represented as a string (normally a mapping of the address in ASCII), and operation is the name of the operation to invoke as defined in IDL. Argument-list is zero or more arguments separated by spaces. Dish currently requires the argument list to match the interface specification both in number and position (one could imagine using the parameter names rather than position).

For example, consider the following partial interface description for the Fresco Glyph interface:

    interface Glyph : FrescoObject {
        attribute StyleObj style;
	    void append(in Glyph g);
        void prepend(in Glyph g);
        void need_redraw();
    }
Now suppose that g1 and g2 have been bound to Fresco glyph objects. The following Dish commands are possible:
    g1 append g2
    g1 prepend g2
    g1 need_redraw
    g2 need_redraw
    g1 _set_style [ g2 _get_style ]

The brackets ("[]") denote a nested command. To avoid potential ambiguities, IDL attributes have 
separate commands for assigning and retrieving a value.


Predefined commands and variables

Fresco defines a set of top-level objects, called "kits," that provide operations for creating other objects. For example, the drawing kit has operations for creating fonts, colors, and images; the figure kit has operations for creating circles, polygons, and labels; and the widget kit has operations for creating push buttons, menus, and scroll bars.

Dish defines these top-level objects as variables. For example, to create a circle one could give the command

    figure_kit circle 0 [figure_kit default_style] 0 0 100
The arguments to the circle operation are the figure mode (where 0 means filled), the figure style (in this example we retrieve the default style), the origin, and the radius. Similar commands are possible to create other kinds of objects.

Dish defines one more important command, "main," that corresponds to the main operation defined by the top-level Fresco interface. This operation takes two arguments, a viewer and a glyph, that define the input and output behavior of a window, respectively. Either of these arguments may be zero (but not both). If the viewer is zero, then the window is not interested in events. If the glyph is zero, then the viewer defines both input and output.

The Dish implementation of the main command spawns a new thread, opens a new display connection, maps the window, and executes a Fresco event loop for the display. This approach means that several scripts may be started and controlled interactively.

For example, here is the Dish script to create a circle, put it in a window, and map the window to the default display:

    main 0 [figure_kit circle 0 [figure_kit default_style] 0 0 100]
The current implementation does not create a new Tcl interpreter, though it probably should to avoid concurrency problems. However, even if a new interpreter is created, object references will still refer to addresses in shared memory, so the user can interactively send commands to objects that are on the screen.

Strings

The Fresco interface defines strings as objects and requires the programmer to convert C++ string literals to string objects explicitly. Rather than force a Dish user to perform the same operations, Dish implicitly converts Tcl strings to Fresco strings. For example, the following C++ code implements a window containing the label "hello world":
    FigureKit* figure_kit = fresco->figure_kit();
    fresco->main(
        nil, figure_kit->label(figure_kit->default_style(), Fresco::string_ref("hello world"))
    );
The equivalent Dish program is as follows:
    main 0 [
        figure_kit label [	figure_kit default_style	] "hello world"
    ]
Note that in the Dish version the string "hello world" is used directly as an argument to the label operation. The Dish implementation automatically creates the string object to be passed.

Aggregates

Aggregates such as sequences and structs are specified in Dish as Tcl lists. Each aggregate is surrounded by a pair of braces. Aggregates may be nested and can be assigned to Dish variables. For example, suppose we want to scale a glyph g1 by a factor of 0.5, then we can use the following code:
    set t [g1 transformation]
    if { $t != 0 } {
    	t scale "{0.5 0.5 0.5}"
    }
The double quotes are necessary to indicate that the aggregate should be treated as a single argument. The use of an open brace at the end of a line as a line continuation symbol.

Out parameters

Certain Fresco operations call for out parameters or inout parameters. Dish expects the name of a Dish variable as the argument for these cases. When an argument is passed to an out parameter, the name in the argument does not have to correspond to an existing Tcl variable. Dish will create a variable and assign the out parameter value to the variable upon return from the operation. For example, to print the requisition of a glyph g1, which describes the desired geometry for the glyph, one can use the code:
    g1 request r
    puts $r
Note that the Dish variable `r' does not have to exist before using it as an out parameter. In the case of an inout parameter, the variable used as the argument must already exist. Dish evaluates the variable just like any other Dish variable and passes the value as the actual argument. Upon return from the operation, the variable is updated with the return value corresponding to the inout parameter. For example, to transform a vector by the transformation object t1, we can use the code:
    set vector "{1 1 1}"
    t1 transform vector
    puts $vector


This example also illustrates the duality of Dish variables. In a Dish coomand (g1) they are 
evaluated by default, while evaluation is not automatic in a Tcl command (puts).

Callbacks

The Dish action command allows users to specify callbacks. The syntax for the action command is,
    action command
where command is any valid Dish command. The action command returns a Fresco action object, which can be used any place in the interface that accepts an action. For example, the following Dish program creates a push button with the label "Push me" that prints "pushed" whenever the button is clicked:
    proc push_me {message} {
        puts $message
        flush stdout
    }
    set button [
        widget_kit push_button [
            figure_kit label [figure_kit default_style]		 "push me"
        ] [
            action push_me "pushed"
        ]
    ]
    main button [layout_kit margin button 10.0]

Implementation

Most of the work implementing Dish has really been in the Fresco request interface that supports dynamic invocation and the Fresco interface translator that generates run-time type information. The design of the manipulation of object references in Tcl also has some associated subtle issues.

Request interface

A request object may be created for any Fresco object. The interface to a request object allows one to set the operation to a string, pass arguments to the operation, and finally try to invoke the request. Dish is responsible for the string-to-binary conversion of the arguments, which are then passed to the request object in binary form. This is especially desirable for aggregates, which may have different representations in different scripting languages. It is therefore appropriate to place the language-dependent implementation in the Dish implementation rather than in the Fresco library. An error code is returned if the invocation fails. The result of the operation is also returned in birary form and Dish performs the conversion back to strings. Dish must also use the op_info operation to determine whether there are any out parameters and if the operation returns a value or not. The RequestObj interface is similar to the Request interface defined by CORBA and its IDL definition is as follows:
    interface RequestObj {
        enum CallStatus {
            initial, ok, unknown_operation,
            bad_argument_count, bad_argument_type
        };
        void set_operation(in string s);
        CallStatus invoke();	
        CallStatus op_info(out TypeObj::OpInfo op);
        void put_char(in char value);
        char get_char();

        // Elided operations for putting and getting other basic data types

        void put_object(in BaseObject obj);
        BaseObject get_object();
        void begin_aggregate();
        void end_aggregate();	
    };
There is a binary interface for putting and getting each IDL basic data type. We only show these related to Char and BaseObject; others are elided. When passing an aggregate argument, it must be bracketed by a begin_aggregate/end_aggregate pair. Aggregates can be nested.

Run-time type information

The TypeObj interface describes type information pertaining to each Fresco object. TypeObj provides the name of the type, whether or not it is an interface (only interface objects are entered into the Dish object table), and details on each operation. Operation information include its name, the return type, and a list of parameters. Parameter information include its name, mode, and type. The TypeObj interface is as follows:
    interface TypeObj {
        enum ParamMode { param_in, param_out, param_inout };
        struct ParamInfo {
            string name;
            ParamMode mode;
            TypeObj type;
        };
        struct OpInfo {
            string name;
            long index;
            TypeObj result;
            sequence params;
        };
        string name();
        boolean is_interface();
        boolean op_info(	out TypeObj::OpInfo op, in long index	);
    };

Interface translator

The type information provided by TypeObj is generated by Ix, the Fresco interface translator. Ix generates C++ classes corresponding to the interface definitions and also can edit C++ source files to assist with the coding of implementation classes. The type information is generated as part of the filtering process.

The filtering process takes one or more interfaces and modifies a C++ implementation file according to annotations embedded in the file. Some of these annotations instruct Ix to generate type information from the interfaces and deposit that information in the file. The type information is generated in the form of static data structures connected via pointers.

Extending Tcl

The Dish extension to the Tcl interpreter can be divided into two parts: one that adds predefined Dish variables and commands and a second part that modifies the handling of unknown commands to perform dispatching on Fresco objects. The predefined Dish variables and commands are straightforward to define as Tcl extensions.

The second part is more complicated. Our model called for all object references returned from a Dish command to be potential Dish commands themselves. A straight-forward implementation would define a new Dish command for every object reference known by Dish. This is not only expensive, is it also wasteful, since most object references are not used as commands. Our solution is to not generate Dish commands for object references at all. Instead, we redefined the mechanism for handling unknown commands in Tcl to perform a lookup in our object table that contained all object references. If a match is found, then we perform a dynamic invocation on the object. Otherwise, Dish uses the standard Tcl unknown command handling mechanism.

Conclusions

Dish is a command interpreter based on Tcl that allows users to create and manipulate Fresco objects. Any object reference can form a Dish command for executing an operation. Dish can have a variety of uses, including as an interactive shell, a rapid-prototyping environment, and a testing tool.

The advantage of our approach to scripting is that the connection between the scripting language and the operations implemented in C++ is automatic. New operations that are added to Fresco are immediately accessible to Dish without any effort (other than re-running the interface translator). In addition, scripting code can easily be evolved into C or C++ code because the same operations are also available from a programming language.

Bibliography

  1. P. Calder and M. Linton. Glyphs: Flyweight Objects for User Interfaces. Proceedings of the ACM SIGGRAPH Third Annual Symposium on User Interface Software and Technology, 1990.
  2. M. Linton and C. Price. Building Distributed User Interfaces with Fresco. Proceedings of the Seventh X Technical Conference, Boston, Massachusetts, January 1993, pp. 77-87.
  3. J. Ousterhout. Tcl: an Embeddable Command Language. Proceedings of the 1990 Winter USENIX Technical Conference.
  4. J. Ousterhout. An X11 Toolkit Based on the Tcl Language. Proceedings of the 1991 Winter USENIX Technical Conference.

Authors

  • Douglas Pan is a Ph.D. student at Stanford University and a consultant for Fujitsu, Ltd.
  • Mark Linton is a Principal Scientist at Silicon Graphics.