├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE.txt ├── LICENSE_DE.txt ├── README.md ├── dub.sdl ├── example ├── dub.sdl ├── foo │ ├── dub.sdl │ └── source │ │ └── foo.d └── source │ ├── app.d │ └── foo_binding.d └── source └── dynamic.d /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{c,h,d,di,dd,json}] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = tab 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | name: CI 6 | strategy: 7 | matrix: 8 | os: [ubuntu-latest, windows-latest, macOS-latest] 9 | dc: [dmd-latest, ldc-latest] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Install D Compiler 14 | uses: dlang-community/setup-dlang@v1 15 | with: 16 | compiler: ${{ matrix.dc }} 17 | - name: Run Tests 18 | run: | 19 | dub build 20 | dub test 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | dynamic.so 6 | dynamic.dylib 7 | dynamic.dll 8 | dynamic.a 9 | dynamic.lib 10 | dynamic-test-* 11 | *.exe 12 | *.o 13 | *.obj 14 | *.lst 15 | *.dll 16 | *.so 17 | *.dynlib 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 RejectedSoftware e.K. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSE_DE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-ludwig/dynamic/490e673de79d0b0d859f67fafd550824b37d1798/LICENSE_DE.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dynamic 2 | ======= 3 | 4 | This is an automatic generator for runtime bindings to shared libraries/DLLs 5 | that works based on plain static bindings consisting of - typically 6 | `extern(C)` - function prototypes. This means that the same bindings can be 7 | used to either link statically against a library, or to load the library at 8 | runtime. 9 | 10 | Example 11 | ------- 12 | 13 | ``` 14 | import std.stdio : writefln; 15 | import deimos.zmq.zmq; 16 | import dynamic; 17 | 18 | // generates the trampoline functions that forward to 19 | // the API entry points loaded at runtime 20 | mixin dynamicBinding!(deimos.zmq.zmq) _zmq; 21 | 22 | void main() 23 | { 24 | // load all API functions from the shared library 25 | version (Windows) enum libs = ["zmq.dll"]; 26 | else enum libs = ["libzmq.so"]; 27 | _zmq.loadBinding(libs); 28 | 29 | // start to use the API as usual 30 | auto context = zmq_ctx_new(); 31 | auto sock = zmq_socket(context, ZMQ_REP); 32 | int rc = zmq_bind(sock, "tcp://*:5555"); 33 | assert(rc == 0); 34 | 35 | ubyte[10] buf; 36 | auto len = zmq_recv(sock, buf.ptr, buf.length, 0); 37 | writefln(Received: %s", buf[0 .. len]); 38 | } 39 | ``` 40 | 41 | 42 | Testing the included example project 43 | ------------------------------------ 44 | 45 | First, build the example library: 46 | 47 | cd example 48 | dub build :foo 49 | 50 | This will create a dynamic/shared library in the foo/ sub folder. Afterwards, 51 | build and run the example itself: 52 | 53 | dub 54 | 55 | This will load the generated dynamic library at runtime and will then calls the 56 | exported `foo` function. Note how the `foo_binding.d` file defines just a static 57 | function prototype, which could just as well be used to statically link against 58 | the library. 59 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "dynamic" 2 | description "Generates dynamic bindings from static bindings at compile time" 3 | authors "Sönke Ludwig" 4 | copyright "Copyright © 2017, Sönke Ludwig" 5 | license "MIT" 6 | targetType "library" 7 | -------------------------------------------------------------------------------- /example/dub.sdl: -------------------------------------------------------------------------------- 1 | name "dynamic-example" 2 | dependency "dynamic" path=".." 3 | 4 | subPackage "foo" 5 | -------------------------------------------------------------------------------- /example/foo/dub.sdl: -------------------------------------------------------------------------------- 1 | name "foo" 2 | targetType "dynamicLibrary" 3 | targetName "foo" 4 | 5 | // Avoid the Phobos dependency, as that requires the Phobos .so to be installed 6 | // in the system 7 | dflags "-betterC" "-defaultlib=" platform="posix" 8 | -------------------------------------------------------------------------------- /example/foo/source/foo.d: -------------------------------------------------------------------------------- 1 | module foo; 2 | 3 | export extern(C) void foo() 4 | { 5 | import core.stdc.stdio; 6 | 7 | printf("Hello, World!\n"); 8 | } 9 | 10 | version (Windows) { 11 | import core.sys.windows.windows; 12 | import core.sys.windows.dll; 13 | 14 | extern (Windows) 15 | BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved) 16 | { 17 | switch (ulReason) { 18 | default: break; 19 | case DLL_PROCESS_ATTACH: dll_process_attach(hInstance, true); break; 20 | case DLL_PROCESS_DETACH: dll_process_detach(hInstance, true); break; 21 | case DLL_THREAD_ATTACH: dll_thread_attach(true, true); break; 22 | case DLL_THREAD_DETACH: dll_thread_detach(true, true); break; 23 | } 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/source/app.d: -------------------------------------------------------------------------------- 1 | import foo_binding; 2 | import dynamic; 3 | 4 | /// Generates the necessary glue code to the DLL/so 5 | mixin dynamicBinding!foo_binding _foo; 6 | 7 | void main() 8 | { 9 | version(Windows) enum libs = ["foo/foo.dll"]; 10 | else version (OSX) enum libs = ["foo/libfoo.so"]; 11 | else enum libs = ["foo/libfoo.so"]; 12 | _foo.loadBinding(libs); 13 | 14 | foo(); 15 | } 16 | -------------------------------------------------------------------------------- /example/source/foo_binding.d: -------------------------------------------------------------------------------- 1 | module foo_binding; 2 | 3 | extern(C) void foo(); 4 | -------------------------------------------------------------------------------- /source/dynamic.d: -------------------------------------------------------------------------------- 1 | module dynamic; 2 | 3 | enum SymbolSet { all, skipDeprecated } 4 | 5 | 6 | /** Declares a dynamically linked library binding. 7 | 8 | Params: 9 | mod = Alias of a module that contains a static binding consisting of 10 | function prototypes 11 | symbols = Optionally disables binding of deprecated symbols 12 | 13 | Example: 14 | --- 15 | import dynamic; 16 | 17 | import my_c_library : myCLibFunction; 18 | 19 | // define the dynamic binding 20 | dynamicBinding!my_c_library myCLibBinding; 21 | 22 | void main() 23 | { 24 | // load the actual symbols from the dynamic library 25 | version (Windows) myCLibBinding.loadBinding("myclib.dll"); 26 | else version (OSX) myCLibBinding.loadBinding("libmyclib.dylib"); 27 | else myCLibBinding.loadBinding("libmyclib.so", "libmyclib.so.1"); 28 | 29 | // now we can call a function from `my_c_library` as if it were a 30 | // simple statically linked function: 31 | myCLibFunction(); 32 | } 33 | --- 34 | */ 35 | mixin template dynamicBinding(alias mod, SymbolSet symbols = SymbolSet.all) 36 | { 37 | import core.runtime : Runtime; 38 | import std.array : join; 39 | import std.traits : ReturnType, ParameterTypeTuple, functionLinkage; 40 | 41 | alias _prototypes = prototypes!(mod, symbols); 42 | private enum _id(alias p) = __traits(identifier, p); 43 | 44 | static foreach (i, proto; _prototypes) { 45 | mixin("extern("~functionLinkage!proto~") alias P_"~_id!proto~" = ReturnType!proto function(ParameterTypeTuple!proto) " ~ join([__traits(getFunctionAttributes, proto)], " ") ~ ";"); 46 | mixin("P_"~_id!proto~" p_"~_id!proto~";"); 47 | mixin("extern("~functionLinkage!proto~") ReturnType!proto "~_id!proto~"(ParameterTypeTuple!proto params"~(__traits(getFunctionVariadicStyle, proto) == "none" 48 | ? "" : ", ...")~") "~join([__traits(getFunctionAttributes, proto)], " ")~" {\n" 49 | ~ " assert(p_"~_id!proto~" !is null, \"Function not loaded: "~_id!proto~"\");\n" 50 | ~ " return p_"~_id!proto~"(params);\n" 51 | ~ "}"); 52 | } 53 | 54 | void loadBinding(scope string[] library_files...) 55 | { 56 | import std.conv : to; 57 | import std.format : format; 58 | import std.utf : toUTF16z; 59 | import std.string : toStringz; 60 | version (Windows) import core.sys.windows.windows : LoadLibraryW; 61 | else import core.sys.posix.dlfcn : dlopen, RTLD_LAZY; 62 | 63 | foreach (f; library_files) { 64 | version (Windows) void* lib = LoadLibraryW(f.toUTF16z); 65 | else void* lib = dlopen(f.toStringz(), RTLD_LAZY); 66 | if (!lib) continue; 67 | 68 | foreach (proto; _prototypes) { 69 | enum ident = __traits(identifier, proto); 70 | mixin("p_"~ident) = cast(typeof(mixin("p_"~ident)))loadProc(lib, proto.mangleof); 71 | if (!mixin("p_"~ident)) 72 | throw new Exception("Failed to load function '"~proto.mangleof~"' from " ~ f); 73 | } 74 | return; 75 | } 76 | 77 | throw new Exception(format("Failed to load any of the shared library candidates: %(%s, %)", library_files)); 78 | } 79 | } 80 | 81 | /// private 82 | template prototypes(alias mod, SymbolSet symbols) 83 | { 84 | import std.meta : AliasSeq, staticMap; 85 | 86 | template Overloads(string name) { 87 | static if (symbols == SymbolSet.skipDeprecated && isDeprecated!(mod, name)) 88 | alias Overloads = AliasSeq!(); 89 | else 90 | alias Overloads = AliasSeq!(__traits(getOverloads, mod, name)); 91 | } 92 | alias functions = staticMap!(Overloads, AliasSeq!(__traits(allMembers, mod))); 93 | 94 | /*template impl(size_t idx) { 95 | static if (idx < members.length) { 96 | alias impl = AliasSeq!(members[i], impl 97 | } else alias impl = AliasSeq!(); 98 | }*/ 99 | alias prototypes = functions; 100 | } 101 | 102 | // crude workaround to gag deprecation warnings 103 | private enum isDeprecated(alias parent, string symbol) = 104 | !__traits(compiles, { 105 | static assert(!__traits(isDeprecated, __traits(getMember, parent, symbol))); 106 | }); 107 | 108 | /// private 109 | void* loadProc(void* lib, string name) 110 | { 111 | import std.string : toStringz; 112 | 113 | version (Windows) { 114 | import core.sys.windows.windows; 115 | return GetProcAddress(lib, name.toStringz()); 116 | } else { 117 | import core.sys.posix.dlfcn : dlsym; 118 | return dlsym(lib, name.toStringz()); 119 | } 120 | } 121 | --------------------------------------------------------------------------------