ObjFW  Check-in [a74bff96c4]

Overview
Comment:OFPlugin: Completely redesign API
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: a74bff96c4dae6909ee0760190bde62ac4b48edda47fec263dbdd608ae4f1051
User & Date: js on 2022-06-17 17:12:52
Other Links: manifest | tags
Context
2022-06-17
20:04
Make GCC happy again check-in: b29bfe6485 user: js tags: trunk
17:12
OFPlugin: Completely redesign API check-in: a74bff96c4 user: js tags: trunk
14:36
Better workaround for Clang bug on Windows check-in: 658caa441b user: js tags: trunk
Changes

Modified src/OFPlugin.h from [9b033eeb96] to [4badab4fb7].

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

44
45
46
47
48

49
50
51

52
53
54
55
56

57



58
59


60
61
62
63





64
65
66
67
68
69















70
71
72
73









74
75
16
17
18
19
20
21
22





23
24
25





26
27
28
29
30
31
32

33
34



35
36
37
38

39

40
41
42

43
44
45
46
47


48
49
50



51
52
53
54
55






56
57
58
59
60
61
62
63
64
65
66
67
68
69
70




71
72
73
74
75
76
77
78
79
80
81







-
-
-
-
-



-
-
-
-
-







-
+

-
-
-

+


-
+
-



-
+

+
+
+
-
-
+
+

-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+


#import "OFObject.h"

@class OFString;

#ifndef OF_WINDOWS
# include <dlfcn.h>
typedef void *OFPluginHandle;

typedef enum {
	OFDLOpenFlagLazy = RTLD_LAZY,
	OFDLOpenFlagNow  = RTLD_NOW
} OFDLOpenFlags;
#else
# include <windows.h>
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;
	OFPluginHandle _handle;
	OF_RESERVE_IVARS(OFPlugin, 4)
}

/**
 * @brief Loads a plugin from a file.
 * @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 path Path to the plugin file. The suffix is appended automatically.
 * @return The loaded plugin
 * @param name The name to return the plugin path for
 * @return The plugin path
 */
+ (OF_KINDOF(OFPlugin *))pluginWithPath: (OFString *)path;
@end

+ (OFString *)pathForName: (OFString *)name;

/**
 * @brief Creates a new OFPlugin by loading the plugin with the specified path.
 *
#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);
 * @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;
extern void OFDLClose(OFPluginHandle handle);
#ifdef __cplusplus
}
#endif

/**
 * @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

Modified src/OFPlugin.m from [e14701ced1] to [8ad69b7ef8].

26
27
28
29
30
31
32
33



34
35
36


37
38
39
40
41




42
43
44
45
46
47









48
49
50
51





52

53
54

55
56
57
58
59
60
61
62
63
64
65
66

67
68
69
70
71
72
73
74
75
76
77

78


79



80
81
82
83
84




85
86
87

88
89
90

91
92
93
94
95
96
97
98

99
100
101
102
103
104
105




106
107
108
109
110
111
112
113
114

115
116
117
118
119
120
121
122
123
124
125
126
127




128
129
130


131



132






133
134
135
136

137




138
139
140

141
142
143
26
27
28
29
30
31
32

33
34
35
36


37
38
39




40
41
42
43






44
45
46
47
48
49
50
51
52

53
54
55
56
57
58
59
60

61


62
63











64











65
66
67
68

69
70
71
72




73
74
75
76



77



78






79

80
81






82
83
84
85

86







87













88
89
90
91
92


93
94
95
96
97
98

99
100
101
102
103
104
105
106
107
108
109

110
111
112
113
114
115

116


117







-
+
+
+

-
-
+
+

-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-



+
+
+
+
+
-
+
-
-
+

-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+

+
+
-
+
+
+

-
-
-
-
+
+
+
+
-
-
-
+
-
-
-
+
-
-
-
-
-
-

-
+

-
-
-
-
-
-
+
+
+
+
-

-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+

-
-
+
+

+
+
+
-
+
+
+
+
+
+




+
-
+
+
+
+


-
+
-
-

#import "OFLocale.h"
#import "OFString.h"
#import "OFSystemInfo.h"

#import "OFInitializationFailedException.h"
#import "OFLoadPluginFailedException.h"

typedef OFPlugin *(*PluginInit)(void);
#ifndef RTLD_LAZY
# define RTLD_LAZY 0
#endif

OFPluginHandle
OFDLOpen(OFString *path, OFDLOpenFlags flags)
@implementation OFPlugin
+ (instancetype)pluginWithPath: (OFString *)path
{
#ifndef OF_WINDOWS
	return dlopen([path cStringWithEncoding: [OFLocale encoding]], flags);
#else
	if (path == nil)
	return [[[self alloc] initWithPath: path] autorelease];
}

+ (OFString *)pathForName: (OFString *)name
		return GetModuleHandle(NULL);

	if ([OFSystemInfo isWindowsNT])
		return LoadLibraryW(path.UTF16String);
	else
		return LoadLibraryA(
{
#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];
		    [path cStringWithEncoding: [OFLocale encoding]]);
#endif
}

- (instancetype)initWithPath: (OFString *)path
{
	self = [super init];

	@try {
void *
		void *pool = objc_autoreleasePoolPush();
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);
		_handle = dlopen(
#else
	FreeLibrary(handle);
#endif
}

OFString *
OFDLError(void)
{
#ifndef OF_WINDOWS
	return [OFString stringWithCString: dlerror()
				  encoding: [OFLocale encoding]];
		    [path cStringWithEncoding: [OFLocale encoding]], RTLD_LAZY);
#else
		if ([OFSystemInfo isWindowsNT])
			_handle = LoadLibraryW(path.UTF16String);
	return nil;
		else
			_handle LoadLibraryA(
			    [path cStringWithEncoding: [OFLocale encoding]]);
#endif
}

@implementation OFPlugin
+ (id)pluginWithPath: (OFString *)path

		if (_handle == NULL) {
#ifndef OF_WINDOWS
			OFString *error = [OFString
{
	void *pool = objc_autoreleasePoolPush();
	OFPluginHandle handle;
			    stringWithCString: dlerror()
	PluginInit initPlugin;
	OFPlugin *plugin;

				     encoding: [OFLocale encoding]];
#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];
			OFString *error = nil;
#endif

	if ((handle = OFDLOpen(path, OFDLOpenFlagLazy)) == NULL)
		@throw [OFLoadPluginFailedException
		    exceptionWithPath: path
				error: OFDLError()];

			@throw [OFLoadPluginFailedException
			    exceptionWithPath: path
					error: error];
		}
	objc_autoreleasePoolPop(pool);

	initPlugin = (PluginInit)(uintptr_t)OFDLSym(handle, "OFPluginInit");
	if (initPlugin == (PluginInit)0 || (plugin = initPlugin()) == nil) {
		OFDLClose(handle);
		@throw [OFInitializationFailedException
		    exceptionWithClass: self];
	}

		objc_autoreleasePoolPop(pool);
	plugin->_pluginHandle = handle;
	return plugin;
}

- (instancetype)init
{
	if ([self isMemberOfClass: [OFPlugin class]]) {
		@try {
			[self doesNotRecognizeSelector: _cmd];
		} @catch (id e) {
			[self release];
			@throw e;
		}
	} @catch (id e) {
		[self release];
		@throw e;
	}

		abort();
	}
	return self;
}

- (void *)addressForSymbol: (OFString *)symbol
{
#ifndef OF_WINDOWS
	return [super init];
	return dlsym(_handle,
	    [symbol cStringWithEncoding: [OFLocale encoding]]);
#else
	return (void *)(uintptr_t)GetProcAddress(_handle,
	    [symbol cStringWithEncoding: [OFLocale encoding]]);
#endif
}

- (void)dealloc
{
#ifndef OF_WINDOWS
	OFPluginHandle h = _pluginHandle;
	dlclose(_handle);
#else
	FreeLibrary(_handle);
#endif

	[super dealloc];

}
	OFDLClose(h);
}
@end

Modified tests/OFPluginTests.m from [cebf7a6561] to [d69a23db27].

16
17
18
19
20
21
22
23

24
25

26
27
28
29
30
31
32
33



34

35


36
37




38


39




40
41
42
43
16
17
18
19
20
21
22

23
24

25
26
27
28
29
30
31
32
33
34
35
36

37
38
39
40


41
42
43
44
45
46
47

48
49
50
51
52
53
54
55







-
+

-
+








+
+
+
-
+

+
+
-
-
+
+
+
+

+
+
-
+
+
+
+




#include "config.h"

#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();
	OFString *path;
	OFPlugin *plugin;
	Class (*class)(void);
	TestPlugin *plugin;
	TestPlugin *test;

	TEST(@"+[pathForName:]", (path = [OFPlugin pathForName: pluginName]))

	TEST(@"+[pluginWithPath:]",
	    (plugin = [OFPlugin pluginWithPath: pluginPath]))
	TEST(@"+[pluginWithPath:]", (plugin = [OFPlugin pluginWithPath: path]))

	TEST(@"-[addressForSymbol:]",
	    (class = (Class (*)(void))[plugin addressForSymbol: @"class"]))

	test = [[class() alloc] init];
	@try {
	TEST(@"TestPlugin's -[test:]", [plugin test: 1234] == 2468)
		TEST(@"TestPlugin's -[test:]", [test test: 1234] == 2468)
	} @finally {
		[test release];
	}

	objc_autoreleasePoolPop(pool);
}
@end

Modified tests/plugin/TestPlugin.h from [52670dca56] to [2f9aa9b796].

9
10
11
12
13
14
15
16

17
18

19
20
9
10
11
12
13
14
15

16
17

18
19
20







-
+

-
+


 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * 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

Modified tests/plugin/TestPlugin.m from [b5310ee637] to [5616dbe160].

40
41
42
43
44
45
46
47
48


49
50

51
40
41
42
43
44
45
46


47
48
49

50
51







-
-
+
+

-
+

@implementation TestPlugin
- (int)test: (int)num
{
	return num * 2;
}
@end

id
OFPluginInit(void)
Class
class(void)
{
	return [[[TestPlugin alloc] init] autorelease];
	return [TestPlugin class];
}