Best way to access DLL functions in Rust?


#1

I have a Windows DLL (not part of the Win32 API; it is a commercial product for which I have a license). In Python I access it using the ctypes module.

I can see that rust has an ffi module, but I’m wondering if there’s a mature crate that would be higher-level? Note that I want to load the DLL at runtime to access its functions.

In a previous post Michael-F-Bryan (who’s already helped me with other things on this list) gives a link to his FFI tutorial, which is in the first edn. of the book. Is that info. still valid?


#2

My understanding is that the First Edition of the book is still valid - the second edition is just a renewed look at how to teach the language… I would follow the FFI tutorial - look at the Linking section. It does a good job of explaining what you have to do.

You should check out bindgen which /may/ be able to automatically generate FFI bindings if you have the headers you would typically use in a C or C++ application.


#3

Sounds like you’re looking for the libloading crate :wink:

The FFI page in the first version is still valid, although it deals more with using a DLL alongside dynamic linking (i.e. we resolve all DLL symbols when an executable is first loaded into memory). But libloading is probably the thing you’re looking for.

I also maintain a more in-depth guide of Rust FFI. In particular, there’s a chapter on loading libraries at runtime which you may find useful.


#4

Thanks, I’ve now skimmed that & will now read it properly.

I don’t need/want auto-generated bindings. However, I do need to use the RAII pattern, but I know that’s covered in the manual with the Drop trait.

Oh, I didn’t see Michael’s reply: thanks, I’ll read your Dynamic Loading docs & look up libloading.


#5

Just musing, but something that’s nice in C++ is that if you must load your library dynamically at runtime, but already have a header file statically declaring the functions the library exports, you can write some code like this:

auto my_function = reinterpret_cast<decltype(MyFunction)*>(GetProcAddress(dll, "MyFunction"));

Whereas it seems in Rust you have to essentially declare the functions one way for FFI and another way for dynamic library loading.


#6

I don’t think there’s a great ready-made solution here currently. bindgen is definitely great for turning C headers into Rust FFI bindings, it’s used all over the place (including in Firefox). I don’t think there are great solutions for run-time dynamic linking, though. We have a fork of libudev-sys in Firefox and it defines some macros to define the APIs and lazy_static accessors for them, which apparently works well enough that we’re able to use the libudev crate straight from crates.io and just cargo patch our fork of libudev-sys in.

I didn’t know about libloading before, that sounds really useful! I wonder if it’d be worthwhile to add a mode to bindgen to make it produce bindings that used libloading to allow runtime-linking against libraries without doing all the work yourself?


#7

One analogy for static/dynamic linking (which is done at compile or load time) and loading a library at runtime is how we use static or dynamic dispatch for polymorphism.

With static dispatch, the compiler knows everything about all types involved. Whereas in dynamic dispatch you use trait objects, which is a different mechanism altogether and allows the underlying type to be anything.

In the same way, with static/dynamic linking you use the compiler to verify function definitions are consistent (hence the need for bindgen) and then the linker resolves all the symbols. When loading libraries at runtime we’re essentially plucking a random symbol out of a binary then casting it to a function pointer with the signature we want (i.e. all we need to know is the signature).

Both systems use different mechanisms under the hood, with inherently different amounts of flexibility or information available, so it kinda makes sense that linking and loading are done differently.


#8

This sounds a lot like an import library. The idea is you generate a tiny library which contains a bunch of static function pointers (one for each function in the DLL), then there’s an initialisation routine which will take your DLL and set all the function pointers to their corresponding place in the loaded library.

The import library then acts like a trampoline so you link against it at compile time to resolve symbols and get the right function signatures, allowing you to maintain proper type safety. While you still have the flexibility to swap out the code actually being used at runtime.


#9

Yeah, it totally is! The only real difference is whether you let the dynamic linker do that work or not. If you are OK with your binary being dynamically linked against the shared library then you can basically just use bindgen and #[link(name = "whatever")] and that will all work. The original poster here wanted to be able to load a shared library at runtime, which our code in Firefox also does. I don’t know their motivation, but for Firefox we didn’t want a hard dependency on libudev because it’s not always installed on Linux distros, and so you’d get an error launching Firefox if it was missing. It’s a little more work to do things this way, and you have to have runtime checks that the library exists, but it’s nice for optional functionality that you can skip in some cases.


#10

Just a tiny followup. I found that using sharedlib was a lot easier than libloading. In particular it made it straighforward for me to create a struct representing the DLL I want to use and its methods.