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             delete res;
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) { delete l; })(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 }