To write external modules it is a good practice to learn first how ScriptBasic uses the modules.
External functions and external commands in ScriptBasic are declared using the declare sub or declare command statements. An example of such a statement is
declare sub alma alias "trial" lib "ext_tial"
declare command iff alias "iff" lib "ext_tial"
Following this declaration the function or the command can be used just as it were implemented in BASIC.
The difference between external functions and external commands is the way ScriptBasic handles the arguments passed to them. Both external functions and external commands are implemented as C functions in the extension module compiled into a DLL or shareable object. Both of them look like user defined functions in the BASIC source code.
The difference is that external functions are called after the actual arguments are evaluated, while external commands are called without evaluating the arguments. Because of this external functions and external commands are implemented in C functions that have different prototypes. There is a prototype for external functions and a different one for external commands.
When ScriptBasic compiles this line the function or subroutine alma is defined just as a normal function or subroutine defined using the instructions of the basic language. Note that there are no differences other than syntax between subroutines and functions in ScriptBasic. When the program calls the function alma the ScriptBasic run-time system performs a function call to the basic function alma. In other words there is no difference from the caller point of view between the line above and the line:
The function call can be performed in two different ways. One way is when the function appears in an expression. The other way is when the function is called using the call statement. There is no difference between the two calling possibilities from the internal operation point of view of the interpreter. This is because CALL statement is implemented in a very simple way to simply evaluate the expression after the call statement and drop the result.
The calling code does not evaluate the expressions passed to the function as arguments. This is usually the task of the functions. The functions get the node pointer to the expression list where the actual values for the arguments are and they can evaluate them.
The two different declarations declare sub and declare command differ in the way ScriptBasic interpreter handles the arguments. When an external function is declared using the command declare sub the arguments are evaluated by the interpreter before the function implemented in the external module is called. When an external command is declared using the command declare command the arguments are NOT evaluated by the ScriptBasic interpreter before calling the function implemented in the external module. In the latter case the external function has to decide if it wants certain arguments to be evaluated and can call the ScriptBasic function execute_Evaluate via the extension call-back table to evaluate the arguments. Also the prototype of a function declared using the statement declare command is different from the prototype of a function declared using the command declare sub.
When a function is implemented externally ScriptBasic sees a declare sub statement instead of a function or sub statement and starts to execute this statement calling the code implemented in the file `external.c' in the source directory `commands'.
The name of the example function is alma as declared in the statement above. However this is only a symbolic name that exists only during syntax analysis and is not available when the code is executed. The alias for the function is trial. This is the name of the function as it is implemented in the external module. When the interpreter executes this line the function name trial is used in the system call to locate the entry point. The module that contains the function is ext_trial. The actual file name is `ext_trial.dll' or `ext_trial.so' or some other name containing the given name and an extension specific to the operating system. During module load ScriptBasic automatically appends the appropriate extension as defined in the configuration file of ScriptBasic. (For further information on ScriptBasic configuration file syntax and location see the ScriptBasic Users' Guide!) ScriptBasic searches the module in the directories defined in the configuration file and tries to load it using absolute file name. This way the system specific search paths are not taken into account.
When function implemented in an external module is first called the interpreter checks if the module is loaded or not. If the module is not loaded the interpreter loads the module and calls module initialization functions. If the module was already loaded it locates the entry point of the function and calls the function.
During module load ScriptBasic appends the appropriate dynamic load library extension and tries to load the module from the directories defined in the configuration file. It takes the directories in the order they are specified in the configuration file and in case it can load the module from a directory listed it stops trying.
When the module is loaded ScriptBasic locates the function versmodu and calls it. The task of this function is to negotiate the interface version between the external module and ScriptBasic. The current interface version is defined in the file `basext.c' with the C macro INTERFACE_VERSION. ScriptBasic calls this function to tell the module what version ScriptBasic supports. The function can decide if the module can work with the indicated version and can answer: yes it is OK, no it is not OK or yes, but I can support only version X. This is a negotiation process that finally result some agreement or the module is abandoned if no agreement can be reached.
The function versmodu gets three arguments:
int versmodu(int Version, char *pszVariation, void **ppModuleInternal)
The first argument is the version of the interface. The second argument is the ZCHAR terminated 8-character string of the variation. This is “STANDARD” for the standard, stand alone, command line version of ScriptBasic. The ppModuleInternal pointer points to the module pointer initialized to NULL. This pointer is hardly ever used in this function, but its address is passed as a third argument in case some application needs it. The role of this pointer will be discussed later.
The function should check the parameters passed and return either zero in case it can not accept the interface or the highest interface it can handle. If this is the same as the version passed in the first argument the module should be accepted. If this is smaller than the interface version offered by ScriptBasic the interpreter can decide if it can support the older interface required by the module.
If the function versmodu returns a version larger than the version offered the interpreter will interpret this as a negotiation failure and will treat the module as not loaded.
If there is no function named versmodu in the library ScriptBasic crosses the fingers and hopes the best and assumes that the module will be able to work with the interface ScriptBasic offers. (should we change it to be configurable to disallow such modules?)
After the successful version negotiation the interpreter calls the function named bootmodu. This function gets four arguments.
int bootmodu(pSupportTable pSt,
int bootmodu(pSupportTable pSt, void **ppModuleInternal, pFixSizeMemoryObject pParameters, pFixSizeMemoryObject *pReturnValue)
The first parameter is a pointer to the interface structure. This interface structure can and should be used to communicate with ScriptBasic. The second parameter points to the module pointer. The last two parameters are NULL for this function. The reason to pass two NULL pointers is that this is the prototype of each function callable by ScriptBasic implemented in the module. The last two parameters point to the parameters of the function and to the left value where the function value is to be returned. bootmodu actually does not get any parameter and should not pass any value back.
This function can be used to initialize the module, to allocate memory for the common storage if the functions implemented in the module keep some state information. If there is no function named bootmodu in the library file ScriptBasic assumes that the module does not need initialization. If the function bootmodu exists it should return zero indicating success or an error code. If an error code is returned the module is treated as failed. And an error is raised. (Errors can be captured using the BASIC ON ERROR GOTO command.
When this function returns the ScriptBasic interpreter evaluates the arguments and performs a call to the function named trial in our example.
When the program has finished the interpreter tries to locate the function finimodu in the module. This function may exist and should have the same prototype as any other function (except versmodu):
int finimodu(pSupportTable pSt,
This function can be used to perform clean-up tasks. The interpreter may call the finimodu functions of different modules in different threads asynchronously. (However it does not currently.)
Note that there is no need to release the allocated memory in case the module allocates memory using the memory allocation methods provided by the interface. Other resources may need release; for example files may need closing.