Index: src/OFPlugin.h ================================================================== --- src/OFPlugin.h +++ src/OFPlugin.h @@ -18,58 +18,64 @@ @class OFString; #ifndef OF_WINDOWS # include typedef void *OFPluginHandle; - -typedef enum { - OFDLOpenFlagLazy = RTLD_LAZY, - OFDLOpenFlagNow = RTLD_NOW -} OFDLOpenFlags; #else # include typedef HMODULE OFPluginHandle; - -typedef enum { - OFDLOpenFlagLazy = 0, - OFDLOpenFlagNow = 0 -} OFDLOpenFlags; #endif OF_ASSUME_NONNULL_BEGIN /** * @class OFPlugin OFPlugin.h ObjFW/OFPlugin.h * - * @brief Provides a system for loading plugins at runtime. + * @brief A class representing a loaded plugin (shared library). * - * A plugin must subclass @ref OFPlugin and have a global function called - * `OFPluginInit`, which returns an instance of the @ref OFPlugin subclass and - * takes no parameters. */ +OF_SUBCLASSING_RESTRICTED @interface OFPlugin: OFObject { - OFPluginHandle _pluginHandle; - OF_RESERVE_IVARS(OFPlugin, 4) + OFPluginHandle _handle; } /** - * @brief Loads a plugin from a file. - * - * @param path Path to the plugin file. The suffix is appended automatically. - * @return The loaded plugin - */ -+ (OF_KINDOF(OFPlugin *))pluginWithPath: (OFString *)path; -@end - -#ifdef __cplusplus -extern "C" { -#endif -extern OFPluginHandle OFDLOpen(OFString *path, OFDLOpenFlags flags); -extern void *OFDLSym(OFPluginHandle handle, const char *symbol); -extern OFString *_Nullable OFDLError(void); -extern void OFDLClose(OFPluginHandle handle); -#ifdef __cplusplus -} -#endif + * @brief Returns the plugin path for a plugin with the specified name. + * + * E.g. on ELF systems, it appends .so, while on macOS and iOS, it creates the + * appropriate plugin path. This can also be prefixed by a directory. + * + * @param name The name to return the plugin path for + * @return The plugin path + */ ++ (OFString *)pathForName: (OFString *)name; + +/** + * @brief Creates a new OFPlugin by loading the plugin with the specified path. + * + * @param path The path to the plugin file. The suffix is appended + * automatically. + * @return An new, autoreleased OFPlugin + */ ++ (instancetype)pluginWithPath: (OFString *)path; + +/** + * @brief Initializes an already allocated OFPlugin by loading the plugin with + * the specified path. + * + * @param path The path to the plugin file. The suffix is appended + * automatically. + * @return An initialized OFPlugin + */ +- (instancetype)initWithPath: (OFString *)path; + +/** + * @brief Returns the address for the specified symbol, or `nil` if not found. + * + * @param symbol The symbol to return the address for + * @return The address for the speccified symbol, or `nil` if not found + */ +- (nullable void *)addressForSymbol: (OFString *)symbol; +@end OF_ASSUME_NONNULL_END Index: src/OFPlugin.m ================================================================== --- src/OFPlugin.m +++ src/OFPlugin.m @@ -28,116 +28,90 @@ #import "OFSystemInfo.h" #import "OFInitializationFailedException.h" #import "OFLoadPluginFailedException.h" -typedef OFPlugin *(*PluginInit)(void); - -OFPluginHandle -OFDLOpen(OFString *path, OFDLOpenFlags flags) -{ -#ifndef OF_WINDOWS - return dlopen([path cStringWithEncoding: [OFLocale encoding]], flags); -#else - if (path == nil) - return GetModuleHandle(NULL); - - if ([OFSystemInfo isWindowsNT]) - return LoadLibraryW(path.UTF16String); - else - return LoadLibraryA( - [path cStringWithEncoding: [OFLocale encoding]]); -#endif -} - -void * -OFDLSym(OFPluginHandle handle, const char *symbol) -{ -#ifndef OF_WINDOWS - return dlsym(handle, symbol); -#else - return (void *)(uintptr_t)GetProcAddress(handle, symbol); -#endif -} - -void -OFDLClose(OFPluginHandle handle) -{ -#ifndef OF_WINDOWS - dlclose(handle); -#else - FreeLibrary(handle); -#endif -} - -OFString * -OFDLError(void) -{ -#ifndef OF_WINDOWS - return [OFString stringWithCString: dlerror() - encoding: [OFLocale encoding]]; -#else - return nil; -#endif -} - -@implementation OFPlugin -+ (id)pluginWithPath: (OFString *)path -{ - void *pool = objc_autoreleasePoolPush(); - OFPluginHandle handle; - PluginInit initPlugin; - OFPlugin *plugin; - -#if defined(OF_MACOS) - path = [path stringByAppendingFormat: @".bundle/Contents/MacOS/%@", - path.lastPathComponent]; -#elif defined(OF_IOS) - path = [path stringByAppendingFormat: @".bundle/%@", - path.lastPathComponent]; -#else - path = [path stringByAppendingString: @PLUGIN_SUFFIX]; -#endif - - if ((handle = OFDLOpen(path, OFDLOpenFlagLazy)) == NULL) - @throw [OFLoadPluginFailedException - exceptionWithPath: path - error: OFDLError()]; - - objc_autoreleasePoolPop(pool); - - initPlugin = (PluginInit)(uintptr_t)OFDLSym(handle, "OFPluginInit"); - if (initPlugin == (PluginInit)0 || (plugin = initPlugin()) == nil) { - OFDLClose(handle); - @throw [OFInitializationFailedException - exceptionWithClass: self]; - } - - plugin->_pluginHandle = handle; - return plugin; -} - -- (instancetype)init -{ - if ([self isMemberOfClass: [OFPlugin class]]) { - @try { - [self doesNotRecognizeSelector: _cmd]; - } @catch (id e) { - [self release]; - @throw e; - } - - abort(); - } - - return [super init]; +#ifndef RTLD_LAZY +# define RTLD_LAZY 0 +#endif + +@implementation OFPlugin ++ (instancetype)pluginWithPath: (OFString *)path +{ + return [[[self alloc] initWithPath: path] autorelease]; +} + ++ (OFString *)pathForName: (OFString *)name +{ +#if defined(OF_MACOS) + return [name stringByAppendingFormat: @".bundle/Contents/MacOS/%@", + name.lastPathComponent]; +#elif defined(OF_IOS) + return [name stringByAppendingFormat: @".bundle/%@", + name.lastPathComponent]; +#else + return [name stringByAppendingString: @PLUGIN_SUFFIX]; +#endif +} + +- (instancetype)initWithPath: (OFString *)path +{ + self = [super init]; + + @try { + void *pool = objc_autoreleasePoolPush(); + +#ifndef OF_WINDOWS + _handle = dlopen( + [path cStringWithEncoding: [OFLocale encoding]], RTLD_LAZY); +#else + if ([OFSystemInfo isWindowsNT]) + _handle = LoadLibraryW(path.UTF16String); + else + _handle LoadLibraryA( + [path cStringWithEncoding: [OFLocale encoding]]); +#endif + + if (_handle == NULL) { +#ifndef OF_WINDOWS + OFString *error = [OFString + stringWithCString: dlerror() + encoding: [OFLocale encoding]]; +#else + OFString *error = nil; +#endif + @throw [OFLoadPluginFailedException + exceptionWithPath: path + error: error]; + } + + objc_autoreleasePoolPop(pool); + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void *)addressForSymbol: (OFString *)symbol +{ +#ifndef OF_WINDOWS + return dlsym(_handle, + [symbol cStringWithEncoding: [OFLocale encoding]]); +#else + return (void *)(uintptr_t)GetProcAddress(_handle, + [symbol cStringWithEncoding: [OFLocale encoding]]); +#endif } - (void)dealloc { - OFPluginHandle h = _pluginHandle; +#ifndef OF_WINDOWS + dlclose(_handle); +#else + FreeLibrary(_handle); +#endif [super dealloc]; - - OFDLClose(h); } @end Index: tests/OFPluginTests.m ================================================================== --- tests/OFPluginTests.m +++ tests/OFPluginTests.m @@ -18,26 +18,38 @@ #import "TestsAppDelegate.h" #import "plugin/TestPlugin.h" #ifndef OF_IOS -static OFString *const pluginPath = @"plugin/TestPlugin"; +static OFString *const pluginName = @"plugin/TestPlugin"; #else -static OFString *const pluginPath = @"PlugIns/TestPlugin"; +static OFString *const pluginName = @"PlugIns/TestPlugin"; #endif static OFString *const module = @"OFPlugin"; @implementation TestsAppDelegate (OFPluginTests) - (void)pluginTests { void *pool = objc_autoreleasePoolPush(); - TestPlugin *plugin; + OFString *path; + OFPlugin *plugin; + Class (*class)(void); + TestPlugin *test; + + TEST(@"+[pathForName:]", (path = [OFPlugin pathForName: pluginName])) + + TEST(@"+[pluginWithPath:]", (plugin = [OFPlugin pluginWithPath: path])) - TEST(@"+[pluginWithPath:]", - (plugin = [OFPlugin pluginWithPath: pluginPath])) + TEST(@"-[addressForSymbol:]", + (class = (Class (*)(void))[plugin addressForSymbol: @"class"])) - TEST(@"TestPlugin's -[test:]", [plugin test: 1234] == 2468) + test = [[class() alloc] init]; + @try { + TEST(@"TestPlugin's -[test:]", [test test: 1234] == 2468) + } @finally { + [test release]; + } objc_autoreleasePoolPop(pool); } @end Index: tests/plugin/TestPlugin.h ================================================================== --- tests/plugin/TestPlugin.h +++ tests/plugin/TestPlugin.h @@ -11,10 +11,10 @@ * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ -#import "OFPlugin.h" +#import "OFObject.h" -@interface TestPlugin: OFPlugin +@interface TestPlugin: OFObject - (int)test: (int)num; @end Index: tests/plugin/TestPlugin.m ================================================================== --- tests/plugin/TestPlugin.m +++ tests/plugin/TestPlugin.m @@ -42,10 +42,10 @@ { return num * 2; } @end -id -OFPluginInit(void) +Class +class(void) { - return [[[TestPlugin alloc] init] autorelease]; + return [TestPlugin class]; }