-
Notifications
You must be signed in to change notification settings - Fork 12
DerelictUtil for Users
There are two modules of which users of any binding built on top of DerelictUtil should be aware. The first is derelict.util.exception. The second is derelict.util.loader.
###Exceptions Derelict's custom exceptions reside in the derelict.util.exception module. The root exception is DerelictException. When calling the load method of any loader derived from SharedLibLoader, this root exception can be caught to indicate that the library failed to load.
For more specific exception handling, there are two subclasses of DerelictException.
The first, SharedLibLoadException is thrown when a loader fails to load a shared library from the file system. This is usually due to one or two reasons. The most common reason is that the shared library is not on the system path. By default, the loaders search the default system path, which varies across operating systems. Loaders also allow users to specify an absolute path to a shared library. If this exception is thrown in this case, it means the library was not present at that path.
The second exception is SymbolLoadException. When this exception is thrown, it indicates that the shared library was successfully loaded into the application's address space, but an expected function symbol was not found in the library. This exception is commonly seen when attempting to load a shared library of a version that does not match that against which the binding was created. For example, a binding might be created for the library libFoo 1.2. Perhaps libFoo 1.1 is on the system, but that version of the library is missing or two functions that were added in 1.2. When the loader attempts to find one of those functions in the library, it discovers the function is missing and throws a SymbolLoadException. A similar case happens if a newer version of a library has been released that removes on or more functions.
Normally, it is not necessary to distinguish between SharedLibLoadExceptions and SymbolLoadExceptions in code. Often, it is enough to catch the generic Exception, but in cases where a distinction is necessary, then DerelictException should serve the purpose. The error messages contained in each type of exception provide enough detail to distinguish the difference. The two subclasses exist solely for those rate cases where it might be beneficial to catch them separately.
DerelictUtil also provides a mechanism that I call selective symbol loading. This allows a user to prevent a SymbolLoadException from being thrown in specific cases. For instance, in the libFoo example above, where the binding was made for 1.2, but the user might have 1.1 installed, selective symbol loading can be used to ignore the new 1.2 functions that are missing from the 1.1 version.
The module derelict.util.exception declares two type aliases, MissingSymbolCallbackFunc and MissingSymbolCallbackDg, each that take a single string parameter that is the name of a missing symbol. The former is a function pointer, the latter a delegate. A missing symbol callback of either type can be set on any instance of SharedLibLoader via its missingSymbolCallbck property, passing either a function pointer or a delegate as the sole parameter. When a missing symbol is encountered, the loader will call the callback if one has been set. If the callback returns ShouldThrow.No, the symbol will be ignored and no exception will be thrown. Conversely, returning ShouldThrow.Yes causes the exception to be thrown. An example follows.
ShouldThrow missingSymFunc( string symName ) {
// Assume getBar is a function added in libFoo 1.2. Our application does not
// use getBar, so libFoo 1.1 is acceptable. So if we find that getBar is missing,
// don't throw. This will allow version 1.1 to load.
if( symName == "getBar") {
return ShouldThrow.No;
}
// Any other missing symbol should throw.
return ShouldThrow.Yes;
}
void loadLibFoo() {
// First, set the missing symbol callback.
DerelictFoo.missingSymbolCallback = &missingSymFunc;
// Load libFoo
DerelictFoo.load();
}
###Loading
Although it is possible to bind a shared library using Derelict's low-level shared library API, it is not useful to expose such a binding to end users. Instead, anyone using a binding built on DerelictUtil should always be interfacing with the loader API as defined in the module derelict.util.loader.
The module declares a single class, SharedLibLoader, which exposes a handful of methods to load and unload shared libraries. Bindings typically subclass this class to create a custom loader for a specific library. Users can then call public methods of the SharedLibLoader class on instances of the subclass. Bindings in DerelictOrg all follow the convention that loader instances are exposed globally, shared across threads, and are instantiated in static module constructors. So using a loader for a hypothetical libFoo might look like this.
import derelict.foo.foo;
void loadFoo() {
DerelictFoo.load();
}
void load() - this method is the version of load that is most commonly called. Each instance of SharedLibLoader should have one or more default, platform-specific shared library names that it searches for. For example, "libFoo.dll" on Windows and "libFoo.so, libFoo-1.2.so" on Linux. Calling the parameterless version of load will cause the loader to attempt to load the library using the default name(s). If the library fails to load, a DerelictException, either SharedLibLoadException or SymbolLoadException, will be thrown.
try {
DerelictFoo.load();
} catch( DerelictException de ) {
Log.writefln( "Failed to load libFoo: %s", de.msg );
}
void load( string names ) - this version of load takes a string containing one or more shared library names. If multiple names are passed, they should be separated by commas (spaces following the commas may be included for readability, but are not required). The loader will attempt to find each library name in the string, from left to right, until either one of them is opened successfully or all of them have failed to open. In the case of total failure, a DerelictException will be thrown. It will be the head of an exception chain containing one instance of SharedLibLoadException for each library that failed to load. The error message for each exception can be read by following the next field of each exception. Once one library is found and opened, loading may fail if an expected symbol is missing from the library. In this case, no attempt will be made to use any remaining library names in the names string. Instead, SymbolLoadException may be thrown. Note that the same exception behavior can apply to the parameterless version of load if a binding attempts to load multiple library names by default.
try {
// This attempts to load first from an app subdirectory, then from the global search path.
DerelictFoo.load( "/sharedLibs/libFoo.dll, libFoo.dll" );
} catch( SymbolLoadException sle ) {
Log.writefln( "libFoo is missing a symbol: %s", sle.msg );
} catch( SharedLibLoadException slle ) {
Log.writeln( "Failed to load libFoo: " );
Log.writeln( "\t%s", slle.msg );
Log.writeln( "\t%s", slle.next.msg );
}
void load( string[] names ) - this version of load behaves the same as the one above, the only difference is that one or more library names are passed as an array rather than as a single, comma-separated string.
void unload() - a shared library can be unloaded at any time by calling this method. All bindings under the DerelictOrg umbrella do this automatically via module destructors when an app exits. Be aware that all function pointers that were assigned a value via a load method from the same object on which unload is called will no longer be valid. Attempting to call one of them will result in the application crashing, usually with an Access Error.
bool isLoaded() - returns true if a shared library is currently loaded, and false otherwise. This is very useful for cleaning up resources in a C library as an application is shutting down.
// If a library has some sort of cleanup or termination function, guarding it with a call
// to isLoaded will ensure that it isn't called if the library isn't loaded Doing so can cause
// an access violation, since function calls in a dynamic binding are made through function pointers.
if( DerelictFoo.isLoaded ) {
fooShutdown();
}
void missingSymbolCallback( MissingSymbolCallbackDg callback ) - sets the callback to be called in the case that a symbol is missing from a shared library. The single parameter is a delegate. See the section on Selective Symbol Loading above for details.
void missingSymbolCallback( MissingSymbolCallbackFunc callback ) - same as above, except that the parameter is a function pointer.