1 2 // Copyright 2018 - 2021 Michael D. Parker 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 /// Cross-platform interface to system APIs for manually loading C libraries. 8 module bindbc.loader.sharedlib; 9 10 import core.stdc.stdlib; 11 import core.stdc..string; 12 13 /// Handle to a shared library 14 struct SharedLib { 15 private void* _handle; 16 } 17 18 /// Indicates an uninitialized or unassigned handle. 19 enum invalidHandle = SharedLib.init; 20 21 /// Holds information about failures in loading shared libraries and their symbols. 22 struct ErrorInfo { 23 private: 24 char[32] _error; 25 char[96] _message; 26 27 public @nogc nothrow @property: 28 /** 29 Returns the string "Missing Symbol" to indicate a symbol load failure, and 30 the name of a library to indicate a library load failure. 31 */ 32 const(char)* error() return const { return _error.ptr; } 33 34 /** 35 Returns a symbol name for symbol load failures, and a system-specific error 36 message for library load failures. 37 */ 38 const(char)* message() return const { return _message.ptr; } 39 } 40 41 private { 42 ErrorInfo[] _errors; 43 size_t _errorCount; 44 } 45 46 @nogc nothrow: 47 48 /** 49 Returns a slice containing all `ErrorInfo` instances that have been accumulated by the 50 `load` and `bindSymbol` functions since the last call to `resetErrors`. 51 */ 52 const(ErrorInfo)[] errors() 53 { 54 return _errors[0 .. _errorCount]; 55 } 56 57 /** 58 Returns the total number of `ErrorInfo` instances that have been accumulated by the 59 `load` and `bindSymbol` functions since the last call to `resetErrors`. 60 */ 61 size_t errorCount() 62 { 63 return _errorCount; 64 } 65 66 /** 67 Sets the error count to 0 and erases all accumulated errors. This function 68 does not release any memory allocated for the error list. 69 */ 70 void resetErrors() 71 { 72 _errorCount = 0; 73 memset(_errors.ptr, 0, _errors.length * ErrorInfo.sizeof); 74 } 75 76 /* 77 void freeErrors() 78 { 79 free(_errors.ptr); 80 _errors.length = _errorCount = 0; 81 } 82 */ 83 84 /** 85 Loads a symbol from a shared library and assigns it to a caller-supplied pointer. 86 87 Params: 88 lib = a valid handle to a shared library loaded via the `load` function. 89 ptr = a pointer to a function or variable whose declaration is 90 appropriate for the symbol being bound (it is up to the caller to 91 verify the types match). 92 symbolName = the name of the symbol to bind. 93 */ 94 void bindSymbol(SharedLib lib, void** ptr, const(char)* symbolName) 95 { 96 // Without this, DMD can hang in release builds 97 pragma(inline, false); 98 99 assert(lib._handle); 100 auto sym = loadSymbol(lib._handle, symbolName); 101 if(sym) { 102 *ptr = sym; 103 } 104 else { 105 addErr("Missing Symbol", symbolName); 106 } 107 } 108 109 /** 110 Formats a symbol using the Windows stdcall mangling if necessary before passing it on to 111 bindSymbol. 112 113 Params: 114 lib = a valid handle to a shared library loaded via the `load` function. 115 ptr = a reference to a function or variable of matching the template parameter 116 type whose declaration is appropriate for the symbol being bound (it is up 117 to the caller to verify the types match). 118 symbolName = the name of the symbol to bind. 119 */ 120 void bindSymbol_stdcall(T)(SharedLib lib, ref T ptr, const(char)* symbolName) 121 { 122 import bindbc.loader.system : bindWindows, bind32; 123 124 static if(bindWindows && bind32) { 125 import core.stdc.stdio : snprintf; 126 import std.traits : ParameterTypeTuple; 127 128 uint paramSize(A...)(A args) 129 { 130 size_t sum = 0; 131 foreach(arg; args) { 132 sum += arg.sizeof; 133 134 // Align on 32-bit stack 135 if((sum & 3) != 0) { 136 sum += 4 - (sum & 3); 137 } 138 } 139 return sum; 140 } 141 142 ParameterTypeTuple!f params; 143 char[128] mangled; 144 snprintf(mangled.ptr, mangled.length, "_%s@%d", symbolName, paramSize(params)); 145 symbolName = mangled.ptr; 146 } 147 bindSymbol(lib, cast(void**)&ptr, symbolName); 148 } 149 150 /** 151 Loads a shared library from disk, using the system-specific API and search rules. 152 153 libName = the name of the library to load. May include the full or relative 154 path for the file. 155 */ 156 SharedLib load(const(char)* libName) 157 { 158 auto handle = loadLib(libName); 159 if(handle) return SharedLib(handle); 160 else { 161 addErr(libName, null); 162 return invalidHandle; 163 } 164 } 165 166 /** 167 Unloads a shared library from process memory. 168 169 Generally, it is not necessary to call this function at program exit, as the system will ensure 170 any shared libraries loaded by the process will be unloaded. However, it may be useful to call 171 this function to release shared libraries that are no longer needed by the program during runtime, 172 such as those that are part of a "hot swap" mechanism or an extension framework. 173 */ 174 void unload(ref SharedLib lib) { 175 if(lib._handle) { 176 unloadLib(lib._handle); 177 lib = invalidHandle; 178 } 179 } 180 181 private: 182 void allocErrs() { 183 size_t newSize = _errorCount == 0 ? 16 : _errors.length * 2; 184 auto errs = cast(ErrorInfo*)malloc(ErrorInfo.sizeof * newSize); 185 if(!errs) exit(EXIT_FAILURE); 186 187 if(_errorCount > 0) { 188 memcpy(errs, _errors.ptr, ErrorInfo.sizeof * _errors.length); 189 free(_errors.ptr); 190 } 191 192 _errors = errs[0 .. newSize]; 193 } 194 195 void addErr(const(char)* errstr, const(char)* message) 196 { 197 if(_errors.length == 0 || _errorCount >= _errors.length) { 198 allocErrs(); 199 } 200 201 auto pinfo = &_errors[_errorCount]; 202 strcpy(pinfo._error.ptr, errstr); 203 204 if(message) { 205 strncpy(pinfo._message.ptr, message, pinfo._message.length); 206 pinfo._message[pinfo._message.length - 1] = 0; 207 } 208 else { 209 sysError(pinfo._message.ptr, pinfo._message.length); 210 } 211 ++_errorCount; 212 } 213 214 version(Windows) 215 { 216 import core.sys.windows.windows; 217 extern(Windows) @nogc nothrow alias pSetDLLDirectory = BOOL function(const(char)*); 218 pSetDLLDirectory setDLLDirectory; 219 220 void* loadLib(const(char)* name) 221 { 222 return LoadLibraryA(name); 223 } 224 225 void unloadLib(void* lib) 226 { 227 FreeLibrary(lib); 228 } 229 230 void* loadSymbol(void* lib, const(char)* symbolName) 231 { 232 return GetProcAddress(lib, symbolName); 233 } 234 235 void sysError(char* buf, size_t len) 236 { 237 char* msgBuf; 238 enum uint langID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); 239 240 FormatMessageA( 241 FORMAT_MESSAGE_ALLOCATE_BUFFER | 242 FORMAT_MESSAGE_FROM_SYSTEM | 243 FORMAT_MESSAGE_IGNORE_INSERTS, 244 null, 245 GetLastError(), 246 langID, 247 cast(char*)&msgBuf, 248 0, 249 null 250 ); 251 252 if(msgBuf) { 253 strncpy(buf, msgBuf, len); 254 buf[len - 1] = 0; 255 LocalFree(msgBuf); 256 } 257 else strncpy(buf, "Unknown Error\0", len); 258 } 259 260 /** 261 Adds a path to the default search path on Windows, replacing the path set in a previous 262 call to the same function. 263 264 Any path added via this function will be added to the default DLL search path as documented at 265 https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw. 266 267 Generally, when loading DLLs on a path that is not on the search path, e.g., from a subdirectory 268 of the application, the path should be prepended to the DLL name passed to the load function, 269 e.g., "dlls\\SDL2.dll". If `setCustomLoaderSearchPath(".\\dlls")` is called first, then the subdirectory 270 will become part of the DLL search path and the path may be omitted from the load function. (Be 271 aware that ".\\dlls" is relative to the current working directory, which may not be the application 272 directory, so the path should be constructed appropriately.) 273 274 Some DLLs may depend on other DLLs, perhaps even attempting to load them dynamically at run time 275 (e.g., SDL2_image only loads dependencies such as libpng if it is initialized at run time with 276 support for those dependencies). In this case, if the DLL and its dependencies are placed in a subdirectory and 277 loaded as e.g., "dlls\\SDL2_image.dll", then the dependencies will not be found; the 278 system loader will look for them on the regular DLL search path. When that happens, the solution 279 is to call `setCustomLoaderSearchPath` with the subdirectory before initializing the library. 280 281 Calling this function with `null` as the argument will reset the default search path. 282 283 When the function returns `false`, the relevant `ErrorInfo` is added to the global error list and can 284 be retrieved by looping through the array returned by the `errors` function. 285 286 When placing DLLs in a subdirectory of the application, it should be considered good practice to 287 call `setCustomLoaderSearchPath` to ensure all DLLs load properly. It should also be considered good 288 practice to reset the default search path once all DLLs are loaded. 289 290 This function is only available on Windows, so any usage of it should be preceded with 291 `version(Windows)`. 292 293 Params: 294 path = the path to add to the DLL search path, or `null` to reset the default. 295 296 Returns: 297 `true` if the path was successfully added to the DLL search path, otherwise `false`. 298 */ 299 public 300 bool setCustomLoaderSearchPath(const(char)* path) 301 { 302 if(!setDLLDirectory) { 303 auto lib = load("Kernel32.dll"); 304 if(lib == invalidHandle) return false; 305 lib.bindSymbol(cast(void**)&setDLLDirectory, "SetDllDirectoryA"); 306 if(!setDLLDirectory) return false; 307 } 308 return setDLLDirectory(path) != 0; 309 } 310 } 311 else version(Posix) { 312 import core.sys.posix.dlfcn; 313 314 void* loadLib(const(char)* name) 315 { 316 return dlopen(name, RTLD_NOW); 317 } 318 319 void unloadLib(void* lib) 320 { 321 dlclose(lib); 322 } 323 324 void* loadSymbol(void* lib, const(char)* symbolName) 325 { 326 return dlsym(lib, symbolName); 327 } 328 329 void sysError(char* buf, size_t len) 330 { 331 auto msg = dlerror(); 332 strncpy(buf, msg != null ? msg : "Unknown Error", len); 333 buf[len - 1] = 0; 334 } 335 } 336 else static assert(0, "bindbc-loader is not implemented on this platform.");