Unable to export unmangled dll entry points for stdcall
Hi.
After some questions on IRC I think this might be a bug or at least a missing feature: I'd like to export functions with a name of my choosing. I knew about #[no_mangle] and learned about #[export_name] on IRC, but both fail to work in my case.
Code/test.rs:
pub extern "stdcall" fn stdcall() {
}
#[no_mangle]
pub extern "stdcall" fn stdcall_nomangle() {
}
#[export_name="stdcall_export"]
pub extern "stdcall" fn stdcall_exportname() {
}
pub extern "C" fn c() {
}
#[no_mangle]
pub extern "C" fn c_nomangle() {
}
#[export_name="c_export"]
pub extern "C" fn c_exportname() {
}
Build with rustc --cratetype dylib test.rs
Result:

So, every single stdcall entry point follows the name@sizeofarguments convention. I have a number of DLLs right in front of me (not based on rust) that export stdcall entries and DON'T use that convention. In fact, I'm reasonably sure that most of the windows API is both using stdcall and exporting 'unmangled' names.
Can I create something similar with rust? Am I missing another attribute? Should no_mangle work in these cases? Should export_name?
All dllexport'ed functions are usually "mangled" in that way. It's not rust-specific; gcc does same and I think msvc link.exe does too. It's usually not an issue, but it becomes an issue when you want to use
GetProcAddressor want to provide platform-agnostic symbol names. In this case, you have to create.deffile then pass it to linker. See this example. However there is a bug: dllexport doesn't work well if there is "explicit" dllexport.klutzy/defunct at 2014-10-23 15:27:58
This is a LLVM issue - it mangles stdcall symbols so that the
@Nsuffix reflects the correct number of bytes in arguments (for MSVC compatibility, I guess). It is however possible to bypass mangling by starting symbol name with\x01. Try this:#[export_name="\x01_stdcall_export"] pub extern "stdcall" fn stdcall_exportname() { }vadimcn at 2014-11-12 03:34:41
An updated command line,
--crate-type, gives$ nm libhello.so | grep stdcall 0000000000086630 T stdcall_export 0000000000086610 T stdcall_nomangle 0000000000086620 t _ZN16stdcall_nomangle10__rust_abiE 0000000000086640 t _ZN18stdcall_exportname10__rust_abiE 0000000000086600 t _ZN7stdcall10__rust_abiE 00000000000865f0 T _ZN7stdcall20h92816bbf6494be08eaaEI am not an expert on stdcall, but it would seem that the
@stuff is gone now. I guess this got fixed/modified upstream in the last year?EDIT: Also, I did do this on Linux, as I assumed that the ABI is the important part here, not Windows specifically. That could be a wrong assumption, I am terrible at Windows stuff :(
Steve Klabnik at 2015-12-31 19:10:29
I am not an expert on stdcall, but it would seem that the @ stuff is gone now.
The
@stuff seems still there for me withrustc 1.7.0 (a5d1e7a59 2016-02-29)on Windows. @vadimcn's\x01trick worked.Bruno Bigras at 2016-03-18 19:36:30
What would be cool is if, for the
cdylibcrate type, Rust would automatically generate a.deffile that mapped from the decorated versions to the undecorated versions so the import library would have the decorated versions as the linker expects, while the DLL itself has the undecorated versions makingGetProcAddresseasy.Peter Atashian at 2016-06-27 00:11:33
I have inverse problem: I try to set exported name with "@" symbol. In my case I want to export function with name: "_AbortCompilerPass@4" on x86 and "AbortCompilerPass" on amd64.
So, with gnu toolchain I create fork like:
#[cfg(target_pointer_width = "32")] #[export_name = "_AbortCompilerPass"] pub extern "stdcall" fn abort_compiler_pass_extern(how: winapi::DWORD) { abort_compiler_pass(how)} #[cfg(target_pointer_width = "64")] #[export_name = "AbortCompilerPass"] pub extern "stdcall" fn abort_compiler_pass_extern(how: winapi::DWORD) { abort_compiler_pass(how)}And it works correctly. This code also works with msvc amd64 toolchain.
But on msvc x86 toolchain I can't find way to get exported name like "_AbortCompilerPass@4".
I try to use:
- #[export_name = "_AbortCompilerPass@4"]
- #[link_args = "/EXPORT:_AbortCompilerPass@4=_AbortCompilerPass"]
But MS linker ignored all @4 in all variants :(
Artem V. Navrotskiy at 2016-09-22 13:20:49
I created simple example with my issue: https://github.com/bozaro/rust-msvcdll It also contains test, so you can run:
cargo testFor toolchains:
- stable-i686-pc-windows-msvc - FAIL
- stable-i686-pc-windows-gnu - OK
- stable-x86_64-pc-windows-msvc - OK
- stable-x86_64-pc-windows-gnu - OK
I try to implement wrapper for Visual Studio compiler plugin:
%VS120COMNTOOLS%\..\..\VC\bin\c2.dllAlso looks like removing
@4is much simple, then adding:link /dll test.obj /export:"_foobar=_foo@4"- add export_foo@4as_foobarlink /dll test.obj /export:"_foobar@4=_foo@4"- add export_foo@4as_foobar@4link /dll test.obj /export:"_foo@4=_foobar"- add export_foobaras_foo(without@4)
Artem V. Navrotskiy at 2016-09-22 13:41:21
Also in ideal world I want one of two options:
- Set base name in export_name (like:
#[export_name = "AbortCompilerPass"]) and it works as expected on all toolchains (on x86_64 and i686); - Set exactly full name in export_name (like:
#[export_name = "_AbortCompilerPass@4"]) for x86_64 and i686 separately without dark magic.
At this moment:
- I can't set correct export name on msvc i686 toolchain because I can't set full name to
export_nameoption; - I need set before-
@prefix "_AbortCompilerPass" for i686 platform instead base function name.
Artem V. Navrotskiy at 2016-09-22 14:06:15
- Set base name in export_name (like:
@bozaro I think the
@4is added by LLVM automatically (or maybe the linker?). To work around this you could try:#[export_name = "\x01AbortCompilerPass"]The leading "1 byte" tells LLVM to disable all name mangling.
Alex Crichton at 2016-09-26 20:55:28
@alexcrichton: @bozaro problem is the opposite. Currently (rust 1.11)
@Nis not added bystable-i686-pc-windows-msvctoolchain, though 32-bit stdcall calling convention expects it.Marat Radchenko at 2016-09-26 21:04:12
Here's an update on what the current situation is for the example in the original post using
i686-pc-windows-msvc. This is the resulting import library:Version : 0 Machine : 14C (x86) TimeDateStamp: 57E9902F Mon Sep 26 17:16:31 2016 SizeOfData : 00000015 DLL name : cdylib.dll Symbol name : _c_export Type : code Name type : no prefix Hint : 9 Name : c_export Version : 0 Machine : 14C (x86) TimeDateStamp: 57E9902F Mon Sep 26 17:16:31 2016 SizeOfData : 00000017 DLL name : cdylib.dll Symbol name : _c_nomangle Type : code Name type : no prefix Hint : 10 Name : c_nomangle Version : 0 Machine : 14C (x86) TimeDateStamp: 57E9902F Mon Sep 26 17:16:31 2016 SizeOfData : 0000001D DLL name : cdylib.dll Symbol name : _stdcall_export@0 Type : code Name type : undecorate Hint : 11 Name : stdcall_export Version : 0 Machine : 14C (x86) TimeDateStamp: 57E9902F Mon Sep 26 17:16:31 2016 SizeOfData : 0000001F DLL name : cdylib.dll Symbol name : _stdcall_nomangle@0 Type : code Name type : undecorate Hint : 12 Name : stdcall_nomangleThis is what is exported from the DLL itself:
This looks like the correct behavior to me, aside from those __rustfunctions which really shouldn't be exported. The original issue as stated has been fixed.Now in @bozaro 's case,
c2.dlldoes have a decorated name, and I'm not really sure how to make it work.Peter Atashian at 2016-09-26 21:33:08
Oh! Sorry I misinterpreted this issue totally. This is probably because we don't use
dllexportbut instead pass a list of symbols to the linker that need to get exported. In that list we don't include anything with@N, and we may need to do that to get these symbols exported.Alex Crichton at 2016-09-27 00:40:29
@alexcrichton If we want to do this properly, we'll need to distinguish between the name exported from the import library and the name exported from the DLL itself, and unfortunately
#[export_name]is only a single attribute (and I don't really have any faith in Rust doing things properly). I'd argue our current situation where we pass a DEF file to the linker with undecorated symbols is the best default as it results in decorated names in the import library for linking purposes while the DLL itself exports undecorated names to makeGetProcAddresseasy.Peter Atashian at 2016-09-27 00:51:53
Triage: no changes I'm aware of
Steve Klabnik at 2017-09-30 16:14:01
was this ever fixed?
snek at 2018-06-19 00:21:31
^ having this issue and cant find a working workaround, tried everything from renaming to using \x01 am at a loss spent too long on this ;-;
Ahriana Jackson at 2018-06-19 00:45:34
Just got bitten by this.
Nico OrrĂ¹ at 2018-10-02 20:40:27
If you need to do some very specific renaming (adding the "@12", etc), you can use this workaround in
.cargo/configIf overrides the
.deffile that rustc generates with a custom one (see https://docs.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-def-files?view=vs-2019).This isn't without downsides: It'll override any transitive exports from dependencies your library has, unless you explicitly re-add them. As such, this might not work for every use case. This is undoubtedly an unstable hack around the problem. It also only works with MSVC
[target.i686-pc-windows-msvc] rustflags = [ # Allows us to export the correct stdcall symbol names for Windows 32-bit binaries. # Rust has no way to explicitly export a symbol named "_RVExtension@12", it cuts off the @12. # This overrides the linker's /DEF argument, to force it to export the symbols we want. "-Clink-arg=/DEF:Win32.def", # Generate a map file so we can see what symbols exist, and what we're exporting. (NOT REQUIRED) "-Clink-arg=/MAP:SymbolInfo.map", # Add exported symbol info to the map file (NOT REQUIRED) "-Clink-arg=/MAPINFO:EXPORTS" ]Contents of Win32.def are exports which exactly match the private symbols rustc produced (visible in that map file that gets dumped)
LIBRARY EXPORTS _RVExtensionVersion@8 _RVExtension@12 _RVExtensionArgs@20Spoffy at 2020-07-15 20:25:10