├── .gitignore ├── LICENSE_1_0.txt ├── README.md ├── dub.sdl └── source └── bindbc └── loader ├── codegen.d ├── package.d ├── sharedlib.d └── system.d /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | dub.selections.json 3 | .dub 4 | dscanner.ini 5 | *.kate-swp 6 | .directory 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [BindBC-Loader](https://git.sleeping.town/BindBC/bindbc-loader) 2 | This library contains the cross-platform shared library loading API used by the BindBC packages in their dynamic binding configurations. It is compatible with BetterC, `@nogc`, and `nothrow`, and intended as a replacement for [DerelictUtil](https://github.com/DerelictOrg/DerelictUtil), which does not provide the same level of compatibility. 3 | 4 | The dynamic configuration of each official BindBC package implements its own public load function, which calls into the BindBC-Loader API to load function pointers from the appropriate shared library. BindBC-Loader is only for dynamic bindings, which have no link-time dependency on the bound library. 5 | 6 | Users of packages dependent on BindBC-Loader need not be concerned with its configuration or the loader API. For such users, only the error handling API is of relevance. Anyone implementing a shared library loader on top of BindBC-Loader should be familiar with the entire public API and its configuration. 7 | 8 | | Table of Contents | 9 | |-------------------| 10 | |[License](#license)| 11 | |[Error handling](#error-handling)| 12 | |[Configurations](#configurations)| 13 | |[Default Windows search path](#default-windows-search-path)| 14 | 15 | ## License 16 | 17 | BindBC-Loader—as well as every other library in the [BindBC project](https://github.com/BindBC)—is licensed under the [Boost Software License](https://www.boost.org/LICENSE_1_0.txt). 18 | 19 | ## Error handling 20 | BindBC-Loader does not use exceptions. This decision was made for easier maintenance of the BetterC compatibility, and to provide a common API between both configurations. An application using a dependent package can check for errors consistently when compiling with and without BetterC. 21 | 22 | The `errors()` function is most relevant to end-users of dependant libraries. `errorCount()`, and `resetErrors()` may also be occasionally useful. All three are found in the `bindbc.loader.sharedlib` module. 23 | 24 | Errors are usually generated in two cases: 25 | 1. When a shared library cannot be loaded, usually because the shared library file cannot be found. 26 | 2. When a symbol in the library fails to load. 27 | 28 | Multiple errors may be generated for each case, as attempts may be made to load a shared library from multiple paths, and failure to load one symbol does not immediately abort the load. 29 | 30 | In the official BindBC bindings, the load function for each binding will return one of these values: 31 | 1. `noLibrary` if the library file fails to load. 32 | 2. `badLibrary` when an expected symbol fails to load. 33 | 3. `success`, or a version number. 34 | 35 | For newer BindBC libraries these values belong to the `bindbc.loader.sharedlib.LoadMsg` enum. For older BindBC libraries these values belong to a binding-specific enum. For example, `SDLSupport.noLibrary` or `SDLSupport.badLibrary` for BindBC-SDL. 36 | 37 | The function `bindbc.loader.sharedlib.errors()` returns an array of `ErrorInfo` structs that have two properties: 38 | 39 | * `error`: For a library load failure, this is the name of the library. Otherwise, it is the string `"Missing Symbol"`. 40 | * `message`: In the case of a library load failure, this contains a system-specific error message. Otherwise, it contains the name of the symbol that failed to load. 41 | 42 | Here is an example of what error handling might look like when loading the SDL library with BindBC-SDL: 43 | ```d 44 | import bindbc.sdl; 45 | 46 | /* 47 | Import the sharedlib module for error handling. Assigning an alias ensures that the 48 | function names do not conflict with other public APIs. This isn't strictly necessary, 49 | but the API names are common enough that they could appear in other packages. 50 | */ 51 | import loader = bindbc.loader.sharedlib; 52 | 53 | bool loadLib(){ 54 | LoadMsg ret = loadSDL(); 55 | if(ret != sdlSupport){ 56 | //Log the error info 57 | foreach(info; loader.errors){ 58 | /* 59 | A hypothetical logging function. Note that `info.error` and 60 | `info.message` are null-terminated `const(char)*`, not `string`. 61 | */ 62 | logError(info.error, info.message); 63 | } 64 | 65 | //Optionally construct a user-friendly error message for the user 66 | string msg; 67 | if(ret == SDLSupport.noLibrary){ 68 | msg = "This application requires the SDL library."; 69 | }else{ 70 | SDL_version version; 71 | SDL_GetVersion(&version); 72 | msg = "Your SDL version is too low: "~ 73 | itoa(version.major)~"."~ 74 | itoa(version.minor)~"."~ 75 | itoa(version.patch)~ 76 | ". Please upgrade to 2.0.16+."; 77 | } 78 | //A hypothetical message box function 79 | showMessageBox(msg); 80 | return false; 81 | } 82 | return true; 83 | } 84 | ``` 85 | 86 | `errorCount()` returns the number of errors that have been generated. This might prove useful as a shortcut when loading multiple libraries: 87 | 88 | ```d 89 | loadSDL(); 90 | loadOpenGL(); 91 | if(loader.errorCount > 0){ 92 | //Log the errors 93 | } 94 | ``` 95 | 96 | `resetErrors()` is available to enable alternate approaches to error handling. This clears the `ErrorInfo` array and resets the error count to 0. 97 | 98 | Sometimes, failure to load one library may not be a reason to abort the program. Perhaps an alternative library can be used, or the functionality enabled by that library can be disabled. For such scenarios, it can be convenient to keep the error count specific to each library: 99 | 100 | ```d 101 | if(loadSDL() != sdlSupport){ 102 | //Log errors here 103 | 104 | //Start with a clean slate 105 | loader.resetErrors(); 106 | //And then attempt to load GLFW instead 107 | if(loadGLFW() != glfwSupport){ 108 | //Log errors and abort 109 | } 110 | } 111 | ``` 112 | 113 | ## Configurations 114 | BindBC-Loader is not configured to compile with BetterC compatibility by default. Users of packages dependent on BindBC-Loader should not configure BindBC-Loader directly. Those packages have their own configuration options that will select the appropriate loader configuration. 115 | 116 | Implementers of bindings using BindBC-Loader can make use of two configurations: 117 | * `nobc`, which does not enable BetterC, and is the default. 118 | * `yesbc` enables BetterC. 119 | 120 | Binding implementers should typically provide four configuration options. Two for static bindings (BetterC and non-BetterC), and two for dynamic bindings using the `nobc` and `yesbc` configurations of BindBC-Loader: 121 | 122 | | ┌ | DRuntime | BetterC | 123 | |-------------|------------|-------------| 124 | | **Dynamic** | `dynamic` | `dynamicBC` | 125 | | **Static** | `static` | `staticBC` | 126 | 127 | Anyone using multiple BindBC packages with dynamic bindings must ensure that they are all configured to either use BetterC compatibility, or not. Configuring one BindBC package to use the BetterC configuration and another to use the non-BetterC configuration will cause conflicting versions of BindBC-Loader to be compiled, resulting in compiler or linker errors. 128 | 129 | ## Default Windows search path 130 | Sometimes, it is desirable to place shared libraries in a subdirectory of the application. This is particularly common on Windows. Normally, any DLLs in a subdirectory can be loaded by prepending the subdirectory to the DLL name and passing that name to the appropriate load function (e.g. `loadSDL("dlls\\SDL2.dll")`). This is fine if the DLL has no dependency on any other DLLs, or if its dependencies are somewhere in the default DLL search path. If, however, its dependencies are also in the same subdirectory, then the DLL will fail to load&emdash;the system loader will be looking for the dependencies in the default DLL search path. 131 | 132 | As a remedy, BindBC-Loader exposes the `setCustomLoaderSearchPath` function on Windows&endash;since other systems don't need to programmatically modify the shared library search path. To use it, call it prior to loading any DLLs and provide as the sole argument the path where the DLLs reside. Once this function is called, then the BindBC library's load function(s) may be called with no arguments as long as the DLL names have not been changed from the default. 133 | 134 | An example with BindBC-SDL: 135 | 136 | ```d 137 | import bindbc.sdl; 138 | import bindbc.loader 139 | 140 | //Assume the DLLs are stored in the "dlls" subdirectory 141 | version(Windows) setCustomLoaderSearchPath("dlls"); 142 | 143 | if(loadSDL() < sdlSupport){ /*handle the error*/ } 144 | if(loadSDL_Image() < sdlImageSupport){ /*handle the error*/ } 145 | 146 | //Give SDL_image a chance to load libpng and libjpeg 147 | auto flags = IMG_INIT_PNG | IMG_INIT_JPEG; 148 | if(IMG_Init(flags) != flags){ /*handle the error*/ } 149 | 150 | //Now reset to the default loader search path 151 | version(Windows) setCustomLoaderSearchPath(null); 152 | ``` 153 | 154 | If the DLL name has been changed to something the loader does not recognise (e.g. `"MySDL.dll"`) then it will still need to be passed to the load function. (e.g. `loadSDL("MySDL.dll")`) 155 | 156 | Please note that it is up to the programmer to ensure the path is valid. Generally, using a relative path like `"dlls"` or `".\\dlls"` is unreliable, as the program may be started in a directory that is different from the application directory. It is up to the programmer to ensure that the path is valid. The loader makes no attempt to fetch the current working directory or validate the path. 157 | 158 | For details about how this function affects the system DLL search path, see the documentation of [the Win32 API function `SetDllDirectoryW`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw). -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "bindbc-loader" 2 | description "Cross-platform shared library loader usable with BetterC." 3 | license "BSL-1.0" 4 | authors "Aya Partridge" "Mike Parker" 5 | 6 | targetType "staticLibrary" 7 | targetPath "lib" 8 | targetName "BindBC_Loader" 9 | libs "dl" platform="linux" 10 | 11 | configuration "noBC" { 12 | 13 | } 14 | 15 | configuration "yesBC" { 16 | buildOptions "betterC" 17 | } 18 | -------------------------------------------------------------------------------- /source/bindbc/loader/codegen.d: -------------------------------------------------------------------------------- 1 | /+ 2 | + Copyright 2022 – 2024 Aya Partridge 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.codegen; 8 | 9 | enum makeLibPaths = (string[] names, string[][string] platformNames=null, string[][string] platformPaths=null) nothrow pure @safe{ 10 | string[] namesFor(string platform){ 11 | if(platform in platformNames) return platformNames[platform] ~ names; 12 | else return names; 13 | } 14 | string[] pathsFor(string platform){ 15 | if(auto ret = platform in platformPaths){ 16 | return *ret ~ [""]; 17 | } 18 | else return [""]; 19 | } 20 | string[] ret; 21 | version(Windows){ 22 | foreach(n; namesFor("Windows")){ 23 | foreach(path; pathsFor("Posix")){ 24 | ret ~= [ 25 | path ~ n~`.dll`, 26 | ]; 27 | } 28 | } 29 | }else version(OSX){ 30 | foreach(n; namesFor("OSX")){ 31 | foreach(path; pathsFor("OSX")){ 32 | ret ~= [ 33 | path ~ `lib`~n~`.dylib`, 34 | path ~ n, 35 | ]; 36 | } 37 | } 38 | foreach(n; namesFor("OSX")){ 39 | ret ~= [ 40 | `/opt/homebrew/lib/lib`~n~`.dylib`, 41 | `/Library/Frameworks/`~n~`.framework/`~n, 42 | `/System/Library/Frameworks/`~n~`.framework/`~n, 43 | ]; 44 | } 45 | }else version(Posix){ 46 | foreach(n; namesFor("Posix")){ 47 | foreach(path; pathsFor("Posix")){ 48 | ret ~= [ 49 | path ~ `lib`~n~`.so`, 50 | ]; 51 | } 52 | } 53 | }else static assert(0, "BindBC-Loader does not have library search paths set up for this platform."); 54 | string joined = `[`; 55 | foreach(item; ret[0..$-1]){ 56 | joined ~= `"` ~ item ~ `",`; 57 | } 58 | return joined ~ `"` ~ ret[$-1] ~ `"]`; 59 | }; 60 | unittest{ 61 | version(Windows){ 62 | assert(mixin(makeLibPaths(["test"], null, [ 63 | "Windows": ["windows_path/"], 64 | ])) == ["windows_path/test.dll", "test.dll"]); 65 | }else version(OSX){ 66 | assert(mixin(makeLibPaths(["test"], null, [ 67 | "OSX": ["macos_path/"], 68 | ])) == [ 69 | `macos_path/libtest.dylib`, 70 | `macos_path/test`, 71 | `libtest.dylib`, 72 | `test`, 73 | `/opt/homebrew/lib/libtest.dylib`, 74 | `/Library/Frameworks/test.framework/test`, 75 | `/System/Library/Frameworks/test.framework/test`, 76 | ]); 77 | }else version(Posix){ 78 | assert(mixin(makeLibPaths(["test"], null, [ 79 | "Posix": ["posix_path/"], 80 | ])) == ["posix_path/libtest.so", "libtest.so"]); 81 | } 82 | } 83 | 84 | enum makeDynloadFns = (string name, string libNames, string[] bindModules) nothrow pure @safe{ 85 | string dynloadFns = ` 86 | private SharedLib lib; 87 | 88 | @nogc nothrow{ 89 | void unload`~name~`(){ if(lib != bindbc.loader.invalidHandle) lib.unload(); } 90 | 91 | bool is`~name~`Loaded(){ return lib != bindbc.loader.invalidHandle; } 92 | 93 | LoadMsg load`~name~`(){ 94 | enum libNamesCT = `~libNames~`; 95 | const(char)[][libNamesCT.length] libNames = libNamesCT; 96 | 97 | LoadMsg ret; 98 | foreach(name; libNames){ 99 | ret = load`~name~`(name.ptr); 100 | if(ret == LoadMsg.success) break; 101 | } 102 | return ret; 103 | } 104 | 105 | LoadMsg load`~name~`(const(char)* libName){ 106 | lib = bindbc.loader.load(libName); 107 | if(lib == bindbc.loader.invalidHandle){ 108 | return LoadMsg.noLibrary; 109 | } 110 | 111 | auto errCount = errorCount(); 112 | `; 113 | 114 | foreach(mod; bindModules){ 115 | dynloadFns ~= "\n\t\t"~mod~".bindModuleSymbols(lib);"; 116 | } 117 | 118 | dynloadFns ~= ` 119 | 120 | if(errCount != errorCount()) return LoadMsg.badLibrary; 121 | return LoadMsg.success; 122 | } 123 | }`; 124 | 125 | return dynloadFns; 126 | }; 127 | -------------------------------------------------------------------------------- /source/bindbc/loader/package.d: -------------------------------------------------------------------------------- 1 | /+ 2 | + Copyright 2023 – 2024 Aya Partridge 3 | + Copyright 2018 - 2021 Michael D. Parker 4 | + Distributed under the Boost Software License, Version 1.0. 5 | + (See accompanying file LICENSE_1_0.txt or copy at 6 | + http://www.boost.org/LICENSE_1_0.txt) 7 | +/ 8 | module bindbc.loader; 9 | 10 | public import 11 | bindbc.loader.sharedlib, 12 | bindbc.loader.codegen, 13 | bindbc.loader.system; 14 | -------------------------------------------------------------------------------- /source/bindbc/loader/sharedlib.d: -------------------------------------------------------------------------------- 1 | /+ 2 | + Copyright 2023 – 2024 Aya Partridge 3 | + Copyright 2018 - 2022 Michael D. Parker 4 | + Distributed under the Boost Software License, Version 1.0. 5 | + (See accompanying file LICENSE_1_0.txt or copy at 6 | + http://www.boost.org/LICENSE_1_0.txt) 7 | +/ 8 | /// Cross-platform interface to system APIs for manually loading C libraries. 9 | module bindbc.loader.sharedlib; 10 | 11 | import core.stdc.stdlib; 12 | import core.stdc.string; 13 | 14 | /* 15 | TODO: less pointers, more types: 16 | void* => HMODULE (Windows) 17 | (convert these to null-terminated `char*` interally to avoid user input issues:) 18 | char* => char[] 19 | const(char)* => string 20 | */ 21 | 22 | enum LoadMsg{ 23 | success, 24 | noLibrary, 25 | badLibrary, 26 | } 27 | 28 | /// Handle to a shared library 29 | struct SharedLib{ 30 | private void* _handle; 31 | } 32 | 33 | /// Indicates an uninitialized or unassigned handle. 34 | enum invalidHandle = SharedLib.init; 35 | 36 | /// Holds information about failures in loading shared libraries and their symbols. 37 | struct ErrorInfo{ 38 | private{ 39 | char* _error; 40 | char* _message; 41 | } 42 | 43 | nothrow @nogc: 44 | /** 45 | Returns the string "Missing Symbol" to indicate a symbol load failure, and 46 | the name of a library to indicate a library load failure. 47 | */ 48 | @property const(char)* error() return const{ return _error; } 49 | 50 | /** 51 | Returns a symbol name for symbol load failures, and a system-specific error 52 | message for library load failures. 53 | */ 54 | @property const(char)* message() return const{ return _message; } 55 | } 56 | 57 | /* 58 | TODO: wrap all the following behaviour into an object so that we can have 1 per library & there doesn't have to be one if there's 0 errors 59 | and also use operator overloads to make this work more logically 60 | */ 61 | private{ 62 | ErrorInfo[] _errors; 63 | size_t _errorCount; 64 | } 65 | 66 | @nogc nothrow: 67 | 68 | /** 69 | Returns a slice containing all `ErrorInfo` instances that have been accumulated by the 70 | `load` and `bindSymbol` functions since the last call to `resetErrors`. 71 | */ 72 | const(ErrorInfo)[] errors(){ 73 | return _errors[0 .. _errorCount]; 74 | } 75 | 76 | /** 77 | Returns the total number of `ErrorInfo` instances that have been accumulated by the 78 | `load` and `bindSymbol` functions since the last call to `resetErrors`. 79 | */ 80 | size_t errorCount(){ 81 | return _errorCount; 82 | } 83 | 84 | /** 85 | Sets the error count to 0 and erases all accumulated errors. This function 86 | does not release any memory allocated for the error list. 87 | */ 88 | void resetErrors(){ 89 | _errorCount = 0; 90 | memset(_errors.ptr, 0, _errors.length * ErrorInfo.sizeof); 91 | } 92 | 93 | /* 94 | void freeErrors(){ 95 | free(_errors.ptr); 96 | _errors.length = _errorCount = 0; 97 | } 98 | */ 99 | 100 | /** 101 | Loads a symbol from a shared library and assigns it to a caller-supplied pointer. 102 | 103 | Params: 104 | lib = a valid handle to a shared library loaded via the `load` function. 105 | ptr = a pointer to a function or variable whose declaration is 106 | appropriate for the symbol being bound (it is up to the caller to 107 | verify the types match). 108 | symbolName = the name of the symbol to bind. 109 | */ 110 | void bindSymbol(SharedLib lib, void** ptr, const(char)* symbolName){ 111 | pragma(inline, false); // Without this, DMD can hang in release builds 112 | 113 | assert(lib._handle); 114 | auto sym = loadSymbol(lib._handle, symbolName); 115 | if(sym){ 116 | *ptr = sym; 117 | }else{ 118 | addErr("Missing Symbol", symbolName); 119 | } 120 | } 121 | 122 | /** 123 | Formats a symbol using the Windows stdcall mangling if necessary before passing it on to 124 | bindSymbol. 125 | 126 | Params: 127 | lib = a valid handle to a shared library loaded via the `load` function. 128 | ptr = a reference to a function or variable of matching the template parameter 129 | type whose declaration is appropriate for the symbol being bound (it is up 130 | to the caller to verify the types match). 131 | symbolName = the name of the symbol to bind. 132 | */ 133 | void bindSymbol_stdcall(T)(SharedLib lib, ref T ptr, const(char)* symbolName){ 134 | static if((){ 135 | version(Windows) return (void*).sizeof == 4; 136 | else return false; 137 | }()){ 138 | import core.stdc.stdio : snprintf; 139 | import std.traits : ParameterTypeTuple; 140 | 141 | uint paramSize(A...)(A args){ 142 | size_t sum = 0; 143 | foreach(arg; args){ 144 | sum += arg.sizeof; 145 | 146 | // Align on 32-bit stack 147 | if((sum & 3) != 0){ 148 | sum += 4 - (sum & 3); 149 | } 150 | } 151 | return sum; 152 | } 153 | 154 | ParameterTypeTuple!f params; 155 | char[128] mangled; 156 | snprintf(mangled.ptr, mangled.length, "_%s@%d", symbolName, paramSize(params)); 157 | symbolName = mangled.ptr; 158 | } 159 | bindSymbol(lib, cast(void**)&ptr, symbolName); 160 | } 161 | 162 | /** 163 | Loads a shared library from disk, using the system-specific API and search rules. 164 | 165 | Params: 166 | libName = the name of the library to load. May include the full or relative path for the file. 167 | 168 | Returns: 169 | A shared library, or `invalidHandle` on failure. 170 | */ 171 | SharedLib load(const(char)* libName){ 172 | auto handle = loadLib(libName); 173 | if(handle){ 174 | return SharedLib(handle); 175 | }else{ 176 | addErr(libName, null); 177 | return invalidHandle; 178 | } 179 | } 180 | 181 | /** 182 | Unloads a shared library from process memory. 183 | 184 | Generally, it is not necessary to call this function at program exit, as the system will ensure 185 | any shared libraries loaded by the process will be unloaded. However, it may be useful to call 186 | this function to release shared libraries that are no longer needed by the program during runtime, 187 | such as those that are part of a "hot swap" mechanism or an extension framework. 188 | */ 189 | void unload(ref SharedLib lib){ 190 | if(lib._handle){ 191 | unloadLib(lib._handle); 192 | lib = invalidHandle; 193 | } 194 | } 195 | 196 | private: 197 | void allocErrs(){ 198 | size_t newSize = _errorCount == 0 ? 16 : _errors.length * 2; 199 | auto errs = cast(ErrorInfo*)malloc(ErrorInfo.sizeof * newSize); 200 | if(!errs) exit(EXIT_FAILURE); 201 | 202 | if(_errorCount > 0){ 203 | memcpy(errs, _errors.ptr, ErrorInfo.sizeof * _errors.length); 204 | free(_errors.ptr); 205 | } 206 | 207 | _errors = errs[0 .. newSize]; 208 | } 209 | 210 | void copyString(char** dst, const(char)* src){ 211 | *dst = cast(char*)malloc(strlen(src) + 1); 212 | strcpy(*dst, src); 213 | } 214 | 215 | void addErr(const(char)* errstr, const(char)* message){ 216 | if(_errors.length == 0 || _errorCount >= _errors.length){ 217 | allocErrs(); 218 | } 219 | 220 | auto pinfo = &_errors[_errorCount]; 221 | copyString(&pinfo._error, errstr); 222 | 223 | if(message){ 224 | copyString(&pinfo._message, message); 225 | }else{ 226 | sysError(pinfo); 227 | } 228 | ++_errorCount; 229 | } 230 | 231 | version(Windows){ 232 | import core.sys.windows.winbase; 233 | import core.sys.windows.winnt; 234 | private alias BOOL = int; 235 | private alias HMODULE = void*; 236 | extern(Windows) @nogc nothrow alias pSetDLLDirectory = BOOL function(const(char)*); 237 | pSetDLLDirectory setDLLDirectory; 238 | 239 | HMODULE loadLib(const(char)* name){ 240 | return LoadLibraryA(name); 241 | } 242 | 243 | void unloadLib(void* lib){ 244 | FreeLibrary(lib); 245 | } 246 | 247 | void* loadSymbol(void* lib, const(char)* symbolName){ 248 | return GetProcAddress(lib, symbolName); 249 | } 250 | 251 | void wstringToString(char** dst, const(wchar)* src){ 252 | import core.sys.windows.winnls; 253 | auto srcLen = lstrlenW(src); 254 | auto dstBuffLen = wchar.sizeof*srcLen + 1; 255 | *dst = cast(char*)malloc(dstBuffLen); 256 | WideCharToMultiByte( 257 | CP_UTF8, 258 | 0, 259 | src, 260 | srcLen, 261 | *dst, 262 | cast(int)dstBuffLen, 263 | null, 264 | null, 265 | ); 266 | } 267 | 268 | void sysError(ErrorInfo* pinfo){ 269 | wchar* msgBuf; 270 | enum uint langID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); 271 | 272 | FormatMessageW( 273 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 274 | FORMAT_MESSAGE_FROM_SYSTEM | 275 | FORMAT_MESSAGE_IGNORE_INSERTS, 276 | null, 277 | GetLastError(), 278 | langID, 279 | cast(wchar*)&msgBuf, 280 | 0, 281 | null, 282 | ); 283 | 284 | if(msgBuf){ 285 | wstringToString(&pinfo._message, msgBuf); 286 | LocalFree(msgBuf); 287 | }else{ 288 | copyString(&pinfo._message, "Unknown Error"); 289 | } 290 | } 291 | 292 | /** 293 | Adds a path to the default search path on Windows, replacing the path set in a previous 294 | call to the same function. 295 | 296 | Any path added via this function will be added to the default DLL search path as documented at 297 | https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw. 298 | 299 | Generally, when loading DLLs on a path that is not on the search path, e.g., from a subdirectory 300 | of the application, the path should be prepended to the DLL name passed to the load function, 301 | e.g., "dlls\\SDL2.dll". If `setCustomLoaderSearchPath(".\\dlls")` is called first, then the subdirectory 302 | will become part of the DLL search path and the path may be omitted from the load function. (Be 303 | aware that ".\\dlls" is relative to the current working directory, which may not be the application 304 | directory, so the path should be constructed appropriately.) 305 | 306 | Some DLLs may depend on other DLLs, perhaps even attempting to load them dynamically at run time 307 | (e.g., SDL2_image only loads dependencies such as libpng if it is initialized at run time with 308 | support for those dependencies). In this case, if the DLL and its dependencies are placed in a subdirectory and 309 | loaded as e.g., "dlls\\SDL2_image.dll", then the dependencies will not be found; the 310 | system loader will look for them on the regular DLL search path. When that happens, the solution 311 | is to call `setCustomLoaderSearchPath` with the subdirectory before initializing the library. 312 | 313 | Calling this function with `null` as the argument will reset the default search path. 314 | 315 | When the function returns `false`, the relevant `ErrorInfo` is added to the global error list and can 316 | be retrieved by looping through the array returned by the `errors` function. 317 | 318 | When placing DLLs in a subdirectory of the application, it should be considered good practice to 319 | call `setCustomLoaderSearchPath` to ensure all DLLs load properly. It should also be considered good 320 | practice to reset the default search path once all DLLs are loaded. 321 | 322 | This function is only available on Windows, so any usage of it should be preceded with 323 | `version(Windows)`. 324 | 325 | Params: 326 | path = the path to add to the DLL search path, or `null` to reset the default. 327 | 328 | Returns: 329 | `true` if the path was successfully added to the DLL search path, otherwise `false`. 330 | */ 331 | public bool setCustomLoaderSearchPath(const(char)* path){ 332 | if(!setDLLDirectory){ 333 | auto lib = load("Kernel32.dll"); 334 | if(lib == invalidHandle) return false; 335 | lib.bindSymbol(cast(void**)&setDLLDirectory, "SetDllDirectoryA"); 336 | if(!setDLLDirectory) return false; 337 | } 338 | return setDLLDirectory(path) != 0; 339 | } 340 | }else version(Posix){ 341 | import core.sys.posix.dlfcn; 342 | 343 | void* loadLib(const(char)* name){ 344 | return dlopen(name, RTLD_NOW); 345 | } 346 | 347 | void unloadLib(void* lib){ 348 | dlclose(lib); 349 | } 350 | 351 | void* loadSymbol(void* lib, const(char)* symbolName){ 352 | return dlsym(lib, symbolName); 353 | } 354 | 355 | void sysError(ErrorInfo* pinfo){ 356 | auto msg = dlerror(); 357 | if(!msg) copyString(&pinfo._message, "Uknown Error"); 358 | else copyString(&pinfo._message, msg); 359 | } 360 | }else static assert(0, "bindbc-loader is not implemented on this platform."); 361 | -------------------------------------------------------------------------------- /source/bindbc/loader/system.d: -------------------------------------------------------------------------------- 1 | /+ 2 | + Copyright 2023 – 2024 Aya Partridge 3 | + Copyright 2018 - 2022 Michael D. Parker 4 | + Distributed under the Boost Software License, Version 1.0. 5 | + (See accompanying file LICENSE_1_0.txt or copy at 6 | + http://www.boost.org/LICENSE_1_0.txt) 7 | +/ 8 | module bindbc.loader.system; 9 | 10 | deprecated("Please use `version(D_LP64)` instead; or in a larger if-statement use `(){ version(D_LP64) return true; else return false; }()`, or `(void*).sizeof == 8` instead") 11 | enum bind64 = (void*).sizeof == 8; 12 | deprecated("Please use `version(D_X32)` instead; or in a larger if-statement use `(){ version(D_X32) return true; else return false; }()`, or `(void*).sizeof == 4` instead") 13 | enum bind32 = (void*).sizeof == 4; 14 | 15 | deprecated("Please use `version(Windows)` instead; or in a larger if-statement use `(){ version(Windows) return true; else return false; }()` instead") 16 | enum bindWindows = (){ 17 | version(Windows) return true; 18 | else return false; 19 | }(); 20 | 21 | deprecated("Please use `version(OSX)` instead; or in a larger if-statement use `(){ version(OSX) return true; else return false; }()` instead") 22 | enum bindMac = (){ 23 | version(OSX) return true; 24 | else return false; 25 | }(); 26 | 27 | deprecated("Please use `version(linux)` instead; or in a larger if-statement use `(){ version(linux) return true; else return false; }()` instead") 28 | enum bindLinux = (){ 29 | version(linux) return true; 30 | else return false; 31 | }(); 32 | 33 | deprecated("Please use `version(Posix)` instead; or in a larger if-statement use `(){ version(Posix) return true; else return false; }()` instead") 34 | enum bindPosix = (){ 35 | version(Posix) return true; 36 | else return false; 37 | }(); 38 | 39 | deprecated("Please use `version(Android)` instead; or in a larger if-statement use `(){ version(Android) return true; else return false; }()` instead") 40 | enum bindAndroid = (){ 41 | version(Android) return true; 42 | else return false; 43 | }(); 44 | 45 | deprecated("`bindIOS` is always false. Please use `version(iOS)` to check if your code is compiled for iOS instead") 46 | enum bindIOS = false; 47 | 48 | deprecated("`bindWinRT` is always false. Please use a custom version identifier (e.g. `version(WinRT)`) to check if your code is compiled for WinRT instead") 49 | enum bindWinRT = false; 50 | 51 | deprecated("Please use `version(OpenBSD)` instead; or in a larger if-statement use `(){ version(OpenBSD) return true; else return false; }()` instead") 52 | enum bindOpenBSD = (){ 53 | version(OpenBSD) return true; 54 | else return false; 55 | }(); 56 | 57 | deprecated("Please use `version(FreeBSD)` instead; or in a larger if-statement use `(){ version(FreeBSD) return true; else return false; }()` instead") 58 | enum bindFreeBSD = (){ 59 | version(FreeBSD) return true; 60 | else return false; 61 | }(); 62 | 63 | deprecated("Please use `version(BSD)` instead; or in a larger if-statement use `(){ version(BSD) return true; else return false; }()` instead") 64 | enum bindBSD = (){ 65 | version(FreeBSD) return true; 66 | else version(OpenBSD) return true; 67 | else version(BSD) return true; 68 | else return false; 69 | }(); 70 | --------------------------------------------------------------------------------