Jump to page: 1 2
Thread overview
Load dynamic libraries with no hand-written bindings!
Sep 06, 2022
Andrej Mitrovic
Sep 06, 2022
Ali Çehreli
Sep 06, 2022
Adam D Ruppe
Sep 06, 2022
Adam D Ruppe
Sep 07, 2022
Andrej Mitrovic
Sep 07, 2022
Andrej Mitrovic
Sep 06, 2022
Paul Backus
Sep 07, 2022
Andrej Mitrovic
Sep 07, 2022
Paul Backus
Sep 07, 2022
Andrej Mitrovic
Sep 07, 2022
Paul Backus
Sep 07, 2022
Andrej Mitrovic
Sep 06, 2022
ryuukk_
Sep 07, 2022
Andrej Mitrovic
Sep 07, 2022
Andrej Mitrovic
Sep 07, 2022
Andrej Mitrovic
September 06, 2022

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?

September 06, 2022
On 9/6/22 09:03, Andrej Mitrovic wrote:

> I don't think I'm the first one to figure this out (surely someone came
> up with this stuff before?)

I read from Atila once "Basically, with ImportC every C library can be reflected on, because of D."

Yours is a good example. :)

Ali

September 06, 2022

On 9/6/22 12:03 PM, Andrej Mitrovic wrote:

>

Pretty neat, huh?

Nice! I wonder if it can be used to generate static bindings as well? As in, just use it to spit out a D module with the proper function/struct definitions?

dstep can do this, but maybe easy enough to do with ImportC and a small import file.

Still doesn't help with #defines though...

-Steve

September 06, 2022
On Tuesday, 6 September 2022 at 16:28:18 UTC, Steven Schveighoffer wrote:
> Nice! I wonder if it can be used to generate static bindings as well? As in, just use it to spit out a D module with the proper function/struct definitions?

I'm sure you could set it up, but the point of importc is that there's no benefit to doing that anymore...
September 06, 2022

On Tuesday, 6 September 2022 at 16:03:03 UTC, Andrej Mitrovic wrote:

>
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));
        }
    }
}

Haven't tested, but I think you may be able to simplify this by replacing

FuncType!(__traits(getMember, Module, member))

with

typeof(&__traits(getMember, Module, member))

...which would let you eliminate the FuncType template entirely.

September 06, 2022

That's pretty interesting, and it showcase the power and capabilities of ImportC, nice!

Few notes

  • std.meta/std.traits = bloated and slow, i suggest sticking to plain __traits and copy/paste the few(2-3) functions that you need

  • a whole class and new just to store that? you don't seem to make use of inheritance or any OOP features so a simple struct is enough imo

If both points are solved, it'll be betterC compatible out of the box, a win imo

Also, static import isn't nice, it leaks all the functions to the global scope, a little trick:

import pa = portaudio;

(..)

auto dynamic = Dynamic!(pa, "portaudio_x64.dll")();

This should work?

September 06, 2022

On 9/6/22 12:35 PM, Adam D Ruppe wrote:

>

On Tuesday, 6 September 2022 at 16:28:18 UTC, Steven Schveighoffer wrote:

>

Nice! I wonder if it can be used to generate static bindings as well? As in, just use it to spit out a D module with the proper function/struct definitions?

I'm sure you could set it up, but the point of importc is that there's no benefit to doing that anymore...

Well, importC doesn't work for everything. Just like dstep doesn't.

So generating a static binding allows you to both avoid lots of boilerplate work, but also allows you to get a complete binding for the things that importC cannot do.

Also, dstep requires libclang, importC would not.

You could have, in an importC shim, a way to expose all the things that aren't exposed (e.g. you have some #defines, just put them in a const array, and then you can introspect them to generate proper enums).

I might try this with raylib to see how it works. One thing you won't get is comments though...

-Steve

September 06, 2022
On Tuesday, 6 September 2022 at 16:56:16 UTC, Steven Schveighoffer wrote:
> I might try this with raylib to see how it works. One thing you won't get is comments though...

another use for __traits(comment)!
September 07, 2022

On Tuesday, 6 September 2022 at 16:50:16 UTC, Paul Backus wrote:

>

Haven't tested, but I think you may be able to simplify this by replacing

FuncType!(__traits(getMember, Module, member))

with

typeof(&__traits(getMember, Module, member))

...which would let you eliminate the FuncType template entirely.

Already tried that, but it doesn't work. Maybe it's just a bug in the backend.

September 07, 2022

On Tuesday, 6 September 2022 at 16:53:16 UTC, ryuukk_ wrote:

>
  • a whole class and new just to store that? you don't seem to make use of inheritance or any OOP features so a simple struct is enough imo

There's no need for a class. It was just an example. You can use static foreach in module scope.

« First   ‹ Prev
1 2