Within a GemStone Smalltalk-based application, you may choose to write a C function for certain operations, rather than to perform the work in GemStone. For example, operations that are computationally intensive can be written as C functions and called from within a Smalltalk method (whose high-level structure and control is written in Smalltalk). This approach is similar to the concept of “user-defined primitives”.
This chapter describes how to implement C user action functions that can be called from GemStone, and how to call those functions from a GemBuilder application or a Gem (GemStone session) process.
Some of the functionality provided by User Action libraries can instead be supported using the Foreign Function Interface (FFI) in GemStone Smalltalk, which allows you to invoke functions in C libraries directly from Smalltalk, without compiling C code. The FFI is described in Programming Guide.
Although user actions can be linked directly into an application, they are usually placed in shared libraries so they can be loaded dynamically. The contents of a library are not copied into the executable. Instead, the library’s main function loads all of its user actions. Only one copy is loaded into memory, even if multiple client processes use the library.
User action libraries are used in two ways: They can be application user actions, which are loaded by the application process, or session user actions, which are loaded by the Gem session process. The libraries themselves are the same for both, it is the operation that is used to load the library determines which type it is.
Application user actions are the traditional GemStone user actions. They are used by the application for communication with the Gem or for an interactive interface to the user.
Session user actions add new functionality to the Gem, something like the traditional custom Gem. The difference here is that you only need one Gem, which can customize itself at run time. It loads the appropriate libraries for the code it is running. The decisions are made automatically within GemStone Smalltalk, rather than requiring the users to decide what Gem they need before they start their session.
User Actions are invoked from GemStone Smalltalk. This can be done from GemStone Smalltalk application code, topaz execution, etc, or using GemBuilder for C by calling a function such as GciExecuteStr or GciPerform.
Before it can be used, the user action library must be loaded and initialized; see Loading User Actions.
1. GemStone Smalltalk code invokes a user action function (written in C) by sending a message of the form:
System userAction: aSymbol with: args
The args arguments are passed to the C user action function named aSymbol.
2. The C user action function can call any GemBuilder functions and any C functions provided in the application or the libraries loaded by the application (for application user actions), or provided in the libraries loaded by the Gem (for session user actions).
If a GemBuilder or other GemStone error is encountered during execution of the user action, control is returned to the Gem or your GemBuilder application as if the error had occurred during the call to execute the code.
3. The C user action function must return an OopType as the function result, and must return control directly to the Smalltalk method from which it was called.
NOTE:
Results are unpredictable if the C function uses GCI_LONGJMP instead of returning control to the GemStone Smalltalk virtual machine.
For your GemStone application to take advantage of user action functions, you do the following:
Step 1. Determine which operations to perform in C user action functions rather than in Smalltalk, and write the user action functions.
Step 2. Create a user action library to package the functions.
Step 3. Provide the code to load the user action library.
If the application is to load the library, add the loading code to your application.
If the session is to load the library, use the GemStone Smalltalk method System class>>loadUserActionLibrary: for loading.
Step 4. Write the Smalltalk code that calls your user action. Commit it to your GemStone repository.
Step 5. Debug your user action.
The following sections describe each of these steps.
Writing a C function to install as a user action called from Smalltalk is much the same as writing other C functions. However, since the application that called the user action (whether written in C, Java, or Smalltalk) controls the export set—the set of OOPs to save after execution completes—user actions do not solely control the way objects are preserved from garbage collection.
For more on the issue of retaining references to newly created objects and risk of premature garbage collection, see the discussion underGarbage Collection.
To enable objects to be retained, user actions maintain a separate export set that is specific to the user action, in addition to the PureExportSet. When objects are automatically added to an export set or added using GciSaveObjs, they are put into the user action’s export set rather than into the PureExportSet.
When the user action function completes, its export set is cleared. Don’t save the OOPs in static C variables for use by a subsequent invocation of the user action or by another C function.
To make a newly created object a permanent part of the GemStone repository, the user action has two options:
To create a reference that prevents garbage collection but is not shared between sessions, you may use GemStone Smalltalk code to add them to the user-definable portion of System sessionState using System >> sessionStateAt:put:. Objects referenced in this way will be preserved until the session terminates, at which point they will be subject to garbage collection if there are no other references.
Whether you have one user action or many, the way in which you prepare and package the source code for execution has significant effects upon what uses you can make of user actions at run time. It is important to visualize your intended execution configurations as you design the way in which you package your user actions.
To build a user action library:
1. Include gciua.hf in your C source code.
2. Define the initialization and shutdown functions.
3. Compile with the appropriate options, as described in Chapter 5.
4. Link with gciualib.o and the appropriate options, as described in Chapter 5.
5. Install the library in the $GEMSTONE/ualib directory.
User action libraries must always include the gciua.hf file, rather than the gci.hf or gcirtl.hf file. Using the wrong file causes unpredictable results.
A user action library must define the initialization function GciUserActionInit and the shutdown function GciUserActionShutdown.
Do not call GciInit, GciLogin, or GciLogout within a user action.
Example 4.1 shows how the initialization function GciUserActionInit is defined. You must call GCI_DECLARE_ACTION or GciDeclareAction once for each function in the set of user actions.
static OopType doParse(void)
{
return OOP_NIL;
}
static OopType doFetch(void)
{
return OOP_NIL;
}
extern "C" void GciUserActionInit(void)
{
GCI_DECLARE_ACTION("doParse", doParse, 1);
GCI_DECLARE_ACTION("doFetch", doFetch, 1);
// ...
}
GCI_DECLARE_ACTION associates the Smalltalk name of the user action function userActionName (a C string) with the C address of that function, userActionFunction, and declares the number of arguments that the function takes. A call to GCI_DECLARE_ACTION looks similar to this:
GCI_DECLARE_ACTION("userActionName", userActionFunction, 1)
The function installs the user action into a table of such functions that GemBuilder maintains. Once a user action is installed, it can be called from GemStone.
The name of the user action, “userActionName”, is a case-sensitive, null-terminated string that corresponds to the symbolic name by which the function is called from Smalltalk. The name is significant to 31 characters. It is recommended that the name of the user action be the same as the C source code name for the function, userActionFunction.
The third argument to GCI_DECLARE_ACTION indicates how many arguments the C function accepts. This value should correspond to the number of arguments specified in the Smalltalk message. When it is 0, the function argument is void. Similarly, a value of 1 means one argument. The maximum number of arguments is 8. Each argument is of type OopType.
Your user action library may call GCI_DECLARE_ACTION repeatedly to install multiple C functions. Each invocation of GCI_DECLARE_ACTION must specify a unique userActionName. However, the same userActionFunction argument may be used in multiple calls to GCI_DECLARE_ACTION.
The shutdown function GciUserActionShutdown should also be defined. This function is called when the user action library is unloaded. It is provided so the user action library can clean up any system resources it has allocated. Do not make GemBuilder C calls from this function, because the session may no longer exist. GciUserActionShutdown can be left empty. Example 4.2 shows a shutdown definition that does nothing but report that it has been called.
Shared user actions are compiled for and linked into a shared library. See Chapter 5, “Compiling and Linking” for instructions.
Be sure to check the output from your link program carefully. Linking with shared libraries does not require that all entry points be resolved at link time. Those that are outside of each shared library await resolution until application execution time, or even until function invocation time. You may not find out about incorrect external references until run time.
With slight modifications, existing user action code can be used in a user action library. You need to include gciua.hf instead of gci.hf or gcirtl.hf. Define a GciUserActionShutdown and a GciUserActionInit, if these are not already present. Compile, link, and install according to the instructions for user action libraries.
GemBuilder does not support the loading of any default user action library. Applications and Gems must include code that specifically loads the libraries they require.
Dynamic run-time loading of user action libraries requires some planning to avoid name conflicts. If an executable tries to load a library with the same name as a library that has already been loaded, the operation fails.
When user actions are installed in a process, they are given a name by which GemBuilder refers to them. These names must be unique. If a user action that was already loaded has the same name as one of the user actions in the library the executable is attempting to load, the load operation fails. On the other hand, if the two libraries contain functions with the same implementation but different names, the operation succeeds.
A linked or RPC Gem process can install and execute its own user action libraries. To cause the Gem to do this, use the System class>>loadUserActionLibrary: method in your GemStone Smalltalk application code. A session user action library stays loaded until the session logs out.
The session must load its user actions after the user logs into GemStone (GciLogin). At that time, any application user actions are already loaded. If a session tries to load a library that the application has already defined, it gets an error. The loading code can be written to handle the error appropriately. Two sessions can load the same user action library without conflict.
If the application is to load a user action library, implement an application feature to load it. The GemStone interfaces provide a way to load user actions from your application.
The application must load application user actions after it initializes GemBuilder (GciInit) and before the user logs into GemStone (GciLogin). If the application attempts to install user actions after logging in, an error is returned.
When writing scripts or committing to the database, you can specify the user action library as a full path or a simple base name; it is recommended to use the base name. The code that GemBuilder uses to load a user action library expands the base name <ua> to a valid shared library name for the current platform. For example, the base name custreport would be expanded to libcustreport.so on many UNIX platforms.
The code for loading searches for the file in the following places in the specified order:
Loading user action libraries at run time is the preferred behavior for GemBuilder applications. For application user actions, however, you have the option to create the user actions directly in your C application, not as part of a library. When you implement user actions this way, include gcirtl.hf or gci.hf in your C source code, instead of gciua.hf. (Your C source code should include exactly one of these three include files.)
The GciUserActionInit and GciUserActionShutdown functions are not required, but the application must call GCI_DECLARE_ACTION once for each function in the set of user actions.
After your application has successfully logged in to GemStone (via GciLogin), it may not call GCI_DECLARE_ACTION. If your application attempts to install user actions after logging in, an error will be returned.
After logging in to GemStone, your application can test for the presence of specific user actions by sending the following Smalltalk message:
System hasUserAction: aSymbol
This method returns true if your C application has loaded the user action named aSymbol, false otherwise.
For a list of all the currently available user actions, send this message:
System userActionReport
Once your application or Gem has a way to access the user action library, your GemStone Smalltalk code invokes a user action function by sending a message to the GemStone system. The message can take one of the following forms:
System userAction: aSymbol
System userAction: aSymbol with:arg1 [with:arg2] ...
System userAction: aSymbol withArgs:anArrayOfUpTo8Args
You can use the with keyword from zero to seven times in a message. The aSymbol argument is the name of the user action function, significant to 31 characters. Each method returns the function result.
Notice that these methods allow you to pass up to eight arguments to the C user action function. If you need to pass more than eight objects to a user action, you can create a Collection (for example, an instance of Array), store the objects into the Collection, and then pass the Collection as a single argument object to the C user action function:
| myArray |
myArray := Array new: 10.
"populate myArray, then send the following message"
System userAction: #doSomething with: myArray.
From Smalltalk you can invoke a user action, and within the user action you can do a GciSend, GciPerform, or GciExecute, that may in turn invoke another user action. This kind of circular function calling is limited; no more than 2 user actions may be active at any one time on the current Smalltalk stack. If the limit is exceeded, GemStone raises an error.
Even if you intend to use your library only as session user actions, test them first as application user actions with an RPC Gem. As with applications, never debug user actions with linked versions.
CAUTION
Debug your C code in a process that does not include a Gem.
For more information, see Risk of Database Corruption.
Use the instructions for user actions in Chapter 5, “Compiling and Linking” to compile and link the user action library. Then load the user actions from the RPC version of your application or Topaz. To load from Topaz, use the loadua command.
When an error in a user actions is handled by an on:do: block in Smalltalk by default trims the stack on return from the user action. You can set the runtime configuration parameter GemDebuggerActive, to fetch the stack for the error in the user action.
User actions can be executed either in the GemBuilder application (client) process or in a Gem (server) process, or in both.
The distinction between application user actions that execute in the application and session user actions that execute in the Gem is interesting primarily when the two processes are running remotely, or when the application has more than one Gem process.
When the application and Gem run on different machines and the Gem calls an application user action, the call is made over the network. Computation is done by the application where the application user action is running, and the result is returned across the network. Using a session user action eliminates this network traffic.
On the other hand, for overall efficiency you also need to consider which machine is more suitable for execution of the user action. For example, assume that your application acquires data from somewhere and wishes to store it in GemStone. You could write a user action to create GemStone objects from the data and then store the objects. It might make more sense to execute the user action in the application process rather than transport the raw data to the Gem.
Alternatively, assume there is a GemStone object that could require processing before the application could use it, like a matrix on which you need to perform a Fast Fourier Transform (FFT). If the Gem runs on a more powerful machine than the client, you may wish to run an FFT user action in the Gem process and send the result to your application.
In most situations, session user actions are preferable, because the Gem does not have to make calls to the application. In the case of a linked application, however, an application user action is just as efficient for the linked Gem, because the Gem and application run as one process. Using an application user action guarantees that if any new sessions are created, they will have access to the same user action functions as the first session.
Every Gem can access its own session user actions and the application user actions loaded by its application. A Gem cannot access another Gem’s session user actions, however, even when the Gems belong to the same application.
Although a linked application and its first Gem run in the same process, that process can have session and application user actions, as in Figure 4.1. Application user actions, loaded by the application’s loading function, are accessible to all the Gems. Session user actions in the same process, loaded by the System class>>loadUserActionLibrary: method, are not accessible to the RPC Gem. Conversely, the RPC Gem’s user actions are not accessible to the linked Gem.
User actions can be executed in the user application process under two configurations of GemStone processes. The configurations differ depending upon whether the application is linked or RPC.
In an RPC configuration, the application runs in a separate process from any Gem. Each time the application calls a GemBuilder C function, the function uses remote procedure calls to communicate with a Gem. The remote procedure calls are used whether the Gem is running on the same machine as the application, or on another machine across the network.
The user actions run in the same process as the application. If they call GemBuilder functions, those functions also use remote procedure calls to communicate with the Gem.
In this configuration, all your code executes as a GemStone client (on the application side). It can thus execute on any GemStone client platform; it is not restricted to GemStone server platforms. Care should be taken in coding to minimize remote procedure call overhead and to avoid excessive transportation of GemStone data across the network. The following list enumerates some of the conditions in which you may find occasion to use this configuration:
NOTE:
You can run RPC Topaz as the C application in this configuration for debugging to perform unit testing of user action libraries. Apply a source-level debugger to the Topaz executable, load the libraries with the Topaz loadua command, then call the user actions directly from GemStone Smalltalk.
In a linked configuration, the application, the user actions, and one Gem all run in the same process (on the same machine). All function calls, from the application to GemBuilder and between GemBuilder and the Gem, are resolved by ordinary C-language linkage, not by remote procedure calls.
Since a Gem is required for each GemStone session, the first session uses the (linked) Gem that runs in your application process. This Gem has the advantages that it does not incur the overhead of remote procedure calls, and may not incur as much network traffic. It has the disadvantage that it must run in the same process as the Gem, so that work cannot be distributed between separate client and server processes. Since the application cannot continue processing while the Gem is at work, the non-blocking GemBuilder functions provide no benefit here.
If a linked application user logs in to GemStone more than once, GemStone creates a new RPC Gem process for each new session. If one of these sessions invokes a user action, the user action executes in the same process as the application. If the user action then calls a GemBuilder function, that call is serviced by the linked Gem, not by the Gem from which the user action was invoked.
In this configuration, your code executes only on GemStone server platforms. It cannot execute on client-only platforms because a Gem is part of the same process. The occasions for using this configuration are much the same as those for running user actions with an RPC application, except that you should not use this one for debugging.
CAUTION
Debug your user actions in a process that does not include a Gem. For more information, see Risk of Database Corruption.
Just as with applications, there are two forms of Gems: linked and RPC. The linked Gem is embedded in the gcilnk library and is only used with linked applications.
An RPC Gem executes in a separate process that can install and execute its own user actions. The RPC Gem is RPC because it communicates by means of remote procedure calls, through an RPC GemBuilder, with an application in another process.
However, it is also a separate C program. The Gem itself also uses GemBuilder directly, to interact with the database. That is the reason why the RPC Gem is linked with the gcilnk library. The user action in this configuration executes in the same process as the Gem, with the GemBuilder that does not use remote procedure calls.
CAUTION
Debug your user actions in a process that does not include a Gem. For more information, see Risk of Database Corruption.
The following list enumerates some of the conditions in which you may find occasion to use this configuration: