Thread overview
Elegant way to use dynamic bindings
Feb 09, 2018
Dennis
Feb 09, 2018
Mike Parker
Feb 09, 2018
Mike Parker
Feb 09, 2018
Dennis
Feb 09, 2018
Mike Parker
Feb 09, 2018
Dennis
Feb 09, 2018
Mike Wey
Feb 09, 2018
Dennis
Mar 08, 2018
Dennis
February 09, 2018
I want to bind to a .dll on Windows, so I looked at how Derelict packages are doing it and found it does it like this:

```
extern(C) {
  alias da_CoreGetAPIVersions = m64p_error function(int*,int*,int*,int*);
  ...
}

__gshared {
  da_CoreGetAPIVersions CoreGetAPIVersions;
  ...
}

protected override void loadSymbols() {
  bindFunc(cast(void**)&CoreGetAPIVersions,"CoreGetAPIVersions");
  ...
```
I don't like how each function name is repeated 4 times. Preferably, I'd write it like a static library:
```
extern(C) {
  ///documentation
  m64p_error CoreGetAPIVersions(int*,int*,int*,int*);
}
```
And then use some reflection and mixins to make it work for dynamic bindings.
I found the IMPLIB tool (http://www.digitalmars.com//ctg/implib.html) and made a .lib for the .dll and at first I got:

Error 42: Symbol Undefined _CoreGetAPIVersions
Error: linker exited with status 1

So apparently, you got to bind functions pointers, not functions.
```
extern(C) {
  ///documentation
  m64p_error function(int*,int*,int*,int*) CoreGetAPIVersions;
}
```
But then I get:
object.Error@(0): Access Violation

I presume the .dll isn't loaded properly (if at all), but I can't find a load function in the .lib and don't know how to debug this. So I guess I'll just do it manually since that works, but does anyone have some tips to make .dll bindings elegantly?

February 09, 2018
On Friday, 9 February 2018 at 12:15:04 UTC, Dennis wrote:

>
> I presume the .dll isn't loaded properly (if at all), but I can't find a load function in the .lib and don't know how to debug this. So I guess I'll just do it manually since that works, but does anyone have some tips to make .dll bindings elegantly?

I suggest you read this first:

http://derelictorg.github.io/bindings/

It explains the difference between dynamic bindings and static bindings. The important thing to understand is that both types can be used with DLLs, but only static bindings can be use with static libraries.

Dynamic bindings have no link time dependency on the DLL and the DLL must be loaded manually. Static bindings have a link time dependency and can be linked with either a static library, an import library (on Windows -- what you get when you run implib or compile a DLL yourself), or directly with a shared object (.so) on non-Windows systems (and probably .dylib/Frameworks on Mac).

Dynamic bindings use function pointer because that's the only way to do manuall loading. For static bindings, you use normal function declarations and the OS will load the DLL automatically when the app starts up (dynamic loading) if you've linked to an import library or .so, and, of course, will load nothing if you've linked to a static library.

Some Derelict packages, like DerelictSDL2 and DerelictGLFW3, support both dynamic and static binding. For the former, the function pointer declarations and the loader are all in [1]. For the latter, there is no loader and the declarations are all in [2].

[1] https://github.com/DerelictOrg/DerelictGLFW3/blob/master/source/derelict/glfw3/dynload.d

[2] https://github.com/DerelictOrg/DerelictGLFW3/blob/master/source/derelict/glfw3/statfun.d

So if you don't need to load manually and don't mind the link-time dependency on the C library, then you can use normal function declarations in your binding:

extern(C) @nogc nothrow {
    void foo();
}

February 09, 2018
On Friday, 9 February 2018 at 12:15:04 UTC, Dennis wrote:

> I found the IMPLIB tool (http://www.digitalmars.com//ctg/implib.html) and made a .lib for the .dll and at first I got:
>
> Error 42: Symbol Undefined _CoreGetAPIVersions
> Error: linker exited with status 1
>

Forgot to address this in the previous post.

Did you link with the library you created with implib? That linker error suggests you didn't.
February 09, 2018
I read the Derelict documentation a while ago, I didn't grasp all of it. Reading it again, I can now make sense of it though. :)

On Friday, 9 February 2018 at 12:53:44 UTC, Mike Parker wrote:
> Did you link with the library you created with implib? That linker error suggests you didn't.

I added `pragma(lib, "mupen64plus.lib");` above the extern(C) block.
Adding `libs "mupen64plus"` to dub.sdl doesn't make a difference.

The thing that confuses me is that the import lib has symbols without underscore prefix (I see them when running `libunres -p mupen64plus.lib`), but when I change the function pointer declaration into a function declaration it tries to link "_CoreGetAPIVersions" and can't find it.

I put the .dll's in the root of the project directory by the way. Does that count as a "system library search path"?
February 09, 2018
On Friday, 9 February 2018 at 14:04:05 UTC, Dennis wrote:

> I added `pragma(lib, "mupen64plus.lib");` above the extern(C) block.
> Adding `libs "mupen64plus"` to dub.sdl doesn't make a difference.

Where was the lib file located? Was it in the root project directory? How are you compiling your project? What does your directory tree look like?

>
> The thing that confuses me is that the import lib has symbols without underscore prefix (I see them when running `libunres -p mupen64plus.lib`), but when I change the function pointer declaration into a function declaration it tries to link "_CoreGetAPIVersions" and can't find it.

Yes, the underscore is normally prepended to cdecl functions on Windows. It makes no difference if your function prototypes are properly declared.

>
> I put the .dll's in the root of the project directory by the way. Does that count as a "system library search path"?

That depends. Is that the directory where your executable is written? Are you launching it from inside an Visual Studio or from the command line? Normally, the executable's directory is on the system path, but when using VS the default configuration is to write the executables into configuration-specific subdirectories.
February 09, 2018
On Friday, 9 February 2018 at 14:51:30 UTC, Mike Parker wrote:
> Where was the lib file located? Was it in the root project directory? How are you compiling your project? What does your directory tree look like?
> (...)
> That depends. Is that the directory where your executable is written? Are you launching it from inside an Visual Studio or from the command line? Normally, the executable's directory is on the system path, but when using VS the default configuration is to write the executables into configuration-specific subdirectories.

I use `dub run` from cmd or Mono-D. All .exe, .lib and .dll files are in the root. Source files are in the subdirectory 'source'.

I tried to reduce the problem by making a simple test folder containing the necessary .dll files, import library and source files:

freetype6.dll
libpng12.dll
libpng3.dll
main.d
main.exe
main.obj
mupen64plus-audio-sdl.dll
mupen64plus-input-sdl.dll
mupen64plus-rsp-hle.dll
mupen64plus-video-glide64mk2.dll
mupen64plus-video-rice.dll
mupen64plus.dll
mupen64plus.lib
SDL.dll
zlib1.dll

Where main.d is:
```
import std.stdio;

version(dynamiclink) {

	pragma(lib, "mupen64plus.lib");
	extern(C) nothrow @nogc {
		int CoreGetAPIVersions(int*, int*, int*, int*);
	}
	
	int main() {
		int a, b, c, d;
		CoreGetAPIVersions(&a, &b, &c, &d);
		writeln("Version: ", a, " - ", b, " - ", c, " - ", d);
		return 0;
	}
}

version(dynamicload) {

	import core.sys.windows.windows: LoadLibrary, GetProcAddress, GetLastError;
	extern(C) alias p_CoreGetAPIVersions = int function(int*, int*, int*, int*);
	p_CoreGetAPIVersions CoreGetAPIVersions;

	int main() {
		auto handle = LoadLibrary("mupen64plus.dll");
		CoreGetAPIVersions = cast(p_CoreGetAPIVersions) GetProcAddress(handle, "CoreGetAPIVersions");
		
		int a, b, c, d;
		CoreGetAPIVersions(&a, &b, &c, &d);
		writeln("Version: ", a, " - ", b, " - ", c, " - ", d);
		return 0;
	}
}
```

With `dmd main.d -version=dynamicload` it works, but with `dmd main.d -version=dynamiclink` it still gives this:

main.obj(main)
Error 42: Symbol Undefined _CoreGetAPIVersions
Error: linker exited with status 1
February 09, 2018
On 09-02-18 15:04, Dennis wrote:
> I read the Derelict documentation a while ago, I didn't grasp all of it. Reading it again, I can now make sense of it though. :)
> 
> On Friday, 9 February 2018 at 12:53:44 UTC, Mike Parker wrote:
>> Did you link with the library you created with implib? That linker error suggests you didn't.
> 
> I added `pragma(lib, "mupen64plus.lib");` above the extern(C) block.
> Adding `libs "mupen64plus"` to dub.sdl doesn't make a difference.
> 
> The thing that confuses me is that the import lib has symbols without underscore prefix (I see them when running `libunres -p mupen64plus.lib`), but when I change the function pointer declaration into a function declaration it tries to link "_CoreGetAPIVersions" and can't find it.
> 
> I put the .dll's in the root of the project directory by the way. Does that count as a "system library search path"?

You may need to pass `/s` to implib so it will add the underscore to the symbol in the import library. If it's actually needed depends on what the dll uses.

-- 
Mike Wey
February 09, 2018
On Friday, 9 February 2018 at 18:14:06 UTC, Mike Wey wrote:
> You may need to pass `/s` to implib so it will add the underscore to the symbol in the import library. If it's actually needed depends on what the dll uses.

That did it, now both dynamic loading and dynamic linking work. :) Thanks both of you.

I'd still like to find a nice way to generate the boilerplate code for dynamic loading, if I come up with something I'll post it here.
March 08, 2018
On Friday, 9 February 2018 at 20:19:33 UTC, Dennis wrote:
> I'd still like to find a nice way to generate the boilerplate code for dynamic loading, if I come up with something I'll post it here.

So I ended up using an import library for a while, but I then wanted to get the handle of the DLL, which I don't know how to do with an import library. I also didn't like the non-customizable Windows pop-ups that appear when the libraries can't be found so I finally wrote some mixin code to generate the boilerplate for dynamic loading:

https://gist.github.com/dkorpel/3bf108ca48cb43bdbe3cc8bf30405b4d

Now you only need to declare the functions in a Struct:
```
struct FunctionsInMyDLL {
	int getVersion();
	string getName();
	// more functions
}
```
And the templates do the rest. :)