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.");