1 /**
2 Stuff for working with dynamic libraries.
3 
4 Copyright: Denis Shelomovskij 2013
5 
6 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
7 
8 Authors: Denis Shelomovskij
9 */
10 module unstd.dynamiclib;
11 
12 
13 import std.array;
14 import std.conv;
15 import std.traits;
16 
17 import unstd.c..string;
18 
19 version(Windows)
20 {
21 	import core.sys.windows.windows;
22 	import std.windows.syserror;
23 }
24 else version(Posix)
25 {
26 	import core.sys.posix.dlfcn;
27 }
28 else
29 	static assert(0, "Not implemented");
30 
31 
32 version(UnstdDoc)
33 {
34 	/**
35 	Native dynamic library handle.
36 
37 	It is $(D HMODULE) on $(I Windows) and $(D void*) on $(I Posix).
38 	*/
39 	alias DynamicLibHandle = void*;
40 	enum invalidDynamicLibHandle = null;
41 }
42 else version(Windows)
43 {
44 	alias DynamicLibHandle = HMODULE;
45 	enum invalidDynamicLibHandle = null;
46 }
47 else version(Posix)
48 {
49 	alias DynamicLibHandle = void*;
50 	enum invalidDynamicLibHandle = null;
51 }
52 else
53 	static assert(0, "Not implemented");
54 
55 
56 private T _enforce(T)(T value, lazy const(char)[] msg, string file = __FILE__, size_t line = __LINE__)
57 {
58     if(value)
59 		return value;
60 
61 	version(Windows)
62 		const errorStr = sysErrorString(GetLastError());
63 	else version(Posix)
64 		const errorStr = dlerror().toString();
65 	else
66 		static assert(0, "Not implemented");
67 
68 	throw new Exception(text(msg, "\n", errorStr), file, line);
69 }
70 
71 /**
72 This struct encapsulates functionality for working with dynamic libraries.
73 
74 This struct is neither default constructable nor copyable.
75 Pass it by $(D ref) to functions or use $(STDREF typecons, RefCounted).
76 */
77 struct DynamicLib
78 {
79 	// Fields
80 	// ----------------------------------------------------------------------------------------------------
81 
82 	private
83 	{
84 		DynamicLibHandle _handle = invalidDynamicLibHandle;
85 		immutable bool _own;
86 	}
87 
88 	// Constructors
89 	// ----------------------------------------------------------------------------------------------------
90 
91 	@disable this();
92 	@disable this(this);
93 
94 	/**
95 	Construct a $(D DynamicLib) from a manually obtained $(D DynamicLibHandle).
96 
97 	Params:
98 	handle = A valid $(D DynamicLibHandle).
99 	own = Whether or not to close $(D handle) on destruction.
100 
101 	Preconditions:
102 	$(D handle) is a valid $(D DynamicLibHandle).
103 	*/
104 	this(DynamicLibHandle handle, in bool own) @safe pure nothrow @nogc
105 	in { assert(handle != invalidDynamicLibHandle); }
106 	out { assert(associated); }
107 	body
108 	{
109 		_handle = handle;
110 		_own = own;
111 	}
112 
113 	/**
114 	Construct a $(D DynamicLib) with the library name.
115 
116 	Params:
117 	name = A name of the dynamic library to open.
118 	search = Whether system will search for dynamic library.
119 	If $(D false) $(D name) is expected to be a path for the dynamic library file.
120 
121 	Preconditions:
122 	$(D !name.empty)
123 
124 	Throws:
125 	$(D Exception) on opening error.
126 
127 	Note:
128 	Be careful with $(D search = true) as it can lead to $(B security vulnerability) if used careless.
129 	*/
130 	this(in char[] name, in bool search = false) @trusted
131 	in { assert(!name.empty); }
132 	body
133 	{
134 		version(Windows)
135 		{
136 			import core.exception;
137 			import ascii = std.ascii;
138 			import std.file: getcwd;
139 			import std.path;
140 			import unstd.memory.allocation;
141 			import unstd.utf;
142 
143 			const name2 = search || isAbsolute(name) ? name : buildPath(getcwd(), name);
144 
145 			const needDot = !search && name2[$ - 1] != '.' &&
146 				(name2.length < 4 ||
147 				name2[$ - 4] != '.' ||
148 				ascii.toLower(name2[$ - 3]) != 'd' ||
149 				ascii.toLower(name2[$ - 2]) != 'l' ||
150 				ascii.toLower(name2[$ - 1]) != 'l');
151 
152 			const wcharsCount = memoryAdd(maxLength!wchar(name2), 1 + needDot);
153 			if(!wcharsCount)
154 				onOutOfMemoryError();
155 			auto tmp = tempAlloc!wchar(wcharsCount);
156 			wchar* ptrW = &copyEncoded(name2, tmp.arr)[$ - 1];
157 			if(needDot)
158 				*++ptrW = '.';
159 			*++ptrW = '\0';
160 			auto handle = LoadLibraryW(tmp.ptr);
161 		}
162 		else version(Posix)
163 		{
164 			import std.path;
165 			import std..string;
166 
167 			const name2 = search || name.indexOf('/') != -1 ? name : buildPath(getcwd(), name);
168 
169 			const cName = name2.tempCString();
170 			auto handle = dlopen(cName, RTLD_NOW);
171 		}
172 		else
173 			static assert(0, "Not implemented");
174 
175 		_enforce(handle != invalidDynamicLibHandle, "Failed to open: " ~ name);
176 		this(handle, true);
177 	}
178 
179 	// Destructor
180 	// ----------------------------------------------------------------------------------------------------
181 
182 	~this() @trusted
183 	{
184 		if(_own && associated)
185 			close();
186 	}
187 
188 	// Properties
189 	// ----------------------------------------------------------------------------------------------------
190 
191 	@property
192 	{
193 		/**
194 		Returns whether $(D this) is _associated with a dynamic library handle.
195 		It is asserted that no member functions are called for an unassociated
196 		$(D DynamicLib) struct.
197 
198 		Examples:
199 		---
200 		assert(!DynamicLib.init.associated);
201 		auto h = DynamicLib.init.handle; // assertion failure
202 		---
203 		*/
204 		bool associated() const @safe pure nothrow @nogc
205 		{ return _handle != invalidDynamicLibHandle; }
206 
207 		/**
208 		Returns whether handle of the associated dynamic library will be closed on destruction.
209 		*/
210 		bool ownHandle() const @safe pure nothrow @nogc
211 		in { assert(associated); }
212 		body { return _own; }
213 
214 		/**
215 		Gets native _handle of the associated dynamic library.
216 		*/
217 		@property inout(DynamicLibHandle) handle() inout @safe pure nothrow @nogc
218 		in { assert(associated); }
219 		body { return _handle; }
220 	}
221 
222 	// Operators
223 	// ----------------------------------------------------------------------------------------------------
224 
225 	/**
226 	Returns the address of a symbol named $(D symbolName)
227 	or $(D null) if it is not found.
228 
229 	Note:
230 	$(D null) return doesn't mean the symbol is not found as the address
231 	of a symbol may be $(D null).
232 	*/
233 	void* opBinaryRight(string op : "in")(in char[] symbolName) @trusted
234 	in { assert(associated); }
235 	body
236 	{
237 		version(Windows)
238 			return GetProcAddress(_handle, symbolName.tempCString());
239 		else version(Posix)
240 			return dlsym(_handle, symbolName.tempCString());
241 		else
242 			static assert(0, "Not implemented");
243 	}
244 
245 	/**
246 	Returns the address of a symbol named $(D symbolName).
247 
248 	Throws:
249 	$(D Exception) if the symbol is not found or has $(D null) address.
250 	*/
251 	void* opIndex(in char[] symbolName) @trusted
252 	in { assert(associated); }
253 	body
254 	{
255 		const cSymbolName = symbolName.tempCString();
256 		version(Windows)
257 		{
258 			void* res = GetProcAddress(_handle, cSymbolName);
259 		}
260 		else version(Posix)
261 		{
262 			dlerror(); // clear any existing error
263 			void* res = dlsym(_handle, cSymbolName);
264 		}
265 		else
266 			static assert(0, "Not implemented");
267 		return _enforce(res, "Symbol not found: " ~ symbolName);
268 	}
269 
270 	// Functions
271 	// ----------------------------------------------------------------------------------------------------
272 
273 	/**
274 	Calls the function named $(D functionName) from this dynamic library.
275 
276 	Throws:
277 	$(D Exception) if the function is not found or has $(D null) address.
278 
279 	Examples:
280 	---
281 	extern(C) alias F = int function(int) nothrow;
282 	const int res = dynLib.call!F("f", 5);
283 	---
284 	*/
285 	auto ref call(T)(in char[] functionName, auto ref ParameterTypeTuple!T params) @system
286 	in { assert(associated); }
287 	body
288 	{
289 		return (cast(T) opIndex(functionName))(params);
290 	}
291 
292 	/**
293 	Returns the reference to the variable named $(D varName) from this dynamic library.
294 
295 	Throws:
296 	$(D Exception) if the variable is not found or has $(D null) address.
297 
298 	Examples:
299 	---
300 	dynLib.var!int("i") = 3;
301 	int* j = &dynLib.var!int("j")
302 	---
303 	*/
304 	ref T var(T)(in char[] varName) @system
305 	in { assert(associated); }
306 	body
307 	{
308 		return *(cast(T*) opIndex(varName));
309 	}
310 
311 	/**
312 	Closes native handle and makes the struct unassociated.
313 
314 	Throws:
315 	$(D Exception) on closing error.
316 	*/
317 	void close() @trusted
318 	in { assert(associated); }
319 	out { assert(!associated); }
320 	body
321 	{
322 		version(Windows)
323 			const res = FreeLibrary(_handle) != 0;
324 		else version(Posix)
325 			const res = dlclose(_handle) == 0;
326 		else
327 			static assert(0, "Not implemented");
328 		_enforce(res, "Failed to close dynamic library.");
329 		_handle = invalidDynamicLibHandle;
330 	}
331 }
332 
333 /**
334 Tries to set $(D sym) to point to a symbol named $(D symbolName) in dynamic library $(D lib).
335 
336 Examples:
337 ---
338 extern(C) alias F = void function(int) nothrow;
339 F f;
340 if(dynLib.tryBind!f())
341 	f(5);
342 ---
343 */
344 bool tryBind(alias sym)(auto ref DynamicLib lib, in char[] symbolName = sym.stringof) @system
345 {
346 	sym = cast(typeof(sym)) (symbolName in lib);
347 	return sym !is null;
348 }
349 
350 /**
351 Sets $(D sym) to point to a symbol named $(D symbolName) in dynamic library $(D lib).
352 
353 Throws:
354 $(D Exception) if the symbol is not found or has $(D null) address.
355 
356 Examples:
357 ---
358 extern(C) alias F = void function(int) nothrow;
359 F f;
360 dynLib.bind!f();
361 f(5);
362 ---
363 */
364 void bind(alias sym)(auto ref DynamicLib lib, in char[] symbolName = sym.stringof) @system
365 if(!is(sym))
366 {
367 	sym = cast(typeof(sym)) lib[symbolName];
368 }
369 
370 /**
371 Returns an instance of struct $(D T) with all fields pointing to corresponding
372 symbols in dynamic library $(D lib).
373 
374 Throws:
375 $(D Exception) if any corresponding symbol is not found or has $(D null) address.
376 
377 Examples:
378 ---
379 version(Windows):
380 
381 struct Kernel32
382 {
383 extern(Windows) nothrow:
384 	alias DWORD = uint;
385 	DWORD function() GetVersion, GetTickCount;
386 }
387 const kernel32 = DynamicLib("Kernel32.dll", true).bind!Kernel32();
388 
389 import std.stdio;
390 writefln("GetVersion: %X", kernel32.GetVersion());
391 writefln("GetTickCount: %s", kernel32.GetTickCount());
392 ---
393 */
394 T bind(T)(auto ref DynamicLib lib) @system
395 if(is(T == struct))
396 {
397 	T res;
398 	foreach(i, ref field; res.tupleof)
399 		field = cast(typeof(field)) lib[__traits(identifier, res.tupleof[i])];
400 	return res;
401 }
402 
403 version(Windows) unittest
404 {
405 	{
406 		static assert(!DynamicLib.init.associated);
407 		auto dl = DynamicLib("Gdi32.dll", true);
408 		assert(dl.associated && dl.ownHandle);
409 		assert("GetNearestColor" in dl);
410 		assert(dl["GetNearestColor"]);
411 		assert("GetNearestColorXXX" !in dl);
412 		dl.close();
413 		assert(!dl.associated);
414 	}
415 	{
416 		auto dl = DynamicLib("Kernel32.dll", true);
417 
418 		enum name = "GetVersion";
419 		const val = GetVersion();
420 		alias T = typeof(&GetVersion);
421 
422 		auto f = cast(T) dl[name];
423 		assert(f && f() == val);
424 		assert(f == cast(void*) GetProcAddress(GetModuleHandleA("Kernel32"), name));
425 		assert(dl.call!T(name) == val);
426 		assert(cast(void*) &dl.var!int(name) == f);
427 
428 		T GetVersion;
429 		assert(dl.tryBind!GetVersion() && GetVersion == f);
430 		GetVersion = null;
431 		dl.bind!GetVersion();
432 		assert(GetVersion == f);
433 
434 		struct Kernel32 { T GetVersion; static void f(); }
435 		assert(dl.bind!Kernel32().GetVersion == f);
436 
437 		// Windows loads library again here:
438 		assert(DynamicLib("Kernel32.dll.", true)[name] != f);
439 	}
440 }