There is a really cool benefit to having the ImportC feature in the language. It enables us to dynamically bind to C libraries without having to write any bindings code by hand!
I don't think I'm the first one to figure this out (surely someone came up with this stuff before?), but anyway here's the trick:
- Step 1: Preprocess your C library header file so you can import it in D.
- Step 2: Use the magic of D's compile-time introspection to generate function pointers and the associated loader routines.
- Step 3: ???
- Step 4: Profit!
The entire minimal example that works on Windows is in this repo: https://github.com/AndrejMitrovic/easybind/blob/master/main.d
Copied here verbatim:
static import portaudio;
import std.meta;
import std.stdio;
import std.traits;
import core.sys.windows.windows;
struct Tuple(_FuncType, string _Name) {
alias FuncType = _FuncType;
enum Name = _Name;
}
/* Get the function pointer type of an actual function */
template FuncType(alias symbol) {
ReturnType!symbol function(Parameters!symbol) func;
alias FuncType = SetFunctionAttributes!(typeof(func), functionLinkage!symbol,
functionAttributes!(typeof(func)));
}
/* Get a sequence of (Function type, Name) belonging to the provided module */
template GetFunctionList(alias Module) {
alias GetFunctionList = AliasSeq!();
static foreach (idx, member; __traits(allMembers, Module)) {
static if (isFunction!(__traits(getMember, Module, member))) {
GetFunctionList = AliasSeq!(GetFunctionList,
Tuple!(FuncType!(__traits(getMember, Module, member)), member));
}
}
}
/* Generate dynamic bindings for all functions in Module and load SharedLib */
class Dynamic(alias Module, string SharedLib)
{
/* Load the shared library */
static HANDLE dll;
static this() {
dll = LoadLibraryA(SharedLib);
!dll && assert(0);
}
/* Declare the function pointers */
static foreach (Tup; GetFunctionList!Module) {
mixin("Tup.FuncType " ~ Tup.Name ~ ";");
}
/* Load the function pointers */
this()
{
static foreach (Tup; GetFunctionList!Module) {
*(cast(void**)&__traits(getMember, this, Tup.Name))
= cast(void*)GetProcAddress(dll, Tup.Name);
}
}
}
void main() {
// easy!
auto dynamic = new Dynamic!(portaudio, "portaudio_x64.dll");
printf("Version info %s\n", dynamic.Pa_GetVersionText());
}
And then:
$ dub run
Version info PortAudio V19.7.0-devel, revision unknown
Pretty neat, huh?