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