Posts
Search
Contact
Cookies
About
RSS

dlang wrapping on the fly without dub

Added 19 Feb 2019, 12:17 p.m. edited 18 Jun 2023, 1:12 a.m.

For a number of reason it can be more convenient to build your project using make, for example you might be using more than one language in your project. While you can link into wrappers that dub had downloaded, if you only need a handful of functions from a simple library its not impossible to just wrap the functions on the fly as you need them. Granted if you're coding a simple project in just D and there are wrappers already available you're probably much better off just using dub, equally doing this the hard way definitely helps with the learning process...

Lets have a look at wrapping a few simple function from a foreign library and how we can build them using make.

Lets look at a super simple library written in D

import std.stdio;

extern(C) void hello() {
write("hello ");
}

extern(C) int world(int i) {
writeln("world");
return i*2;
}

Told you it was simple, but we have a few different function prototypes which should be sufficient to illustrate the point, our make rule is just as simple

hellod.so: hello.d
dmd -shared -fPIC -of=hellod.so hello.d

Our "library" is compiled as a shared library (-shared) and the code is also compiled using position independent code (-fPIC)

We're using a platform specific library to load and find the function pointers, as I only use windows for playing the odd game (it can just about manage that...) and a dive into dll hell just doesn't appeal. That said once you have seen how this is used you can use D's conditional compilation to implement the equivalent windows code (I thoroughly recommend looking at the very excellent bindbc project's code)

To actually load our function pointers to the library functions we need to declare a few prototypes...

extern(C) void* dlopen(const char*, int);
extern(C) void* dlsym(void*, const char*);
extern(C) char* dlerror(); extern(C) int dlclose(void);
enum RTLD_LAZY = 0x00001;

we're using libdl to do this, and yes I know that its available in core.sys.posix.dlfcn but don't forget we're doing this the hard way and doing it all for ourselves... we also want some "types" for convenience that allow us to cast the function prototypes to the appropriate signature.

alias void function () hello_t;
alias int function (int) world_t;

first of all we need a "handle" to the library

void* handle = dlopen("./hellod.so", RTLD_LAZY);
if (!handle) {
writeln("can't open library");
return -1;
}

once we have a handle to our library we can then create our function pointers using the appropriate alias's

hello_t hello = cast(hello_t)dlsym(handle, "hello");
if (!hello) {
writeln("Cannot load symbol 'hello': ",fromStringz(dlerror()));
dlclose(handle);
return -1;
}
world_t world = cast(world_t)dlsym(handle, "world");
...

finally using the function pointers is entirely transparent...

hello();
writeln("world passed 2 returned ",world(2));
dlclose(handle);

finally we need a make rule to build our test app

maind: main.d
dmd -L=-ldl main.d -of=maind

The only thing of interest here is that we're passing a flag onto the linker (-ldl) which ensures we're linking in the library containing dlopen and friends

So there you go, hopefully doing all this the hard way you've go an insight as to how wrappers load dynamic libraries and you can go back to using dub...