Dylan's Creole Interface

Interfacing a little language to a big database

Edward Cessna

Ed, a senior software engineer with the Harlequin Group, can be contacted at eec@harlequin.com.


Dylan is an object-oriented dynamic language originally developed by Apple Computer, with input from Harlequin and Carnegie Mellon University. From the outset, the Dylan language design has been in the public domain. But because the term "Dylan" is trademarked, anyone implementing the language must obtain Apple's permission to use the name. Several organizations have announced commercial-quality Dylan releases, including Harlequin's forthcoming DylanWorks, a dynamic development environment and native compiler that produces fast, compact executables. DylanWorks will initially be released for Windows 95 and Windows NT, and provides full interoperability with OLE and Win32 functionality.

Likewise, Carnegie Mellon University is developing an integrated Dylan development environment for UNIX called "Gwydion." This is on the heels of "Mindy," a byte-code compiler for UNIX, Mac, OS/2, and Windows. Other experimental Dylan implementations include Marlais, an experimental Dylan interpreter written in C for UNIX, Macintosh, and Windows; and Thomas, a Dylan interpreter written in Scheme. The current version of Apple's implementation, Apple Dylan, is an integrated development environment for the Macintosh available as a "technology release." The environment generates stand-alone applications and libraries for 68K Macs, native PowerPC, and fat binaries. The environment is not PowerPC native, and runs emulated on PowerPCs.

The Dylan programming environment includes Creole, a foreign-function interface that allows Dylan programs to call routines written in other languages--and routines in another language to call Dylan routines. The Creole specification (designed by David Moon), which is also in the public domain, is the basis for the foreign-function interface used in Apple Dylan, DylanWorks, and Gwydion. Creole provides access to the Macintosh toolbox and third-party libraries needed to develop an application or application component for the Macintosh. In this article, I'll describe how to interface the prealpha version of Dylan with Version 4.02 of the Sybase RDBMS library. There are no technical reasons for my choosing the Sybase RDBMS; I simply have access to it. Mileage and integration steps may vary depending on the version of Sybase or implementation of Dylan or Creole.

Two Dylan concepts--generic functions and methods--are considered functions (C++, on the other hand, has many--operators, virtual, overloaded, member functions, and the like). A generic function is polymorphic and consists of a set of zero or more methods that define its behavior. When a generic function is called, it picks one of its methods (according to argument type) and applies this method to the arguments passed to the generic function. This is more efficient than it sounds because Dylan optimizes function calls. Methods are specialized functions that work on just one set of argument types. This triad is a simple class hierarchy: <function> is the base class for both <generic-function> and <method>.

Creole Overview

Creole is neither a separate application nor a mode of Apple Dylan. It can be viewed as an extension to the Dylan language that adds a statement, define interface, which describes the interface to C functions, structures, unions, global variables, and macros. Creole also adds classes, functions, and macros to support the interaction between Dylan and C code. For Creole to perform this, it has five primary concepts at the language level: interface importation, access paths, cross-language calls, name mapping, and type mapping.

Interface importation involves importing a C header file. Creole imports declarations for functions, variables, types, constants, structures, and unions. The fields of a structure and union are variables to Creole. C macros are functions if the macro accepts parameters; otherwise they are constants. Creole maps C variables and structures and union fields into Dylan getter and setter function pairs. These pairs act as an interface for a variable or "slot" (similar to a C++ class member variable).

A define interface statement, which imports a header file, has a number of options that control Creole's default importation behavior. These options allow selective importing of declarations within a C header file, explicit type-mapping control, and explicit name mapping to avoid name conflicts.

For each imported item, Creole creates a corresponding object or function. Each imported structure or union becomes a class and the fields of the structure or union become slots with corresponding getter and setter functions. Each imported C function becomes a Dylan method with a parameter list that corresponds to the C- function parameter list. The parameter types for the Dylan method can be controlled by Creole's type-mapping support.

If one header file includes another, Creole does not import the declarations within the second header. A separate #include clause or define interface statement can be used to import this second header file.

An access path is a mechanism that loads C functions into memory, making the functions' addresses known to the Dylan program. Creole supports four high-level access paths: inline machine code, external module, and two shared libraries--Apple's Shared Library Manager (ASLM) and Code Fragment Manager (CFM). In addition, there are two low-level access paths called "direct pointer" and "PowerPC transition vectors." The inline machine-code access path is for accessing the toolbox and operating-system traps. The Sybase library is an MPW C object library. The only access path available to C object libraries (if the source code is not available) is the external module path. MPW is required to use this access path.

A Dylan routine can call C functions (or vice versa). Creole records the calling sequence, argument, and result type during importation of a function declaration. Creole creates a corresponding Dylan function that, when invoked, calls the C function, translates arguments and results, and manages the differences between the two languages' run-time environments. Type mapping controls the translation of the arguments and results between languages.

Name conflicts can occur because of differences between C and Dylan. Structures and unions within C have local scope; Dylan has no corresponding feature. C is case sensitive, Dylan is not. Creole has a set of name-translation rules that maps a specific set of C naming conventions into Dylan naming conventions.

Type mapping translates a type in the imported interface to a Dylan class. Type mapping applies to cross-language call arguments, cross-language call results, imported variables, and fields of imported structure and union types. Every C type is mapped to a Dylan type.

To understand how Creole does type mapping, you must understand the type categories that Creole uses. These categories are statically typed pointers, untyped pointers, and by-value types. Statically typed pointers are pointers to objects with a type that cannot be determined at run time. Examples of this are pointers to C structs. The type of such a pointer is declared statically in the interface. The run-time object does not contain any type information.

Untyped pointers do not point to an object. An example in C is void* (and char* in old C code). All untyped pointers are mapped to an instance of <machine-pointer>. The C type information is not retained.

Examples of by-value types are number, character, string, and Boolean.

The Application Nub

A unique aspect of Apple Dylan is the strong separation between the development environment and the application. The development tools are all part of the environment, whereas the code under development is executed within the Application Nub--essentially an empty Dylan application. The environment downloads code and data into the Application Nub via Apple Events. The use of Apple Events allows you to run the environment and Application Nub on separate machines.

With other dynamic programming languages (Lisp and Smalltalk, for instance), it is nearly impossible to separate the application from the development environment. That's why applications developed in these languages have large disk and RAM footprints. Apps developed in Dylan have footprints comparable to those in C++.

Dylan Meets Sybase

The basic procedure for integrating the Sybase library (or any other C library) into a Dylan program is to import the header files and statically link the library into the Application Nub. Supposedly, that's all there is to it. Nothing is this easy, however. In C, you can write ambiguous declarations of variables and functions--and that's a problem for Dylan. More specifically, Sybase's header files, like some UNIX header files, are full of ambiguous declarations.

If you are accessing a shared library (ASLM or CFM), you do not have to rebuild the Application Nub. You simply install the shared library into the system and start calling the desired routines from the listener or your Dylan code.

Creole requires that imported header files should be mutually independent: One header file should not require another header file to be included prior in order to work properly. All supporting declarations must be defined either within the imported header file or within another header included in the imported header file.

Unfortunately, Sybase commits a first-order violation of this rule; most of Sybase's header files must be preceded by sybfront.h. The simple way around this (until Creole has direct support for dependent headers) is to modify the Sybase files to explicitly include sybfront.h. Fortunately, Macintosh header files and standard C library header files are mutually independent. For example, the Macintosh header file Quickdraw.h depends upon the files types.h and QuickdrawText.h, and if you look in Quickdraw.h, you'll see that these files are explicitly included.

Before creating your first define interface statement, Sybase's header files (sybfront.h, syblogin.h, sybdb.h, sybtoken.h, and sybfront.h) must be copied to the project folder. First, create a new module and name it "sybase-dbms". To import the entire contents of sybdb.h, create a source record in the module sybase-dbms with the define interface statement in Example 1(a). When you compile this statement, there will be warnings like Example 1(b) since this define interface statement is a little simplistic.

Sybase uses the pattern of Example 2(a) for most structure definitions. Since Dylan is case insensitive, both dbprocess and DBPROCESS are mapped to the same Dylan object, <dbprocess>. As any C programmer knows, typedef DBPROCESS is essentially a name for struct dbprocess; hence, both dbprocess and DBPROCESS represent the same object. Therefore, you need to import only one of them. Example 2(b) shows what happens when you implicitly import dbprocess. Issuing the compile command generates the error in Example 2(c).

The define statement in Example 2(b) is explicitly telling Creole just to import the declaration of dbprocess. However, Creole does not know the data types--except built-in data types--of the fields that comprise dbprocess. For example, dbfile, the first field of dbprocess, is typed struct servbuf* and Creole does not know what a servbuf is. The structure definition for dbprocess has a number of fields, most of which are immaterial. Using Creole's struct clause, you can explicitly import only those fields that you have an interest in. For now, you will not import any fields; see Example 2(d). Another compile yields no error or warning message. Creole has created a class by the name <dbprocess>. If you type <dbprocess> in the listener, Dylan will respond with #<the class sybase-dbmsdbprocess>>. This object (classes are objects) can then be inspected by selecting the Inspect Listener Result menu item from the Debug menu.

Sybase from the Listener

C functions linked into the Application Nub can be invoked directly from the listener and send their results back to the listener--a portal into the run time. Dylan expressions can be entered into the listener: They will be compiled, downloaded, and executed within the run time. Values returned by the execution of the entered expression will be printed in the listener.

To illustrate, I'll import types, functions, and macro definitions necessary to invoke the basic Sybase functions from the listener. Once everything is imported, I will initialize Sybase's db-library, create and initialize a login record, create and open a connection to a database process on the server, close a database connection, and exit (cleanup) db-library from the listener.

The first function to import is dbinit, which initializes the Sybase library and has the prototype RETCODE dbinit(void), where RETCODE is typedefed as an int. Example 3(a) is the corresponding define interface statement. Compiling this generates the warning message in Example 3(b). Creole is saying that the declaration for the dbinit function has been imported, but it has no idea where the object code of the function is. For now, you will specify the location of the function using the external-module clause, which specifies the access path; see Example 3(c).

The next function to import is dblogin, which creates a login record for use in dbopen and has the prototype LOGINREC* dblogin(void). Since LOGINREC has not been imported, it goes on to the import list. In LOGINREC, defined in syblogin.h, the macros DBSETLHOST, DBSETLUSER, DBSETLPWD, and DBSETLAPP set the host name, user name, user password, and application name fields, respectively.

Examining the LOGINREC macros reveals the fields that must be imported: lhostname, lhostnlen, lusername, lusernlen, lpw, lpwnlen, lhostproc, lhplen, lappname, lappnlen, lservname, lservnlen, lprogname, and lprognlen.

The declarations of the various character fields (for example, char lhostname[MAXNAME]) depend upon the size specifier MAXNAME, where MAXNAME is a #define constant defined in sybdb.h. Note that MAXNAME is defined in sybdb.h but is needed in syblogin.h, where LOGINREC is defined--syblogin.h does not include sybdb.h. This creates another dependency between header files: syblogin.h requires sybdb.h to be previously included. To handle this dependency, you can hard-code the constant in either syblogin.h or through the define clause for the #include statement (for example, #include "syblogin.h", define: {"MAXNAME" => 30}).

You now need to import both the macros that set the fields of a LOGINREC and the function dbsetlname, which these macros call. Creole creates inline functions for these macros. dbsetlname has the prototype RETCODE dbsetlname(LOGINREC* lptr, char* name, int type). The second parameter, name, could cause a run-time problem if you try to pass a string as the second argument. name has a C type of char*, but Creole treats it as an untyped pointer. C programmers know that name is probably a null-terminated string, but it could also be a pointer to a single character or to an array of characters. There is no way for Creole (or even the C compiler) to determine the complete data type of this argument. The behavior of the program determines the semantics of name.

You can avoid these problems if header files are structured properly. Pointer types (that is, char*) should be typedefed and these typedefs should be used in place of the pointer types. If you defined a typedef for SybString (typedef char* SybString;) in the Sybase header files and replaced all references to char* with SybString, you would only have to tell Creole once that a SybString maps to a <c-string>. A good example of this is StringPtr and StringHandle defined in types.h.

When Creole reads the declaration for dbsetlname, it creates the function dbset-lname(lptr :: <LOGINREC>, name :: <machine-pointer>, type :: <integer>) => return-code :: <integer>. If you passed a string for the name argument within a dbsetlname call, you would get a type mismatch: A string, typed <c-string> is not a <machine-pointer>. You need to explicitly define the type for name via the function clause of the define interface statement.

Listing One presents two define interface statements: one for sybdb.h and one for syblogin.h. Next, the dbopen function is imported, which creates a database process on the server and opens a connection to the process. The prototype for this function is DBPROCESS* dbopen(LOGINREC* password, char* servername). There is nothing new to import, other than the function name. dbopen has the same problem as dbsetlname: the argument servername is an untyped pointer and a program needs to pass it a string. Using the function clause solves this.

The last functions to import are dbclose and dbexit. dbclose has the prototype void dbclose(DBPROCESS* dbproc), and dbexit has the prototype void dbexit(void). With both functions, the only symbols you need to import are the function names. Listing Two thus becomes the define interface statement for sybdb.h.

Building the Application Nub

In rebuilding the Application Nub using the external access path, you create a source record that calls the write-external-modules function. When compiled, this function generates an assembly file that contains the definition of the external modules. The name of the generated file is the name passed to this function; external-modules.a would suffice.

The first step to building the new Application Nub is to bring up the project from within Dylan and compile--without connecting to the Application Nub--each of the define interface statements then compile the write-external-modules function call. If you try to compile the define interface statements while connected to the standard Application Nub, Apple Dylan will complain that it cannot find your external modules, since the standard Application Nub does not have the Sybase code linked in. After you make a custom Application Nub that includes both the Sybase code and the external module that makes it accessible to Dylan, this problem will disappear.

Once the external modules file has been generated, you need to modify the makefile (supplied with Apple Dylan) for the Application Nub to include the Sybase library (libsybdb.o). Next, launch MPW (Version 3.3 or later), build the Application Nub, and move the custom Application Nub to your project folder.

Before connecting to the new Application Nub, copy (or make an alias of) the Apple Dylan file Kernel.dl to your Extensions folder or to your project folder. Now you can go back into Apple Dylan and connect to the Application Nub. In the prealpha version of Apple Dylan, the Application Nub must be named "Application Nub." Apple Dylan looks for this file by name.

After the needed functions and types have been imported and the Application Nub has been rebuilt with the Sybase library, Sybase calls can be made by hand (from the listener). Before typing into the listener, set the module to sybase dbms; see Example 4.

Sybase Error and Message Handlers

Before importing functions to do queries, you can optionally install error and message handlers for Sybase to invoke. If handlers are not installed, Sybase will write error messages to the console (generally considered a bad practice for a commercial app) and exit.

Installing the error and message handler is straightforward using dberrhandle and dbmsghandle. Importing these functions is another story, starting with how dberrhandle and dbmsghandle are declared. In the Sybase file sybdb.h, dberr-handle is declared as int (*dberrhandle(int (*handler)()))();. dbmsghandle has a similar declaration problem.

Creole can't handle this declaration because the type information for the parameters of the function the handler points to is insufficient. You must therefore create a header file with an enhanced declaration of dberrhandle and dbmsghandle, plus a separate define interface statement that imports their definitions. You could modify the header files directly, but it is probably better not to modify a third-party header file. Listing Three shows the sybfix.h include file. In Listing Four, the corresponding define interface statement adds callback, a new clause that defines a Dylan macro. When invoked, the macro creates a C-callable function, or "alien method," which obeys C-calling sequences and can be passed to a C program. The callback clause's argument-type allows explicit typing of an ambiguous argument. The fifth, sixth, and seventh arguments of the msgHandler callback macro are declared as pointers to a char*. Dylan does not know that these arguments are strings and, without the argument typing, sees them as untyped pointers.

To install the callbacks, you can create the handlers as normal Dylan methods and assign the results of the callback macros to a variable that is then passed to dberrhandle or dbmsghandle.

The message handler takes arguments whose data type match the declaration in the sybfix.h file. There is nothing elegant about this message handler--it prints the value of the various parameters to the listener. The release of Dylan I'm using doesn't support printing to the listener, so I use the warning-signaling mechanism. The signal method is a Dylan version of printf that takes an arbitrary number of arguments, where the first argument is the format string that controls the printing of the remaining arguments. Since signal is part of the signaling mechanism, it prints the word "warning" before the formatted string; see Listing Five. sybfront.h must be imported before the message handler can be compiled without warnings. This method uses the constant $int_continue defined in this header file. This and other constants can be imported using the define interface statement in Listing Six.

You can create the callback routine once the message handler is defined. The use of the callback macro seems strange because the Dylan macro system is incomplete; macro use may change once the system is finalized. Listing Seven sets up an alien-callable alien method that calls the message handler. The error-handler function and ErrHandler callback are performed similarly; see Listing Eight.

Querying Sybase

The next step is to query the database. Listing Nine lists the function declarations that must be imported to issue a query to Sybase. dbcmd needs a function clause: The second argument has a C type of char*. dbbind does not need a function clause to augment the type information for destvar; you will be passing an untyped-pointer argument.

To import the constants NO_MORE_RESULTS, NO_MORE_ROWS, NTBSTRINGBIND, DBNOERR, and INTBIND, take Listing Ten as the final define interface statement. I'll query against a sample table of viruses and mortality rates. The mortality rates in Table 1 were distilled from The Cambridge World History of Human Disease. I took liberties in summarizing the mortality rates for each virus. In some cases, the rates were presented in terms of a range; in others, the rates were presented for hospitalization or no care. If there were more than one number, I took the lower one. This gives everything needed to write a program (or a method) that queries the database and returns. The oh-oh method connects to the fictitious morbidity database on server "Oahu" as user "virologist" with password "Aiea." Once the connection has been established, oh-oh issues a query to the database asking for a list of viruses where the mortality rate is greater than 50 percent. Listing Eleven shows the results of this query, which are displayed in the listener using the signal facility. To run oh-oh, invoke it from the listener. Example 5 shows how this method runs.

Conclusion

For more information on Dylan and Creole, refer to Dylan Interim Reference Manual available at cambridge.apple.com via ftp or on the Dylan Web page located at http://www.cambridge.apple.com.

For More Information

Apple Computer

1 Infinite Loop

Cupertino, CA 95014

http://www.apple.com

Harlequin Inc.

1 Cambridge Center

Cambridge, MA 02142

http://www.harlequin.com

Carnegie-Mellon University

5000 Forbes Avenue

Pittsburgh, PA 15213

http://cmu.edu

Example 1: (a) Using the define interface statement to import the contents of sybdb.h; (b) compiling the statement in (a) generates this message.

(a)
define interface

#include "sybdb.h"

end interface; (b) Warning in a Creole interface: "dbprocess" and "DBPROCESS" both map to Dylan variable <dbprocess>; one will be lost.

Example 2: (a) Typical Sybase structure definition; (b) implicitly importing dbprocess; (c) Error message generated when compiling (b); (d) using Creole's struct clause.

(a)
struct dbprocess

{

<wonderful field declarations removed>

};

typedef struct dbprocess DBPROCESS; (b) define interface

#include "sybdb.h",

import: {"dbprocess"};

end interface; (c) Fatal Error in a Creole interface: No type mapping known for "struct servbuf*". Cannot import variable "dbprocess::dbfile" because there is no type mapping from "struct servbuf*" to a Dylan class. You should use define interface to import the file or use type: {"struct servbuf*" => your-class} to add an explicit type mapping. (d) define interface

#include "sybdb.h",

import: {"dbprocess"};

struct "dbprocess", import: {};

end interface;

Example 3: (a) The define interface statement that initializes the Sybase library; (b) compiling generates this message; (c) specifying the location of the function using the external-module clause.

(a)
define interface

#include "sybdb.h", import: {"dbinit", "dbprocess"};

struct "dbprocess", import: {};

end interface; (b) Warning in a Creole interface: No access path to "dbinit" exists. On the 68k we can only call things via inline machine code or one of the access paths to external object code, which are External Modules, the Code Fragment Manager, and the Apple Shared Library Manager. This means that there is no way to call "dbinit". An attempted call will signal an error at runtime. (c) define interface

#include "sybdb.h",

import: {"dbinit", "dbprocess"},

external-module: syb-externals;

struct "dbprocess", import: {};

end interface;

Example 4: Setting the module to sybase dbms before using the listener (boldface denotes user input).

Welcome to Apple Dylan!
Dylan> dbinit()
1
Dylan> define variable login = dblogin()
define login
Dylan> dbsetlhost(login, "Oahu")
1
Dylan> dbsetluser(login, "tourist")
1
Dylan> dbsetlpwd(login, "Aiea")
1
Dylan> dbsetlapp(login, "example1")
1
Dylan> define variable dbproc = dbopen(login, "db")
define dbproc
Dylan> dbproc
#<Dylan-User%<dbprocess> at: #x019FE7C0>
Dylan> dbclose(dbproc)
Dylan> dbexit()
Dylan>

Example 5: Running the sample method (boldface denotes user input).

Welcome to Apple Dylan!
Dylan> oh-oh()
Warning: Ebola Zaire 90
Warning: Ebola Sudan 52
Warning: Bubonic Plague 51
#f

Table 1: Sample mortality rates from The Cambridge World History of Human Disease.

Virus                           Mortality

Yellow Fever                    20

Typhoid Fever                   10

Smallpox Variola Major          25

Smallpox Variola Minor          1

Rocky Mtn. Spotted Fever        20

Relapsing Fever                 5

Marburg Fever                   NULL

Leptospirosis                   5

Legionnaires' Disease           15

Lassa Fever                     16

Influenza                       1

Ebola Sudan                     52

Ebola Zaire                     90
 
Bubonic Plague                  51

Listing One

define interface
    #include "sybdb.h",
        import:
            {"dbinit", "dbprocess", "dbsetlname",
                // Parameterized Macros
                "DBSETLHOST", "DBSETLUSER", "DBSETLPWD", "DBSETLAPP",
                // Constant Macros
                "MAXNAME"},
        external-module: syb-externals;
    struct "dbprocess", import: {};
    function "dbsetlname",
        argument-type: {name => <c-string>};
end interface;
define interface
    #include "syblogin.h",
        define: {"MAXNAME" => 30},
        import: {"loginrec"};
    struct "loginrec",
        import:
            {"lhostname", "lhostnlen",
                "lusername", "lusernlen",
                "lpw", "lpwnlen",
                "lhostproc", "lhplen",
                "lappname", "lappnlen",
                "lservname", "lservnlen",
                "lprogname", "lprognlen"};
end interface;

Listing Two

define interface
    #include "sybdb.h",
        import:
            {"dbinit",
                "dbprocess", "dblogin", "dbopen",
                "dbclose", "dbexit", "dbsetlname",
                // Parameterized Macros
                "DBSETLHOST", "DBSETLUSER", "DBSETLPWD", "DBSETLAPP",
                // Constant Macros
                "MAXNAME"},
            external-module: syb-externals;
    struct "dbprocess", import: {};
    function "dbopen",
        argument-type: {servername => <c-string>};
    function "dbsetlname",
        argument-type: {name => <c-string>};
end interface;

Listing Three

#include "sybfront.h"
#include "sybdb.h"
typedef int (*ErrHandler)(DBPROCESS* dbproc, int severity, int dberr, 
                                  int oserr, char* dberrstr, char* oserrstr);
ErrHandler  dberrhandle(ErrHandler handler);
typedef int  (*MsgHandler)(DBPROCESS* dbproc, int msgno, int msgstate, 
                                  int severity, char* msgtext, char* srvname, 
                                  char* procname, unsigned short line);
MsgHandler  dbmsghandle(MsgHandler handler);

Listing Four

define interface
    #include "sybfix.h",
        import:
            {"dberrhandle", "dbmsghandle", "ErrHandler",
                "MsgHandler"},
            external-module: syb-fix-externals;
            callback "ErrHandler",
                argument-type: {5 => <c-string>},
                argument-type: {6 => <c-string>};
            callback "MsgHandler",
                argument-type: {5 => <c-string>},
                argument-type: {6 => <c-string>},
                argument-type: {7 => <c-string>};
end interface;

Listing Five

define method message-handler(dbproc :: <dbprocess>,
                              msgno :: <integer>,
                              msgstate :: <integer>,
                              severity :: <integer>,
                              msgtext :: <c-string>,
                              srvname :: <c-string>,
                              procname :: <c-string>,
                              line :: <integer>)
=> action :: <integer>;
    ignore(dbproc);
    signal("~&Msg ~d, Level ~d, State ~d", msgno,
        severity, msgstate);
    if (size(srvname) > 0)
        signal("Server '~a', ", srvname);
    end if;
    if (size(procname) > 0)
        signal("Procedure '~a', ", procname);
    end if;
    if (line > 0)
        signal("Line ~d", line);
    end if;
    signal("~&~a", msgtext);
    $int_continue;
end method;

Listing Six

define interface
    #include "sybfront.h",
        import:
            {"INT_EXIT", "INT_CONTINUE", "INT_CANCEL", "SUCCEED", "FAIL"};
end interface;

Listing Seven

define variable message-handler-proc =
MsgHandler(dbproc(msgno, msgstate, severity, msgtext, srvname, 
                    procname, line), message-handler(dbproc, msgno, msgstate, 
                    severity, msgtext, srvname, procname, line));

Listing Eight

define method error-handler(dbproc :: <dbprocess>, severity :: <integer>,
                            dberr :: <integer>, oserr :: <integer>,
                            dberrstr :: <c-string>, oserrstr :: <c-string>)
=> action :: <integer>;
    ignore(dbproc);
    ignore(oserr);
    ignore(dberr);
    ignore(severity);
    if (dbproc = $null-machine-pointer | dbdead(dbproc))
        $INT_EXIT;
    else
        signal("DB-Library error:~&~a", dberrstr);
        if (oserr ~= $DBNOERR)
            signal("Operating-system error:~&~a", oserrstr);
        end if;
        $INT_CANCEL;
    end if;
end method;
define variable error-handler-proc = ErrHandler(dbproc(severity, dberr, oserr,
                       dberrstr, oserrstr),error-handler(dbproc, severity,
                       dberr, oserr, dberrstr, oserrstr));

Listing Nine

RETCODE dbcmd(DBPROCESS* dbproc, char* cmdstring);
RETCODE dbsqlexec(DBPROCESS* dbproc);
RETCODE dbresults(DBPROCESS* dbproc);
RETCODE dbbind(DBPROCESS* dbproc, int column, int vartype, DBINT varlen, 
                                                              BYTE* destvar);
STATUS dbnextrow(DBPROCESS* dbproc);

Listing Ten

    #include "sybdb.h",
        import:
            {"dbinit", "dbsetlname", "dbprocess", "dblogin", "dbopen",
             "dbclose", "dbexit", "dbcmd", "dbsqlexec", "dbdead", "dbbind",
             "dberrhandle", "dbmsghandle", "dbnextrow", "dbresults",
                // Parameterized Macros
                "DBSETLHOST", "DBSETLUSER", "DBSETLPWD", "DBSETLAPP",
                //  Message/Error Handler Types
                "MHANDLEFUNC", "EHANDLEFUNC",
                //  Constants
                "MAXNAME", "NO_MORE_RESULTS", "DBNOERR", "NO_MORE_ROWS"
                // Binding Constants
                "INTBIND", "NTBSTRINGBIND"},
     external-module: syb-externals;
  struct "dbprocess", import: {};
  function "dbopen", argument-type: {servername => <c-string>};
  function "dbsetlname", argument-type: {name => <c-string>};
  function "dbcmd", argument-type: {cmdstring => <c-string>};
end interface;

Listing Eleven

oh-oh
define method oh-oh()
    if (dbinit() = #f)
        error("Unable to initialize Sybase.");
    end if;
    dberrhandle(error-handler-proc);
    dbmsghandle(message-handler-proc);
    let login = dblogin();
    dbsetlhost(login, "Oahu");
    dbsetluser(login, "virologist");
    dbsetlpwd(login, "Aiea");
    dbsetlapp(login, "macabre");
    let dbproc = dbopen(login, "morbidity");
    dbcmd(dbproc, "select name, mortality ");
    dbcmd(dbproc, "from virus ");
    dbcmd(dbproc, "where mortality > 50");
    dbsqlexec(dbproc);
    for (result-code :: <integer> = dbresults(dbproc)
        then dbresults(dbproc), until result-code = $NO_MORE_RESULTS)
        if (result-code = $SUCCEED)
            //  <c-string> Virus Name
            with-stack-block(name(<machine-pointer>, 31),
            //  <integer> Mortality
            with-stack-block(
                    mortality(<machine-pointer>, 4),
                begin
                    dbbind(dbproc, 1, $NTBSTRINGBIND, 0, name);
                    dbbind(dbproc, 2, $INTBIND, 0, mortality);
                    while(dbnextrow(dbproc) ~= $NO_MORE_ROWS)
                        signal("~A ~A", c-string-at(name),
                            signed-long-at(mortality));
                    end while;
            end))));
        end if;
    end for;
    dbexit();
end method;