1 
2 //          Copyright Michael D. Parker 2018.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE_1_0.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 
7 module bindbc.loader.sharedlib;
8 
9 import core.stdc.stdlib;
10 import core.stdc.string;
11 
12 /// Handle to a shared library
13 struct SharedLib {
14     private void* _handle;
15 }
16 
17 /// Indicates an uninitialized or unassigned handle.
18 enum invalidHandle = SharedLib.init;
19 
20 // Contains information about shared library and symbol load failures.
21 struct ErrorInfo {
22 private:
23     char[32] _error;
24     char[96] _message;
25 
26 public @nogc nothrow @property:
27     /**
28         Returns the string "Missing Symbol" to indicate a symbol load failure, and
29         the name of a library to indicate a library load failure.
30     */
31     const(char)* error() const { return _error.ptr; }
32 
33     /**
34         Returns a symbol name for symbol load failures, and a system-specific error
35         message for library load failures.
36     */
37     const(char)* message() const { return _message.ptr; }
38 }
39 
40 private {
41     __gshared ErrorInfo[] _errors;
42     __gshared size_t _errorCount;
43 }
44 
45 @nogc nothrow:
46 
47 /**
48     Returns an slice containing all errors that have been accumulated by the
49     `load` and `bindSymbol` functions since the last call to `resetErrors`.
50 */
51 const(ErrorInfo)[] errors()
52 {
53     return _errors[0 .. _errorCount];
54 }
55 
56 /**
57     Returns the total number of errors that have been accumulated by the
58     `load` and `bindSymbol` functions since the last call to `resetErrors`.
59 */
60 size_t errorCount()
61 {
62     return _errorCount;
63 }
64 
65 /**
66     Sets the error count to 0 and erases all accumulated errors. This function
67     does not release any memory allocated for the error list.
68 */
69 void resetErrors()
70 {
71     _errorCount = 0;
72     memset(_errors.ptr, 0, _errors.length * ErrorInfo.sizeof);
73 }
74 
75 /*
76 void freeErrors()
77 {
78     free(_errors.ptr);
79     _errors.length = _errorCount = 0;
80 }
81 */
82 
83 /**
84     Loads a symbol from a shared library and assigns it to a caller-supplied pointer.
85 
86     Params:
87         lib =           a valid handle to a shared library loaded via the `load` function.
88         ptr =           a pointer to a function or variable pointer whose declaration is
89                         appropriate for the symbol being bound (it is up to the caller to
90                         verify the types match).
91         symbolName =    the name of the symbol to bind.
92 */
93 void bindSymbol(SharedLib lib, void** ptr, const(char)* symbolName)
94 {
95     // Without this, DMD can hang in release builds
96     pragma(inline, false);
97 
98     assert(lib._handle);
99     auto sym = loadSymbol(lib._handle, symbolName);
100     if(sym) {
101         *ptr = sym;
102     }
103     else {
104         addErr("Missing Symbol", symbolName);
105     }
106 }
107 
108 /**
109     Formats a symbol using the Windows stdcall mangling if necessary before passing it on to
110     bindSymbol.
111 
112     Params:
113         lib =           a valid handle to a shared library loaded via the `load` function.
114         ptr =           a pointer to a function or variable pointer whose declaration is
115                         appropriate for the symbol being bound (it is up to the caller to
116                         verify the types match).
117         symbolName =    the name of the symbol to bind.
118 */
119 void bindSymbol_stdcall(Func)(SharedLib lib, ref Func f, const(char)* symbolName)
120 {
121     import bindbc.loader.system : bindWindows, bind32;
122 
123     static if(bindWindows && bind32) {
124         import core.stdc.stdio : snprintf;
125         import std.traits : ParameterTypeTuple;
126 
127         uint paramSize(A...)(A args)
128         {
129             size_t sum = 0;
130             foreach(arg; args) {
131                 sum += arg.sizeof;
132 
133                 // Align on 32-bit stack
134                 if((sum & 3) != 0) {
135                     sum += 4 - (sum & 3);
136                 }
137             }
138             return sum;
139         }
140 
141         ParameterTypeTuple!f params;
142         char[128] mangled;
143         snprintf(mangled.ptr, mangled.length, "_%s@%d", symbolName, paramSize(params));
144         symbolName = mangled.ptr;
145     }
146     bindSymbol(lib, cast(void**)&f,  symbolName);
147 }
148 
149 /**
150     Loads a shared library from disk, using the system-specific API and search rules.
151 
152     libName =           the name of the library to load. May include the full or relative
153                         path for the file.
154 */
155 SharedLib load(const(char)* libName)
156 {
157     auto handle = loadLib(libName);
158     if(handle) return SharedLib(handle);
159     else {
160         addErr(libName, null);
161         return invalidHandle;
162     }
163 }
164 
165 /**
166     Unloads a shared library from process memory.
167 
168     Generally, it is not necessary to call this function at program exit, as the system will ensure
169     any shared libraries loaded by the process will be unloaded then. However, any loaded shared
170     libraries that are no longer needed by the program during runtime, such as those that are part
171     of a "hot swap" mechanism, should be unloaded to free up resources.
172 */
173 void unload(ref SharedLib lib) {
174     if(lib._handle) {
175         unloadLib(lib._handle);
176         lib = invalidHandle;
177     }
178 }
179 
180 private:
181 void allocErrs() {
182     size_t newSize = _errorCount == 0 ? 16 : _errors.length * 2;
183     auto errs = cast(ErrorInfo*)malloc(ErrorInfo.sizeof * newSize);
184     if(!errs) exit(EXIT_FAILURE);
185 
186     if(_errorCount > 0) {
187         memcpy(errs, _errors.ptr, ErrorInfo.sizeof * _errors.length);
188         free(_errors.ptr);
189     }
190 
191     _errors = errs[0 .. newSize];
192 }
193 
194 void addErr(const(char)* errstr, const(char)* message)
195 {
196     if(_errors.length == 0 || _errorCount >= _errors.length) {
197         allocErrs();
198     }
199 
200     auto pinfo = &_errors[_errorCount];
201     strcpy(pinfo._error.ptr, errstr);
202 
203     if(message) {
204         strncpy(pinfo._message.ptr, message, pinfo._message.length);
205         pinfo._message[pinfo._message.length - 1] = 0;
206     }
207     else {
208         sysError(pinfo._message.ptr, pinfo._message.length);
209     }
210     ++_errorCount;
211 }
212 
213 version(Windows)
214 {
215     import core.sys.windows.windows;
216 
217     void* loadLib(const(char)* name)
218     {
219         return LoadLibraryA(name);
220     }
221 
222     void unloadLib(void* lib)
223     {
224         FreeLibrary(lib);
225     }
226 
227     void* loadSymbol(void* lib, const(char)* symbolName)
228     {
229         return GetProcAddress(lib, symbolName);
230     }
231 
232     void sysError(char* buf, size_t len)
233     {
234         char* msgBuf;
235         enum uint langID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
236 
237         FormatMessageA(
238             FORMAT_MESSAGE_ALLOCATE_BUFFER |
239             FORMAT_MESSAGE_FROM_SYSTEM |
240             FORMAT_MESSAGE_IGNORE_INSERTS,
241             null,
242             GetLastError(),
243             langID,
244             cast(char*)&msgBuf,
245             0,
246             null
247         );
248 
249         if(msgBuf) {
250             strncpy(buf, msgBuf, len);
251             buf[len - 1] = 0;
252             LocalFree(msgBuf);
253         }
254         else strncpy(buf, "Unknown Error\0", len);
255     }
256 }
257 else version(Posix) {
258     import core.sys.posix.dlfcn;
259 
260     void* loadLib(const(char)* name)
261     {
262         return dlopen(name, RTLD_NOW);
263     }
264 
265     void unloadLib(void* lib)
266     {
267         dlclose(lib);
268     }
269 
270     void* loadSymbol(void* lib, const(char)* symbolName)
271     {
272         return dlsym(lib, symbolName);
273     }
274 
275     void sysError(char* buf, size_t len)
276     {
277         const (char)* msg = dlerror();
278         strncpy(buf, msg != null ? msg : "Unknown Error", len);
279         buf[len - 1] = 0;
280     }
281 }
282 else static assert(0, "bindbc-loader is not implemented on this platform.");