1 /** 2 * The shared library module provides a basic layer around the native functions 3 * used to load symbols from shared libraries. 4 * 5 * Copyright: Copyright (C) 2007 Tomasz Stachowiak 6 * License: BSD style: $(LICENSE) 7 * Authors: Tomasz Stachowiak, Anders Bergh 8 */ 9 10 module tango.sys.SharedLib; 11 12 13 private { 14 import tango.stdc.stringz : fromStringz; 15 16 version (Windows) { 17 import tango.sys.Common : SysError; 18 import tango.sys.win32.Types : HINSTANCE, HMODULE, BOOL; 19 20 extern (Windows) { 21 void* GetProcAddress(HINSTANCE, const(char)*); 22 BOOL FreeLibrary(HMODULE); 23 24 version (Win32SansUnicode) 25 HINSTANCE LoadLibraryA(const(char)*); 26 else { 27 enum {CP_UTF8 = 65001} 28 HINSTANCE LoadLibraryW(const(wchar)*); 29 int MultiByteToWideChar(uint, uint, const(char)*, int, wchar*, int); 30 } 31 } 32 } 33 else version (Posix) { 34 import tango.stdc.posix.dlfcn; 35 } 36 else { 37 static assert (false, "No support for this platform"); 38 } 39 40 version (SharedLibVerbose) import tango.util.log.Trace; 41 } 42 43 version (Posix) { 44 version (FreeBSD) { } else { pragma (lib, "dl"); } 45 } 46 47 48 /** 49 SharedLib is an interface to system-specific shared libraries, such 50 as ".dll", ".so" or ".dylib" files. It provides a simple interface to obtain 51 symbol addresses (such as function pointers) from these libraries. 52 53 Example: 54 ---- 55 56 void main() { 57 if (auto lib = SharedLib.load(`c:\windows\system32\opengl32.dll`)) { 58 Trace.formatln("Library successfully loaded"); 59 60 void* ptr = lib.getSymbol("glClear"); 61 if (ptr) { 62 Trace.formatln("Symbol glClear found. Address = 0x{:x}", ptr); 63 } else { 64 Trace.formatln("Symbol glClear not found"); 65 } 66 67 lib.unload(); 68 } else { 69 Trace.formatln("Could not load the library"); 70 } 71 72 assert (0 == SharedLib.numLoadedLibs); 73 } 74 75 ---- 76 77 This implementation uses reference counting, thus a library is not loaded 78 again if it has been loaded before and not unloaded by the user. 79 Unloading a SharedLib decreases its reference count. When it reaches 0, 80 the shared library associated with it is unloaded and the SharedLib instance 81 is deleted. Please do not delete SharedLib instances manually, unload() will 82 take care of it. 83 84 Note: 85 SharedLib is thread-safe. 86 */ 87 final class SharedLib { 88 /// Mapped from RTLD_NOW, RTLD_LAZY, RTLD_GLOBAL and RTLD_LOCAL 89 enum LoadMode { 90 Now = 0b1, 91 Lazy = 0b10, 92 Global = 0b100, 93 Local = 0b1000 94 } 95 96 97 /** 98 Loads an OS-specific shared library. 99 100 Note: 101 Please use this function instead of the constructor, which is private. 102 103 Params: 104 path = The path to a shared library to be loaded 105 mode = Library loading mode. See LoadMode 106 107 Returns: 108 A SharedLib instance being a handle to the library, or throws 109 SharedLibException if it could not be loaded 110 */ 111 static SharedLib load(const(char)[] path, LoadMode mode = LoadMode.Now | LoadMode.Global) { 112 return loadImpl(path, mode, true); 113 } 114 115 116 117 /** 118 Loads an OS-specific shared library. 119 120 Note: 121 Please use this function instead of the constructor, which is private. 122 123 Params: 124 path = The path to a shared library to be loaded 125 mode = Library loading mode. See LoadMode 126 127 Returns: 128 A SharedLib instance being a handle to the library, or null if it 129 could not be loaded 130 */ 131 static SharedLib loadNoThrow(const(char)[] path, LoadMode mode = LoadMode.Now | LoadMode.Global) { 132 return loadImpl(path, mode, false); 133 } 134 135 136 private static SharedLib loadImpl(const(char)[] path, LoadMode mode, bool throwExceptions) { 137 SharedLib res; 138 139 synchronized (mutex) { 140 auto lib = path in loadedLibs; 141 if (lib) { 142 version (SharedLibVerbose) Trace.formatln("SharedLib found in the hashmap"); 143 res = *lib; 144 } 145 else { 146 version (SharedLibVerbose) Trace.formatln("Creating a new instance of SharedLib"); 147 res = new SharedLib(path); 148 loadedLibs[path] = res; 149 } 150 151 ++res.refCnt; 152 } 153 154 bool delRes = false; 155 Exception exc; 156 157 synchronized (res) { 158 if (!res.loaded) { 159 version (SharedLibVerbose) Trace.formatln("Loading the SharedLib"); 160 try { 161 res.load_(mode, throwExceptions); 162 } catch (Exception e) { 163 exc = e; 164 } 165 } 166 167 if (res.loaded) { 168 version (SharedLibVerbose) Trace.formatln("SharedLib successfully loaded, returning"); 169 return res; 170 } else { 171 synchronized (mutex) { 172 if (path in loadedLibs) { 173 version (SharedLibVerbose) Trace.formatln("Removing the SharedLib from the hashmap"); 174 loadedLibs.remove(path); 175 } 176 } 177 } 178 179 // make sure that only one thread will delete the object 180 if (0 == --res.refCnt) { 181 delRes = true; 182 } 183 } 184 185 if (delRes) { 186 version (SharedLibVerbose) Trace.formatln("Deleting the SharedLib"); 187 res.destroy; 188 } 189 190 if (exc !is null) { 191 throw exc; 192 } 193 194 version (SharedLibVerbose) Trace.formatln("SharedLib not loaded, returning null"); 195 return null; 196 } 197 198 199 /** 200 Unloads the OS-specific shared library associated with this SharedLib instance. 201 202 Note: 203 It's invalid to use the object after unload() has been called, as unload() 204 will delete it if it's not referenced any more. 205 206 Throws SharedLibException on failure. In this case, the SharedLib object is not deleted. 207 */ 208 void unload() { 209 return unloadImpl(true); 210 } 211 212 213 /** 214 Unloads the OS-specific shared library associated with this SharedLib instance. 215 216 Note: 217 It's invalid to use the object after unload() has been called, as unload() 218 will delete it if it's not referenced any more. 219 */ 220 void unloadNoThrow() { 221 return unloadImpl(false); 222 } 223 224 225 private void unloadImpl(bool throwExceptions) { 226 bool deleteThis = false; 227 228 synchronized (this) { 229 assert (loaded); 230 assert (refCnt > 0); 231 232 synchronized (mutex) { 233 if (--refCnt <= 0) { 234 version (SharedLibVerbose) Trace.formatln("Unloading the SharedLib"); 235 try { 236 unload_(throwExceptions); 237 } catch (Exception e) { 238 ++refCnt; 239 throw e; 240 } 241 242 assert ((path in loadedLibs) !is null); 243 loadedLibs.remove(path); 244 245 deleteThis = true; 246 } 247 } 248 } 249 if (deleteThis) { 250 version (SharedLibVerbose) Trace.formatln("Deleting the SharedLib"); 251 ((SharedLib l) { l.destroy; })(this); 252 } 253 } 254 255 256 /** 257 Returns the path to the OS-specific shared library associated with this object. 258 */ 259 @property const(char)[] path() { 260 return this.path_; 261 } 262 263 264 /** 265 Obtains the address of a symbol within the shared library 266 267 Params: 268 name = The name of the symbol; must be a null-terminated C string 269 270 Returns: 271 A pointer to the symbol or throws SharedLibException if it's 272 not present in the library. 273 */ 274 void* getSymbol(const(char)* name) { 275 return getSymbolImpl(name, true); 276 } 277 278 279 /** 280 Obtains the address of a symbol within the shared library 281 282 Params: 283 name = The name of the symbol; must be a null-terminated C string 284 285 Returns: 286 A pointer to the symbol or null if it's not present in the library. 287 */ 288 void* getSymbolNoThrow(const(char)* name) { 289 return getSymbolImpl(name, false); 290 } 291 292 293 private void* getSymbolImpl(const(char)* name, bool throwExceptions) { 294 assert (loaded); 295 return getSymbol_(name, throwExceptions); 296 } 297 298 299 300 /** 301 Returns the total number of libraries currently loaded by SharedLib 302 */ 303 static uint numLoadedLibs() { 304 return cast(uint) loadedLibs.keys.length; 305 } 306 307 308 private { 309 version (Windows) { 310 HMODULE handle; 311 312 void load_(LoadMode mode, bool throwExceptions) { 313 version (Win32SansUnicode) 314 handle = LoadLibraryA((this.path_ ~ "\0").ptr); 315 else { 316 wchar[1024] tmp = void; 317 auto i = MultiByteToWideChar (CP_UTF8, 0, 318 path.ptr, path.length, 319 tmp.ptr, tmp.length-1); 320 if (i > 0) 321 { 322 tmp[i] = 0; 323 handle = LoadLibraryW (tmp.ptr); 324 } 325 } 326 if (handle is null && throwExceptions) { 327 throw new SharedLibException("Couldn't load shared library '" ~ this.path_.idup ~ "' : " ~ SysError.lastMsg.idup); 328 } 329 } 330 331 void* getSymbol_(const(char)* name, bool throwExceptions) { 332 // MSDN: "Multiple threads do not overwrite each other's last-error code." 333 auto res = GetProcAddress(handle, name); 334 if (res is null && throwExceptions) { 335 throw new SharedLibException("Couldn't load symbol '" ~ fromStringz(name).idup ~ "' from shared library '" ~ this.path_.idup ~ "' : " ~ SysError.lastMsg.idup); 336 } else { 337 return res; 338 } 339 } 340 341 void unload_(bool throwExceptions) { 342 if (0 == FreeLibrary(handle) && throwExceptions) { 343 throw new SharedLibException("Couldn't unload shared library '" ~ this.path_.idup ~ "' : " ~ SysError.lastMsg.idup); 344 } 345 } 346 } 347 else version (Posix) { 348 void* handle; 349 350 void load_(LoadMode mode, bool throwExceptions) { 351 int mode_; 352 if (mode & LoadMode.Now) mode_ |= RTLD_NOW; 353 if (mode & LoadMode.Lazy) mode_ |= RTLD_LAZY; 354 if (mode & LoadMode.Global) mode_ |= RTLD_GLOBAL; 355 if (mode & LoadMode.Local) mode_ |= RTLD_LOCAL; 356 357 handle = dlopen((this.path_ ~ "\0").ptr, mode_); 358 if (handle is null && throwExceptions) { 359 throw new SharedLibException("Couldn't load shared library: " ~ fromStringz(dlerror()).idup); 360 } 361 } 362 363 void* getSymbol_(const(char)* name, bool throwExceptions) { 364 if (throwExceptions) { 365 synchronized (typeof(this).classinfo) { // dlerror need not be reentrant 366 auto err = dlerror(); // clear previous error condition 367 auto res = dlsym(handle, name); // result of null does NOT indicate error 368 369 err = dlerror(); // check for error condition 370 if (err !is null) { 371 throw new SharedLibException("Couldn't load symbol: " ~ fromStringz(err).idup); 372 } else { 373 return res; 374 } 375 } 376 } else { 377 return dlsym(handle, name); 378 } 379 } 380 381 void unload_(bool throwExceptions) { 382 if (0 != dlclose(handle) && throwExceptions) { 383 throw new SharedLibException("Couldn't unload shared library: " ~ fromStringz(dlerror()).idup); 384 } 385 } 386 } 387 else { 388 static assert (false, "No support for this platform"); 389 } 390 391 392 const(char)[] path_; 393 int refCnt = 0; 394 395 396 @property bool loaded() { 397 return handle !is null; 398 } 399 400 401 this(const(char)[] path) { 402 this.path_ = path; 403 } 404 } 405 406 407 private __gshared { 408 SharedLib[char[]] loadedLibs; 409 Object mutex; 410 } 411 412 413 shared static this() { 414 mutex = new Object; 415 } 416 } 417 418 419 class SharedLibException : Exception { 420 this (immutable(char)[] msg) { 421 super(msg); 422 } 423 } 424 425 426 427 428 debug (SharedLib) 429 { 430 void main() 431 { 432 auto lib = new SharedLib("foo"); 433 } 434 }