This file is part of the TADS 2 Author’s Manual.
Copyright © 1987 - 2002 by Michael J. Roberts. All rights reserved.
Edited by NK Guy, tela design.

Appendix F

External Functions

Please note - this section is here for historical reasons and completeness. Most authors will have no use for external functions, as they are neither cross platform nor fully supported in all interpreters. For these reasons external functions are no longer really encouraged.

TADS has a facility that allows you to call a function written in another language, such as C or Assembler, from within your TADS game program. I refer to these routines written in other languages as “external functions” or “user exits.” This appendix describes how to write and invoke external functions.

You may wish to be able to invoke a function written in another language, such as C or Assembler, from within your game program. For example, you may want to write a function that reads some information from a disk file, or you may want to program the sound hardware on your computer. TADS allows you to do this through a mechanism known as “user exits” or “external functions.”

Before going further, I should warn you that writing a user exit is somewhat complicated. I’ve tried to make it as easy as possible to write an external function, but you’ll have to learn a number of subtle details in order to use this mechanism. Furthermore, I don’t have the space in this appendix (or the pedagogical facility) to provide any information about programming in C or Assembler or any other language, or about using your compiler or linker or other programming tools. So, I’ll assume that you already know how to program in C, and that you’re already familiar with your programming tools.

Finally, you should keep in mind that external functions are not portable, by their very nature. An external user function written for one platform will not work on another. More importantly, many TADS interpreters do not support external functions at all. MaxTADS, WinTADS and TADS/2 for Macintosh, Windows 95/NT and OS/2 respectively, do not support external functions.

Declaring and Invoking a User Exit

As far as your TADS program is concerned, an external function is nearly the same as a built-in function. The only difference is that you must tell the TADS compiler that the function is a user exit; you do this by using the special external function declaration. This declaration is similar to a forward declaration for a normal function; for example, if you wish to define a user exit named myfunc, you would include a declaration like this in your TADS program:

  myfunc: external function; 

Once you’ve declared myfunc as an external function, you can call it just as you would call any built-in or user-defined function. For example, you could include code like this in your TADS program:

  x := myfunc(10, 'hello'); 

The Sample User Exit

Invoking a user exit is easy; writing a user exit is a little more complicated. The rest of this appendix describes how to write a user exit. Note that the TADS software contains a sample user exit written in C, as well as a small TADS game program that invokes it; you will probably find it helpful to look at the example to get a better idea of how the external function mechanism works. The user exit itself is contained in the file TESTUX.C; the TADS game program that invokes the user exit is in the file TESTUX.T. You may also want to consult TADSEXIT.H, which is a file that you should include in your C user exits; this file defines the interface between TADS and your external function with easy-to-use macros.

Note that the process of compiling and linking an external function varies by operating system. You should refer to TESTUX.C, which contains information about compiling and linking a user exit for each operating system. Note that you can write user exits in any language that will support a C-style interface; you may wish to use assembly language in particular if your user exit must access your computer’s hardware. However, we only provide the TADSEXIT.H definitions file for the C language, and the instructions in TESTUX.C are specific to C. If you want to use another language, you’ll have to be familiar enough with your compiler and operating system to be able to apply the instructions we’ve provided to your language.

Writing the External Function

In general, you write your TADS user exit as a regular C function. You must not use any static or global variables, but you can use variables on the stack. You can’t call any of the C run-time library functions (such as printf) that may use global variables. If your function or any function it calls uses global variables, the results will be unpredictable, and you may crash the machine.

(Note that the prohibition of static and global variables is a simplification. In actuality, you can use globals on most systems under specific conditions. On MS-DOS, Atari ST, and Macintosh, you can use global variables as long as they are PC-relative - that is, they must be contained in the same program section as the object code of your user exit and must be addressed using the program counter. If you are familiar enough with your compiler to know how to generate PC-relative static variables, which most compilers on these systems can do when presented with the correct combination of secret options, you should be able to use static variables without any harm. Exactly how you go about doing this with your compiler is, obviously, beyond the scope of this appendix.)

The first thing in the C program that contains your user exit should be a #include directive like this:

  #include <tadsexit.h> 

This inserts the header file tadsexit.h (which is part of the TADS software) into the current compilation unit. This file defines the interface between TADS and your user exit program with a set of easy-to-use macros. Using these macros, you don’t need to be aware of the strange details of the data structures and calling sequences that TADS uses to call your user exit and vice versa.

Whatever you call your user exit with the external function declaration in your TADS program, the user exit in your C program is called main. You declare it like this:

  int main(ctx)
  tadsuxdef far *ctx;

You may have two worries at this point. First, if the C program calls the function main, how does TADS know to call it when you call myexit from your game program? Second, if you want to call more than one user exit from your game program, how can they both be named main?

The answer to both questions is that TADS learns the name of your user exit not from the C compiler (which only knows the function as main), but from a “resource editor.” On MS-DOS and Atari ST systems, this resource editor is called TADSRSC, and is provided with the TADS software; refer to the documentation in the file TESTUX.C for information on how to use TADSRSC. On Macintosh systems, you should use a standard resource editor, such as ResEdit, which should be packaged with your C programming tools.

In any case, each user exit is compiled separately, which means that you can have as many as you want - all named main - without your C compiler or TADS becoming confused about all the identical names. Using the resource editor, you add each separately compiled user exit to your game program, assigning the resource the name that you use in your external function declaration. The important thing is that the resource name defined with the resource editor matches the name declared in the external function statement.

The argument to the function main is not the argument that you provided in the invocation of the user exit in your TADS program. Instead, it is a “context,” which you use for communications with TADS. You use this context argument to obtain the actual arguments to your user exit function.

All of your arguments are on a “stack,” which means that you can get to them one at a time. First, you call the function tads_tostyp(ctx) to learn the type of the first argument. This returns one of these values (defined in TADSEXIT.H): TADS_NUMBER, indicating the argument is a long integer; TADS_STRING, indicating the argument is a (single-quoted) string; TADS_NIL or TADS_TRUE, indicating one of the truth values. You cannot pass other types of values to user exits, because the user exits wouldn’t know what to do with other types.

Next, you retrieve the first argument from the stack by calling the function appropriate to its type: tads_popnum(ctx) for numbers, tads_popstr(ctx) for strings, or just tads_pop(ctx) for truth values. This last function has no return value, because all you need to know is the type (true or nil), but you still must call it to remove the argument from the stack; the other two functions return either a long integer or a string descriptor, respectively.

The return value of the tads_popstr(ctx) function is a “string descriptor,” which is not a string pointer in the usual C sense. Instead, it is a special data structure that contains the length of the string and the text of the string. TADS version 2 does not use C-style null-terminated strings, but rather keeps the length of each string along with its text value. You can’t use the string descriptor directly, but two functions are provided that allow you to decompose it into values you can use. Call tads_strlen(ctx, str), where str is the descriptor value returned by tads_popstr(), to get the length of the string; this function returns an integer giving the number of bytes in the string. Call tads_strptr(ctx, str) to get the text of the string; this function returns a character pointer giving the address of the first byte of the string’s text. Note that the string’s text is not null-terminated - you must use the value from tads_strlen() to determine the length of the text buffer. Be careful not to use any of the C functions that require a null-terminated string with the text buffer, because the buffer does not have a terminating null byte.

You should use the special type tads_strdesc to hold the return value of the tads_strpop() function. This type is defined in TADSEXIT.H along with the TADS interface functions.

Never write into a text buffer obtained with tads_strptr(). TADS assumes that you will leave these text buffers unchanged; modifying an argument string’s text buffer will have unpredictable results.

After you have retrieved the first argument, you proceed with other arguments in the same manner. You must retrieve all arguments from the stack with one of the tads_popxxx(ctx) functions, and you must not retrieve any more than you received. You can find out how many arguments your function actually received by calling the function tads_argc(ctx); this function returns an integer telling you the actual number of arguments. (This function returns the actual number of arguments regardless of whether you have retrieved any arguments; it does not change as you pop arguments off the stack.)

If your user exit returns a value, you use one of the tads_pushxxx(ctx, value) functions. To return a number, call tads_pushnum(ctx, value), where value is a long integer. To return a truth value, call tads_pushtrue(ctx) or tads_pushnil(ctx). To return a C-style string (that is, a null-terminated string that you built on the C stack, using a local character array variable), use tads_pushcstr(ctx, pointer), where pointer is the address of the first byte of the string.

You can also ask TADS to allocate space for a string, and build your return value using this space. Call tads_stralo(ctx, len), where len is an integer specifying the number of bytes in the string you wish to return. This function returns a pointer to the first byte of the allocated space. Write your string into this space. The string must not be null-terminated; instead, use exactly the number of bytes that you allocated. Once you have finished building your string, call tads_pushastr(ctx, pointer) to push the return value, where pointer is the value originally returned by tads_stralo(). TADS remembers the length of buffer you allocated, so there is no need to specify the length when returning the string.

Skilled in the works of both languages.
HORACE, Satires (23 B. C.)

Appendix E Table of Contents Appendix G