Navigation: Appendix B: Dolphin Pattern Book > External Interfacing Patterns >
Software systems do not exist in isolation, but need to interface with other systems and make use of pre-existing software components and services. Commonly interfaces to external system, services, and components are supplied in shared libraries (DLLs in Windows). How does one go about calling such services from Smalltalk in a regular way?
Represent each shared library with a class which manages loading and accessing that library and which includes a method for each function of the library one wishes to call. Each external library class then has a singleton instance which is sent messages to invoke the external functions. This scheme fits neatly into the Smalltalk message passing paradigm, and allows for polymorphic behaviour between libraries too.
Creating the Library Class
|1.||Create a New Class that is a subclass of ExternalLibrary. The name of the new class is usually formed from the stem of the DLL name (minus any version, or "bit" suffix) plus the suffix Library; for example, ODBCLibrary might be a suitable name for a class to represent the ODBC32.DLL library.|
|2.||Implement the class method #fileName to answer the path of the library (or just the filename if you want the system to search the path when loading the library as described in the Win32 help for LoadLibrary()). Do not include the ".DLL" extension.|
Add Methods for Each Function
|1.||Implement a New Method on the instance side of the library class for each of the functions exported by the library which you wish to call. Formulate the selectors by following the External Method Selector pattern.|
|2.||Where ANSI and wide (Unicode) character versions of functions exist, the former version should be used because Dolphin does not support Unicode natively at present. For example, we use LoadLibraryA() rather than LoadLibraryW(). Where only a Unicode version exists, explicit conversion to/from Unicode strings may be required. This normally only affects functions with string arguments, though there are some exceptions.|
|3.||Quote any function names which are not valid literal Symbols.|
|4.||The implementation of external library calls has a syntax similar to primitive calls, with primitive: being replaced by the calling convention (usually stdcall: or cdecl:) and with return type symbol, function name (or ordinal), and parameter type symbols following, There are a fixed set of type symbols which correspond to types with which Win32™ programmers will be largely familiar (e.g. dword for 32-bit unsigned integer values, lpvoid for pointers etc). One type, void, is valid only as a return type and some types, e.g. lppvoid, are valid only as argument types. The primitive is normally followed by an #invalidCall message to perform error reporting.|
See the chapter on External Interfacing for more details.
|5.||You may find it convenient to implement helper methods for any additional behaviour required for the library, or to wrap the external methods to make them simpler to use from a Smalltalk programmer's perspective.|
Using External Libraries
|1.||Send messages with the appropriate selectors and arguments to the #default instance of the library class, which is lazily created as required. Objects can be normally converted to appropriate forms for passing to external functions by sending them the #asParameter message. Explicit conversion is frequently not required (e.g. strings can be passed directly to lpstr and lpvoid parameter types) but is not harmful if performed unnecessarily.|
The automatic conversions performed by the external call primitive are described in the chapter on External Interfacing .
|2.||You may wish to close libraries explicitly when they are no longer required to unload them from memory. This is not strictly necessary as Dolphin (and Win32) will do this for you on program termination.|
WinMMLibrary is the representative of WinMM.DLL, the Windows multimedia extensions library. Its class #filename method is simply:
"Answer the file name of the external library which the receiver represents."
We can play sounds via WinMMLibrary if we implement the PlaySound() functions:
playSound: aString hmod: anExternalHandle fdwSound: anInteger
"Plays a sound specified by the given filename, resource, or system event.
A system event may be associated with a sound in the registry.
Answers whether successful.
BOOL PlaySound(LPCSTR pszSound, HMODULE hmod, DWORD fdwSound);"
<stdcall: bool PlaySoundA lpvoid handle dword>
As you can see there is a simple and fairly direct mapping between the function prototype and the Dolphin external call specification.
Class Sound has a simple wrapper for this method:
"Private - Play the receiver with the specified flags.
Answer whether it succeeded."
^WinMMLibrary default playSound: name hmod: location fdwSound: flags
Sound then provides even simpler messages, such as:
"Play the receiver, waiting for the woof to finish"
| flags |
flags := (type bitAnd: ##(SND_ASYNC bitInvert)) bitOr: SND_SYNC.
self play: flags
Any particular external library may contain a large number of functions. Implementing them all may consume quite a lot of memory in unused methods, and may take a lot of time. It is advisable to implement the function calling methods as needed and employ a pattern such as External Method Selector to avoid unnecessary duplication.
Where external functions capture the addresses of objects passed to them, those objects must be allocated from the fixed memory space, as otherwise they may be moved by the memory manager during a garbage collection, invalidating the address. The fixed memory space carries more overhead than the normal object spaces.
Where external functions capture the addresses of objects passed to them, a reference to those objects must be maintained in the image to prevent them from being garbage collected while the external function is still using them.
External interfaces frequently define structured data types, and these must be defined in Dolphin too if the interface is to be used successfully. Please see External Interfacing for more details.
There are quite a few examples of external library interface classes in the base system, for example the core set of Windows libraries all have representatives (KernelLibrary, UserLibrary, GDILibrary), and these contain large numbers of functions. In fact Dolphin performs almost all interfacing with the operating system directly from Smalltalk; very little is hidden in primitives.