1 module sharedlib;
2 
3 import core.stdc.errno;
4 import core.stdc.string;
5 import core.atomic;
6 import core.sync.mutex;
7 import core.sys.posix.dlfcn;
8 import core.sys.posix.unistd;
9 import std.exception;
10 import std.string : toStringz;
11 
12 
13 version (Posix):
14 
15 
16 /**
17 See `man 3 dlopen`.
18 */
19 enum RTLD_LOCAL = 0;
20 enum RTLD_LAZY = 1;
21 enum RTLD_NOW = 2;
22 enum RTLD_GLOBAL = 3;
23 
24 
25 /// To guard all uses libdl our own mutex.
26 /// This code is stallen from std.concurrency.
27 private @property shared(Mutex) initOnceLock()
28 {
29     static shared Mutex lock;
30     if (auto mtx = atomicLoad!(MemoryOrder.acq)(lock))
31         return mtx;
32     auto mtx = new shared Mutex;
33     if (cas(&lock, cast(shared) null, mtx))
34         return mtx;
35     return atomicLoad!(MemoryOrder.acq)(lock);
36 }
37 
38 
39 /// Whole error in libdl is shared at global state.
40 /// So need to guard all uses of libdl with mutex.
41 private void dlerrorWithFunc(bool delegate() del)
42 {
43     auto m = initOnceLock();
44     m.lock();
45     scope (exit) m.unlock();
46 
47     if (!del())
48     {
49         const errorMsg = dlerror();
50         if (errorMsg !is null)
51             errnoEnforce(false, cast(string) errorMsg[0 .. strlen(errorMsg)]);
52         errnoEnforce(false, "failed unknown reason.");
53     }
54 }
55 
56 /**
57    This is a wrapper of UNIX-specified dynamic loading.
58    See `man 3 dlopen`.
59  */
60 struct SharedLibrary
61 {
62     void* handle;
63 
64     ///
65     this(string filename, int flags)
66     {
67         dlerrorWithFunc(() nothrow {
68                 this.handle = dlopen(filename.toStringz, flags);
69                 return this.handle !is null;
70             });
71     }
72 
73     ~this() nothrow @nogc
74     {
75         import core.internal.abort : abort;
76         /// destructor cannot raise exception, so only call dlclose(3).
77         if (this.handle !is null)
78         {
79             const ret = dlclose(this.handle);
80             if (ret != 0)
81                 abort("Error: dlclose(3) failed.");
82         }
83     }
84 
85     ///
86     void close()
87     {
88         dlerrorWithFunc(() nothrow {
89                 return dlclose(this.handle) == 0;
90             });
91     }
92 
93     ///
94     auto get(string symbolName)
95     {
96         void* symbol;
97         dlerrorWithFunc(() nothrow {
98                 symbol = dlsym(this.handle, symbolName.toStringz);
99                 return symbol !is null;
100             });
101         return symbol;
102     }
103 
104     // utility for getting the adress of library loaded.
105     void* getLoadedAddr() nothrow
106     {
107         return cast(void*) *cast(const size_t*) this.handle;
108     }
109 }
110 
111 
112 @system unittest
113 {
114     version (linux)
115     {
116         // Using libm.so gots invalid ELF Header.
117         string libm = "libm.so.6";
118     }
119     else version (OSX)
120     {
121         string libm = "libm.dylib";
122     }
123 
124     {
125         auto lib = new SharedLibrary(libm, RTLD_LAZY);
126         auto ceil = cast(double function(double)) lib.get("ceil");
127         assert(ceil(0.45) == 1);
128     }
129 
130     {
131         auto lib = new SharedLibrary(libm, RTLD_LAZY);
132         const addr = lib.getLoadedAddr();
133         assert(addr !is null);
134     }
135 }