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 = ©Encoded(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 }