www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Elegant way to use dynamic bindings

reply Dennis <dkorpel gmail.com> writes:
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?
Feb 09 2018
next sibling parent Mike Parker <aldacron gmail.com> writes:
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(); }
Feb 09 2018
prev sibling parent reply Mike Parker <aldacron gmail.com> writes:
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.
Feb 09 2018
parent reply Dennis <dkorpel gmail.com> writes:
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"?
Feb 09 2018
next sibling parent reply Mike Parker <aldacron gmail.com> writes:
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.
Feb 09 2018
parent Dennis <dkorpel gmail.com> writes:
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
Feb 09 2018
prev sibling parent reply Mike Wey <mike-wey example.com> writes:
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
Feb 09 2018
parent reply Dennis <dkorpel gmail.com> writes:
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.
Feb 09 2018
parent Dennis <dkorpel gmail.com> writes:
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. :)
Mar 08 2018