Search code examples
visual-c++clrmanaged-c++

Linking static native library to managed C++ project pulls unused (and unexpected) dependencies in


Synopsis:

Managed (/clr) C++ project (.dll) statically links native C++ library (which is compiled with /MD). Static library is big and references a lot of other libraries, but functionality used by managed C++ code is trivial and shouldn't pull in any additional dependencies.

Problems:

  1. linking fails with LNK2001 and LNK2019 mentioning symbols that code definitely does not depend on
  2. even if I add required dependencies, switching toolset (e.g. migrating from VS2017 to VS2019) causes errors to come back (this time mentioning other dependencies)

What happens:

Apparently, /clr switch causes compiler to treat inlined functions differently -- they are no longer get embedded into .obj files (as "weak symbols"), instead they get referenced in the table of imports. This means linker has to find things like:

"public: virtual char const * __cdecl std::exception::what(void)const " (?what@exception@std@@UEBAPEBDXZ)

... which it does and (since CRT libraries are DEFAULTLIB and therefore are used last) typically it finds it in other library (namely, in aforementioned static native lib). So, it finds first .obj file in static lib that contains std::exception::what() and pulls it in -- meaning we now depend on everything said .obj depends. This explains problem #1 (bogus linker errors).

Now, if you compile your static lib with another toolset -- obj files can be stored in different order, causing problem #2.

To reproduce the issue you can use this code (make sure managed project links static lib):

//--------------------
// statlib.cpp
//
#include <exception>

void this_is_a_trap() { throw std::exception(); }

extern int bar();

int foo() { return bar(); }


//--------------------
// clrdll.cpp (managed code)
//
#include <exception>

__declspec(dllexport) void oops()
{
    throw std::exception();
}

If you link with /VERBOSE flag you'll see smth like:

1>    Searching C:\Program Files (x86)\Windows Kits\10\lib\10.0.17763.0\um\x64\oleaut32.lib:
1>    Searching C:\Program Files (x86)\Windows Kits\10\lib\10.0.17763.0\um\x64\uuid.lib:
1>    Searching C:\Program Files (x86)\Windows Kits\10\lib\10.0.17763.0\um\x64\odbc32.lib:
1>    Searching C:\Program Files (x86)\Windows Kits\10\lib\10.0.17763.0\um\x64\odbccp32.lib:
1>    Searching C:\tst\x64\Release\statlib.lib:
1>      Found "public: virtual char const * __cdecl std::exception::what(void)const " (?what@exception@std@@UEBAPEBDXZ)
1>        Referenced in clrdll.obj
1>        Loaded statlib.lib(statlib.obj)   <-- Now we depend on `bar()`

Question

What is the best way to deal with this?

Notes:

  • adding msvcrt.lib to linker inputs (before other static libs) helps, but not always -- certain symbols (such as std::bad_weak_ptr::what() aren't present in msvcrt.lib)

  • this problem is the root cause for this SO post


Solution

  • In mixed (/clr and native) code std::exception::what() (and other similar symbols) do get inlined, but these definitions are managed (not native). Which normally is not an issue, but native code refers to native definition via std::exception's vtable. Normally, such reference (once it fails to resolve) gets redirected to managed definition (which is generated, as stated above), but in this case -- native definition is found in another object (random object from native static library) before "redirection" kicks in, causing that object to be referenced.

    See details here. MS is figuring out a best way to deal with this.