% $Header: c:/tads/doc/RCS/tadsusex.tex 1.1 92/11/13 16:46:24 mroberts Exp $
% Copyright (c) 1992 by Michael J. Roberts.  All Rights Reserved.
% Appendix F: User Exits

\chapterheader{Appendix F}{External Functions}{F}

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.
We refer to these routines written in other languages as ``^{external
functions}'' or ``^{user exits}.''  This appendix describes how to write
and invoke external functions.


\chapterbody

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, we should warn you that writing a user exit
is somewhat complicated.  We've tried to make it as easy as possible
to write an external function, but you will have to learn a number
of subtle details in order to use this mechanism.  Furthermore,
we 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, we'll assume that you already know
how to program in C, and that you're already familiar with your
programming tools.


\minorsection{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 {\tt 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 {\tt myfunc}, you would include
a declaration like this in your TADS program:
^^{external function declaration}

{\example
myfunc: external function;
}

Once you've declared {\tt 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:

{\example
x := myfunc(10, 'hello');
}

\minorsection{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 {\tt TESTUX.C}; the TADS game program that
invokes the user exit is in the file {\tt TESTUX.T}.  You may also
want to consult {\tt 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.
^^{TESTUX.T}
^^{TESTUX.C}
^^{TADSEXIT.H}

Note that the process of compiling
and linking an external function varies by operating system.  You
should refer to {\tt 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 {\tt TADSEXIT.H} definitions file for the ^{C language},
and the instructions in {\tt 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.


\minorsection{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 {\tt \#include} directive like this:
^^{TADSEXIT.H}

{\example
\#include <tadsexit.h>
}

This inserts the header file {\tt 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 {\tt external function}
declaration in your TADS program, the user exit in your C program
is called {\tt main}.  You declare it like this:

{\example
  int main(ctx)
  tadsuxdef far *ctx;
  \lb
}

You may have two worries at this point.  First, if the C program
calls the function {\tt main}, how does TADS know to call it when
you call {\tt 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 {\tt 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 {\tt 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 {\tt 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
{\tt 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 {\tt external function}
declaration.  The important thing is that the resource name defined
with the resource editor matches the name declared in the {\tt external
function} statement.

The argument to the function {\tt 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 {\tt tads\_tostyp(ctx)} to
learn the {\it type} of the first argument.  This returns one of
these values (defined in {\tt TADSEXIT.H}):  {\tt TADS\_NUMBER},
indicating the argument is a long
integer; {\tt TADS\_STRING}, indicating the argument is a
(single-quoted) string; {\tt TADS\_NIL} or {\tt 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:  {\tt tads\_popnum(ctx)} for numbers,
{\tt tads\_popstr(ctx)} for strings, or just
{\tt 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 {\tt tads\_popstr(ctx)} function is a
``string descriptor,'' which is {\it 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 {\tt tads\_strlen(ctx, {\it str})}, where {\it str} is the
descriptor value returned by {\tt tads\_popstr()}, to get the length
of the string; this function returns an integer giving the number
of bytes in the string.  Call {\tt tads\_strptr(ctx, {\it 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 {\it not} null-terminated---you
must use the value from {\tt 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 {\tt tads\_strdesc} to hold the return
value of the {\tt tads\_strpop()} function.  This type is defined
in {\tt TADSEXIT.H} along with the TADS interface functions.

Never write into a text buffer obtained with {\tt 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 {\it must} retrieve all arguments
from the stack with one of the {\tt tads\_pop{\it xxx}(ctx)} functions,
and you {\it must not} retrieve any more than you received.  You can
find out how many arguments your function actually received by
calling the function {\tt 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 {\it not} change as you
pop arguments off the stack.)

If your user exit returns a value, you use one of the
{\tt tads\_push{\it xxx}(ctx, {\it value})} functions.  To return
a number, call {\tt tads\_pushnum(ctx, {\it value})}, where {\it value}
is a long integer.  To return a truth value, call {\tt tads\_pushtrue(ctx)}
or {\tt 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 {\tt tads\_pushcstr(ctx, {\it pointer})},
where {\it 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 {\tt tads\_stralo(ctx, {\it len})},
where {\it 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 {\it not} be null-terminated; instead, use {\it exactly}
the number of bytes that you allocated.  Once you have finished building
your string, call {\tt tads\_pushastr(ctx, {\it pointer})} to push
the return value, where {\it pointer} is the value originally returned
by {\tt tads\_stralo()}.  TADS remembers the length of buffer you
allocated, so there is no need to specify the length when returning
the string.

\endchapter
Skilled in the works of both languages.
\author HORACE, {\sl Satires} (23 B. C.)

\eject
