1

error LNK2019: unresolved external symbol __imp_yourexternFunc

I have a C DLL function that is external called "output" which is similar to printf:

output( format , va_args);

In *.h files its declared:

__declspec( dllexport ) void output( LPCTSTR format, ... );
  or
__declspec( dllimport ) void output( LPCTSTR format, ... );   

(for *.h includes) there is a MACRO that selects between export/import base on usage

In my rust module I declare it extern as:

#[link(name="aDLL", kind="dylib")]
extern {
    fn output( format:LPCTSTR, ...);
}

The dumpbin for this function is as follows (from dumpbin)

 31    ?output@@YAXPEBDZZ (void __cdecl output(char const *,...))

But when I attempt to link this the rustc linker is prepending _imp to the function name:

second_rust_lib_v0.second_rust_lib_v0.ay01u8ua-cgu.6.rcgu.o : error LNK2019: unresolved external symbol __imp_output referenced in function print_something
Ross Youngblood
  • 502
  • 1
  • 3
  • 16
  • 1
    This is essentially the same as your [other question](https://stackoverflow.com/q/68289334/1889329). The TL;DR is: The C++ code uses C++ lineage, but Rust is defaulting to C linkage. You'll simply have to `extern "C"` your C++ code. – IInspectable Jul 08 '21 at 13:57

3 Answers3

1

On windows linking DLLs goes through a trampoline library (.lib file) which generates the right bindings. The convention for these is to prefix the function names with __imp__ (there is a related C++ answer).

There is an open issue that explains some of the difficulties creating and linking rust dlls under windows.

Here are the relevant bits:

If you start developing on Windows, Rust will produce a mylib.dll and mylib.dll.lib. To use this lib again from Rust you will have to specify #[link(name = "mylib.dll")], thus giving the impression that the full file name has to be specified. On Mac, however, #[link(name = "libmylib.dylib"] will fail (likewise Linux).

If you start developing on Mac and Linux, #[link(name = "mylib")] just works, giving you the impression Rust handles the name resolution (fully) automatically like other platforms that just require the base name.

In fact, the correct way to cross platform link against a dylib produced by Rust seems to be:

#[cfg_attr(all(target_os = "windows", target_env = "msvc"), link(name = "dylib.dll"))]
#[cfg_attr(not(all(target_os = "windows", target_env = "msvc")), link(name = "dylib"))]
extern "C" {}
Mathieu Rene
  • 924
  • 5
  • 11
  • 1
    The OP is asking for the opposite: Linking against a C library from Rust. – IInspectable Jul 08 '21 at 15:29
  • The answer IS helpful as it provides useful background on Windows DLL "trampoline" linking. It also provides some breadcrumbs about a linking issue I had trying to link to my created RUST library implictly as using #pragma comment (lib,"mylib") expects the rust build to produce a mylib.dll, mylib.exp and mylib.lib. So the .exp and .lib I had to manualy rename at the moment. – Ross Youngblood Jul 08 '21 at 23:25
1

Like your previous question you continue to ignore how compilers and linkers work. The two concepts you need to wrap your head around are these:

  • LPCTSTR is not a type. It is a preprocessor macro that expands to char const*, wchar_t const*, or __wchar_t const* if you are particularly unlucky. Either way, once the compiler is done, LPCTSTR is gone. Forever. It will not ever show up as a type even when using C++ name decoration.

    It is not a type, don't use it in places where only types are allowed.

  • Compilers support different types of language linkage for external symbols. While you insist to have a C DLL, you are in fact using C++ linkage. This is evidenced by the symbol assigned to the exported function. While C++ linkage is great in that it allows type information to be encoded in the decorated names, the name decoration scheme isn't standardized in any way, and varies widely across compilers and platforms. As such, it is useless when the goal is cross language interoperability (or any interoperability).


As explained in my previous answer, you will need to get rid of the LPCTSTR in your C (or C++) interface. That's non-negotiable. It must go, and unwittingly you have done that already. Since DUMPBIN understands MSVC's C++ name decoration scheme, it was able to turn this symbol

?output@@YAXPEBDZZ

into this code

void __cdecl output(char const *,...)

All type information is encoded in the decorated name, including the calling convention used. Take special note that the first formal parameter is of type char const *. That's fixed, set in stone, compiled into the DLL. There is no going back and changing your mind, so make sure your clients can't either.

You MUST change the signature of your C or C++ function. Pick either char const* or wchar_t const*. When it comes to strings in Rust on Windows there is no good option. Picking either one is the best you have.


The other issue you are going up against is insisting on having Rust come to terms with C++' language linkage. That isn't going to be an option until Standard C++ has formally standardized C++ language linkage. In statistics, this is called the "Impossible Event", so don't sink any more time into something that's not going to get you anywhere.

Instead, instruct your C or C++ library to export symbols using C language linkage by prepending an extern "C" specifier. While not formally specified either, most tools agree on a sufficiently large set of rules to be usable. Whether you like it or not, extern "C" is the only option we have when making compiled C or C++ code available to other languages (or C and C++, for that matter).

If for whatever reason you cannot use C language linkage (and frankly, since you are compiling C code I don't see a valid reason for that being the case) you could export from a DLL using a DEF file, giving you control over the names of the exported symbols. I don't see much benefit in using C++ language linkage, then throwing out all the benefits and pretend to the linker that this were C language linkage. I mean, why not just have the compiler do all that work instead?

Of course, if you are this desperately trying to avoid the solution, you could also follow the approach from your proposed answer, so long as you understand, why it works, when it stops working, and which new error mode you've introduced.

  • It works, in part by tricking the compiler, and in part by coincidence. The link_name = "?output@@YAXPEBDZZ" attribute tells the compiler to stop massaging the import symbol and instead use the provided name when requesting the linker to resolve symbols. This works by coincidence because Rust defaults to __cdecl which happens to be the calling convention for all variadic functions in C. Most functions in the Windows API use __stdcall, though. Now ironically, had you used C linkage instead, you would have lost all type information, but retained the calling convention in the name decoration. A mismatch between calling conventions would have thus been caught during linking. Another opportunity missed, oh well.

  • It stops working when you recompile your C DLL and define UNICODE or _UNICODE, because now the symbol has a different name, due to different types. It will also stop working when Microsoft ever decide to change their (undocumented) name decoration scheme. And it will certainly stop working when using a different C++ compiler.

  • The Rust implementation introduced a new error mode. Presumably, LPCTSTR is a type alias, gated by some sort of configuration. This allows clients to select, whether they want an output that accepts a *const u8 or *const u16. The library, though, is compiled to accept char const* only. Another mismatch opportunity introduced needlessly. There is no place for generic-text mappings in Windows code, and hasn't been for decades.


As always, a few words of caution: Trying to introduce Rust into a business that's squarely footed on C and C++ requires careful consideration. Someone doing that will need to be intimately familiar with C++ compilers, linkers, and Rust. I feel that you are struggling with all three of those, and fear that you are ultimately going to provide a disservice.

Consider whether you should be bringing someone in that is sufficiently experienced. You can either thank me later for the advice, or pay me to fill in that role.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • 1
    @ros In that case, the least you *can* do is get rid of the `LPCTSTR` in Rust. Since you have a pre-built library (presumably), that macro has been resolved. Given the C++ name decoration, it is a `char const*` now, so your Rust signature should be using `*const u8`. You can consider writing a C++ name decorator and implementing a derive macro instead of writing it symbol by hand. That ensures that the Rust signature and the import symbol match. Have the linker help you by doing the little it can. – IInspectable Jul 12 '21 at 15:21
  • Yes, a C++ name decorator is a possible path to reduce the maintenance headache of wrapping our entire native API. At the moment I think I need to choose a handful of API calls where RUST might be useful in a benchmark. I'm looking for a compute intensive task that could benefit from a multi-threaded RUST function. As the overhead and risk is high for such an application the cost benefits have to be significant to accept the risk (as you pointed out earlier). – Ross Youngblood Jul 12 '21 at 15:26
0

This is not my ideal answer, but it is how I solve the problem.

What I'm still looking for is a way to get the Microsoft Linker (I believe) to output full verbosity in the rust build as it can do when doing C++ builds. There are options to the build that might trigger this but I haven't found them yet. That plus this name munging in maybe 80% less text than I write here would be an ideal answer I think.

The users.rust-lang.org user chrefr helped by asking some clarifying questiongs which jogged my brain. He mentioned that "name mangling schema is unspecified in C++" which was my aha moment.

I was trying to force RUST to make the RUST linker look for my external output() API function, expecting it to look for the mangled name, as the native API call I am accessing was not declared with "cdecl" to prevent name mangling.

I simply forced RUST to use the mangled name I found with dumpbin.hex (code below) What I was hoping for as an answer was a way to get linker.exe to output all the symbols it is looking for. Which would have been "output" which was what the compiler error was stating. I was thinking it was looking for a mangled name and wanted to compare the two mangled names by getting the microsoft linker to output what it was attempting to match. So my solution was to use the dumpbin munged name in my #[link] directive:

//#[link(name="myNativeLib")]
//#[link(name="myNativeLib", kind="dylib")]  // prepends _imp to symbol below
#[link(name="myNativeLib", kind="static")]   // I'm linking with a DLL
extern {
//#[link_name = "output"]
#[link_name = "?output@@YAXPEBDZZ"]   // Name found via DUMPBIN.exe /Exports
    fn output( format:LPCTSTR, ...);
}

Although I have access to sources of myNativeLib, these are not distributed, and not going to change. The *.lib and *.exp are only available internally, so long term I will need a solution to bind to these modules that only relys on the *.dll being present. That suggests I might need to dynamically load the DLL instead of doing what I consider "implicit" linking of the DLL. As I suspect rust is looking just at the *.lib module to resolve the symbols. I need a kind="dylibOnly" for Windows DLLS that are distributed without *.lib and *.exp modules.

But for the moment I was able to get all my link issues resolved. I can now call my RUST DLL from a VS2019 Platform Toolset V142 "main" and the RUST DLL can call a 'C' DLL function "output" and the data goes to the propriatary stream that the native "output" function was designed to send data to.

There were several hoops involved but generally cargo/rustc/cbindgen worked well for this newbie. Now I'm trying to condsider any compute intensive task where multithreading is being avoided in 'C' that could be safely implemented in RUST which could be bencmarked to illustrate all this pain is worthwhile.

Ross Youngblood
  • 502
  • 1
  • 3
  • 16