Windows imported DLL function calls in C++ and Rust

I'm trying to understand why C++ and Rust produce different code in this case. In C++ when you call an imported function it simply produces a call using the relevant pointer in the .idata section:

call    cs:__imp_MessageBoxW

But in Rust there's an added layer of indirection. The same call results in:

call    MessageBoxW_0

Where MessageBoxW_0 is a function that jumps to the function pointed at by __imp_MessageBoxW:

jmp     cs:__imp_MessageBoxW

Why is this?

Huh. In trying to come up with a simple test case it would appear it depends on exactly how it's linked. Using #[link] directly produces the same results as C++. So nothing to see here, I guess.

Edit: but for my own reference, there's three ways to specify a link:

// 1. Compiled with `-l user32`
extern "system" {
    fn MessageBoxA(handle: *const c_void, text: *const u8, caption: *const u8, options: u32) -> i32;
}

// 2. Link attribute is directly applied to the function's extern block.
#[link(name="user32")]
extern "system" {
    fn MessageBoxA(handle: *const c_void, text: *const u8, caption: *const u8, options: u32) -> i32;
}

// 3. Link attribute is specified on a different extern block.
#[link(name="user32")]
extern "system" {}
extern "system" {
    fn MessageBoxA(handle: *const c_void, text: *const u8, caption: *const u8, options: u32) -> i32;
}

Only the second results in a direct call. It makes sense that #1 and #3 behave the same way but #3's behaviour may be unexpected considering the Rust Reference says:

It is valid to add the link attribute on an empty extern block. You can use this to satisfy the linking requirements of extern blocks elsewhere in your code (including upstream crates) instead of adding the attribute to each extern block.

Which is true as far as it goes but misses that it's not quite equivalent (at least on Windows).

3 Likes

In case #2 the compiler knows the function comes from a DLL and uses the mechanism used to call dynamically linked functions.

In case #1 and #3 the function could also be defined in a static library and so it has to emit a generic function call that resolves to a function in the import library that then uses the mechanism to do tail calls to dynamically linked function.

It looks like the Windows linker, object and relocation formats don't support changing the call instruction at link time (which I guess is because it would require to add extra NOPs or have a section split after the call).

The comment should probably be fixed to make this clearer.

2 Likes

Thanks. The reason I started down this rabbit hole was due to libstd. The Windows API functions have no link attribute. I'm not sure how much of an issue that is but is it something that should, in principle, be changed?