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* _error; 25 char* _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; } 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; } 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 copyString(char** dst, const(char)* src) 196 { 197 *dst = cast(char*)malloc(strlen(src) + 1); 198 strcpy(*dst, src); 199 } 200 201 void addErr(const(char)* errstr, const(char)* message) 202 { 203 if(_errors.length == 0 || _errorCount >= _errors.length) { 204 allocErrs(); 205 } 206 207 auto pinfo = &_errors[_errorCount]; 208 copyString(&pinfo._error, errstr); 209 210 if(message) { 211 copyString(&pinfo._message, message); 212 } 213 else { 214 sysError(pinfo); 215 } 216 ++_errorCount; 217 } 218 219 version(Windows) 220 { 221 import core.sys.windows.windows; 222 extern(Windows) @nogc nothrow alias pSetDLLDirectory = BOOL function(const(char)*); 223 pSetDLLDirectory setDLLDirectory; 224 225 void* loadLib(const(char)* name) 226 { 227 return LoadLibraryA(name); 228 } 229 230 void unloadLib(void* lib) 231 { 232 FreeLibrary(lib); 233 } 234 235 void* loadSymbol(void* lib, const(char)* symbolName) 236 { 237 return GetProcAddress(lib, symbolName); 238 } 239 240 void sysError(ErrorInfo* pinfo) 241 { 242 char* msgBuf; 243 enum uint langID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); 244 245 FormatMessageA( 246 FORMAT_MESSAGE_ALLOCATE_BUFFER | 247 FORMAT_MESSAGE_FROM_SYSTEM | 248 FORMAT_MESSAGE_IGNORE_INSERTS, 249 null, 250 GetLastError(), 251 langID, 252 cast(char*)&msgBuf, 253 0, 254 null 255 ); 256 257 if(msgBuf) { 258 copyString(&pinfo._message, msgBuf); 259 LocalFree(msgBuf); 260 } 261 else 262 { 263 copyString(&pinfo._message, "Unknown Error"); 264 } 265 } 266 267 /** 268 Adds a path to the default search path on Windows, replacing the path set in a previous 269 call to the same function. 270 271 Any path added via this function will be added to the default DLL search path as documented at 272 https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw. 273 274 Generally, when loading DLLs on a path that is not on the search path, e.g., from a subdirectory 275 of the application, the path should be prepended to the DLL name passed to the load function, 276 e.g., "dlls\\SDL2.dll". If `setCustomLoaderSearchPath(".\\dlls")` is called first, then the subdirectory 277 will become part of the DLL search path and the path may be omitted from the load function. (Be 278 aware that ".\\dlls" is relative to the current working directory, which may not be the application 279 directory, so the path should be constructed appropriately.) 280 281 Some DLLs may depend on other DLLs, perhaps even attempting to load them dynamically at run time 282 (e.g., SDL2_image only loads dependencies such as libpng if it is initialized at run time with 283 support for those dependencies). In this case, if the DLL and its dependencies are placed in a subdirectory and 284 loaded as e.g., "dlls\\SDL2_image.dll", then the dependencies will not be found; the 285 system loader will look for them on the regular DLL search path. When that happens, the solution 286 is to call `setCustomLoaderSearchPath` with the subdirectory before initializing the library. 287 288 Calling this function with `null` as the argument will reset the default search path. 289 290 When the function returns `false`, the relevant `ErrorInfo` is added to the global error list and can 291 be retrieved by looping through the array returned by the `errors` function. 292 293 When placing DLLs in a subdirectory of the application, it should be considered good practice to 294 call `setCustomLoaderSearchPath` to ensure all DLLs load properly. It should also be considered good 295 practice to reset the default search path once all DLLs are loaded. 296 297 This function is only available on Windows, so any usage of it should be preceded with 298 `version(Windows)`. 299 300 Params: 301 path = the path to add to the DLL search path, or `null` to reset the default. 302 303 Returns: 304 `true` if the path was successfully added to the DLL search path, otherwise `false`. 305 */ 306 public 307 bool setCustomLoaderSearchPath(const(char)* path) 308 { 309 if(!setDLLDirectory) { 310 auto lib = load("Kernel32.dll"); 311 if(lib == invalidHandle) return false; 312 lib.bindSymbol(cast(void**)&setDLLDirectory, "SetDllDirectoryA"); 313 if(!setDLLDirectory) return false; 314 } 315 return setDLLDirectory(path) != 0; 316 } 317 } 318 else version(Posix) { 319 import core.sys.posix.dlfcn; 320 321 void* loadLib(const(char)* name) 322 { 323 return dlopen(name, RTLD_NOW); 324 } 325 326 void unloadLib(void* lib) 327 { 328 dlclose(lib); 329 } 330 331 void* loadSymbol(void* lib, const(char)* symbolName) 332 { 333 return dlsym(lib, symbolName); 334 } 335 336 void sysError(ErrorInfo* pinfo) 337 { 338 auto msg = dlerror(); 339 if(!msg) copyString(&pinfo._message, "Uknown Error"); 340 else copyString(&pinfo._message, msg); 341 } 342 } 343 else static assert(0, "bindbc-loader is not implemented on this platform.");