In order to write plugins for breve, you'll need to familiarize yourself with a feature of steve which is generally hidden from users--the C-style function call.steve supports C-style function calls in the form functionName(argument1, arguement2, ...), though the use of this feature is intended for internal use only such as direct interaction with the breve engine and for use with plugins.
The steps to writing a breve plugin are thus:
- write wrapper functions around your C-functions that breve will understand
- write an "entry point function" which will load your new functions
- write a class which provides a class based interface to your breve C-style functions
- build the breve plugin bundle in Project Builder
Prior to this release of the breve API, there were two methods for building plugins. In the current release, there is only one method to build plugins--it is the more flexible and more general approach. Plugins built with the older OS X-only method will need to be recompiled using the new method. Plugins are NOT binary compatible between versions.
Step 1: write wrapper functions around your C-functions that breve will understand
All C functions which will be added to breve have the same prototype. This means that you will have to make a "wrapper" function around the function you want to add. The wrapper function will take the arguments specified by the breve prototype, and plug them in to your own function.The breve function prototype uses the internal steve evaluation structure stEval. The values of the stEval structure are obtained using the following macros which take a pointer to an stEval:
STINT(&x) => returns the int contained in x STDOUBLE(&x) => returns the double contained in x STVECTOR(&x) => returns the slVector contained in x (a 1x3 vector) STSTRING(&x) => returns the char* contained in x STLIST(&x) => returns the stEvalListHead* contained in x STPOINTER(&x) => returns the void* contained in x STOBJECT(&x) => returns the instance pointer (void*) contained in x STMATRIX(&x) => returns the double** contained in x (a 3x3 matrix)The prototype for all functions that are added to the language is:
int function(stEval arguments[], stEval *result, void *instance)The first argument is an array of stEvals which will be interpreted as the arguments passed to the breve C-style function. For each argument, you should copy it into a local variable of the correct type using one of the macros above.
The second argument is a pointer to the return value of your function. You should assign it using the macros above.
The final argument is a pointer to the internal object which is calling your function. Though this may be useful for debugging your module and figuring out which objects are calling your functions, it cannot currently be used for anything else.
The return value should be EC_OK in the event of sucessful execution, or EC_ERROR in the event of a fatal error. Returning EC_ERROR will cause the simulation to stop, so you should generally not return this value. In many cases it is better to indicate the error using a special return value of the internal function (that is to say, buy putting a special value in the "result" struct, not actually returning from your C code with a special value). If you want to print an error, you should do so from the breve class files you write, not from the C code.
As an example, consider the following prototype:
char *downloadURL(char *url, int timeout);Here is the translation to a breve function:
int breveDownloadURL(stEval *arguments, stEval *result, void *instance) { char *url; int timeout; url = STSTRING(&arguments[0]); timeout = STINT(&arguments[1]); STSTRING(result) = downloadURL(url, timeout); return EC_OK; }
Step 2: write an "entry point" function to load the new functions
Your entry point method should have a prototype something like this:
int myPluginInit(void *data);In this entry point function, simply call the following function once for each wrapper function you have created in step 1:
int stNewSteveCall(void *data, char *name, int (*call)(stEval *a, stEval *r, void *i), int rtype, ...);The first argument, data, is simply the data variable which is passed into the entry point function. This is simply internal data which breve needs when adding new functions.
The second argument, name, is the name of the function as it will appear internally to steve.
The third argument, call, is a pointer to the wrapper function which calls your C function. Assuming that you have function prototypes for the wrapper properly defined, this is simply the name of the wrapper function.
The fourth argument, rtype, is the return type your breve function will have. If there is no return type, use AT_NULL. Otherwise, this should be one of AT_INT, AT_DOUBLE, AT_VECTOR, AT_LIST or AT_POINTER.
Subsequent arguments--up to 16--indicate argument types passed to the C function, ending with the argument 0. Define the argument types passed to your function, one after the other using the types AT_INT, AT_DOUBLE, AT_VECTOR, AT_LIST and AT_POINTER. Make sure that the final argument passed to stNewSteveCall is 0.
We will show an example using the function downloadURL with prototype:
char *downloadURL(char *url, int timeout);Remember that our wrapper function has the definition:
int breveDownloadURL(stEval *arguments, stEval *result, void *instance);In this example, we'll want function to appear as downloadURL in breve, even though our wrapper function is actually called breveDownloadURL. The function will return an AT_STRING; it will take an AT_STRING and an AT_INT.
stNewSteveCall(data, "downloadURL", breveDownloadURL, AT_STRING, AT_STRING, AT_INT, 0);The return value of the call is an integer indicating whether the load succeeded or not. A value of 0 indicates that the function was successfully defined. Any other value indicates that an error occurred. The most common error is that an internal function is already defined.
Step 3: write a class which provides a class based interface to your breve C-style functions
The next step is to write a .tz class which interfaces with your internal functions to provide an object-oriented interface to the module.The first thing you'll need in this .tz file is a @plugin line which will load the plugin code. It looks like this:
@plugin "pluginName.o" (pluginEntryFunction)."pluginName.o" is the name of the object file or shared library you're going to load--you may not know the name yet, and it can be different under Mac OS X and Linux, so you may need to come back and change this line after completing step 4 below.
"pluginEntryFunction" will be the name of the name of the entry point function written in step 2.
After the plugin line, you'll define a breve class as usual, in which the internal functions in your plugin are called from steve just like functions in C.
The idea behind wrapping your C-style calls in an object is to hide the specifics of the library behind an object or objects. If your library needs to hold "state" information between calls that cannot be easily passed back and forth between your steve object and your library, create the data structures in your library, and pass a pointer back and forth when required.
To return to the example of the downloadURL function we've defined, we might wrap it inside of an object called URLDownloader, and inside that object, we might define a method like this:
+ to download url theURL (string) with-timeout timeout (int): downloadURL(theURL, timeout).With this approach, the breve user never sees the "downloadURL" internal function--they only deal with the "download" method.
Step 4: build the breve plugin
MAC OS X USERS:Under Mac OS X, you will link the plugin against the breve binary. First you'll need to determine the full path of the breve binary. It will be the location of the breve bundle, plus the text "Contents/MacOS/breve" on the end.
For example, if you have your breve.app installed in /Applications, then the path will be /Applications/breve.app/Contents/MacOS/breve
You'll use this path with the -bundle_loader flag. You should add the text "-bundle_loader
" to the end of the command line. For a simple plugin which doesn't use any other libraries, the command would look something like this:
cc -bundle -o myPlugin.o ./myPluginFuncs.o -bundle_loader /Applications/breve.app/Contents/MacOS/breveLINUX USERS:Under Linux, you should build your plugin as a shared library. The command looks something like this:
ld -shared -o myPlugin.so.1.0 ./myPluginFuncs.o
And now you're done! Simply by loading this .tz file into your simulation, the plugin will automatically load. Make sure that you have the path of both the .tz file and the plugin binary file specified using "@path" statements if necessary.
2/22/2003 jon klein <jklein@spiderland.org>