Pattern: External Library


Context

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™). A means of calling the functions in such shared libraries from Dolphin is required.

Solution

Represent each shared library with a Dolphin 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 has a singleton instance which is then 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.

  1. Create a new subclass of ExternalLibrary (New Class). 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.
  3. Implement instance methods for each of the functions exported by the library which you wish to call. Construct the selectors by following External Method Selectors.
  4. 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.
  5. Quote any function names which are not valid literal Symbols.
  6. Add further instance variables to the new ExternalLibrary subclass to implement additional required state for the library (if any).
  7. Implement additional methods for any additional behaviour required for the library, or to wrap the external methods (e.g. for simplicity of use).
  8. 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 and passing structures by reference, and bool for passing and returning boolean values). One type, void, is valid only as return type (i.e. answer the receiver), 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 example, and External Interfacing for further details.
  9. Send messages with the appropriate selectors and arguments to the #default instance of the class, which is lazily created as required. Objects can be converted to appropriate forms for passing to external functions by sending them the #asParameter message. 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 External Interfacing.
  10. Close libraries explicitly when no longer required to unload them from memory, though this is not entirely necessary as Dolphin (and Win32) will do this for you on program termination.

Example

WinMMLibrary is the representative of WinMM.DLL, the Windows™ multimedia extensions library. Its class #filename method is simply:

fileName
	"Answer the file name of the external library which the receiver represents."

	^'WinMM'

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>
	^self invalidCall

As you can see there is a simple and fairly direct mapping between the function prototype and the Dolphin external call specification.

Sound has a simple wrapper for this method:

play: flags
	"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:

woofAndWait
	"Play the receiver, waiting for the woof to finish"

	| flags |
	flags := (type bitAnd: ##(SND_ASYNC bitInvert)) bitOr: SND_SYNC.
	self play: flags

Consequences

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 as needed and employ a naming convention to avoid 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 (see External Interfacing).

Known Uses

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.