MSVC linker seems link wrong symbol cause segmentation fault(without __declspec(dllimport))

Dun Liang 6 Reputation points
2021-09-07T04:55:52.08+00:00

I have two C++ sources (a.cc, b.cc), one will be compiled into a shared library(a.dll), other will be compiled into an executable(b.exe) and link a.dll, but MSVC linker didn't link symbol correctly in a.dll without __declspec(dllimport), here is the minimum reproduce:

// a.cc
extern "C" int mysym;
// extern "C" __declspec(dllexport) int mysym;

int mysym = 123;

// Compile command line:
// cl -std:c++17 -O2 -fp:fast -EHsc  -I"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include" -I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um" -DNOMINMAX  -LD .\a.cc -Fe:a.dll  -link -LIBPATH:"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\lib\x64" -LIBPATH:"C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x64" -LIBPATH:"C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\ucrt\x64" -EXPORT:mysym



// b.cc
#include <iostream>

using namespace std;

// this is ok
// extern "C" __declspec(dllimport) int mysym;

// this is wrong
extern "C" int mysym;

int main() {
    // expect output 123
    std::cerr << mysym << std::endl;
    std::cerr << &mysym << std::endl;
    return 0;
}

// Compile command line:
// cl -std:c++17 -O2 -fp:fast -EHsc .\b.cc a.lib -I"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include" -I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\um" -DNOMINMAX -Fe:b.exe  -link -LIBPATH:"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\lib\x64" -LIBPATH:"C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x64" -LIBPATH:"C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\ucrt\x64"

When I execute b.exe, the command outputs a random number, not 123, when I add __declspec(dllimport), the command works as expected.

To figure out why, I dump the log with -VERBOSE in b's build command, and found some clues:

// log without __declspec(dllimport)
Searching libraries
    searching a.lib:
      Found mysym
        ref in b.obj
        loaded a.lib(a.dll)

// log with __declspec(dllimport)
Searching libraries
    searching a.lib:
      Found mysym
        ref in a.exp
        loaded a.lib(a.dll)

It seems the symbol lookup is wrong, my questions are:

  1. If linker didn't found the symbol or symbol lookup is wrong, can it produces a link error, rather than link it to a randomly uninitialized address?
  2. Can I tell compiler or linker mysym needs __declspec(dllimport) without changing the source code? Because I have a large project, changing the source code is not possible.
Developer technologies | C++
{count} vote

1 answer

Sort by: Most helpful
  1. metablaster 91 Reputation points
    2021-09-07T07:08:55.827+00:00

    You need to guard your __declspec(...) declaration according to where it is needed.

    Create a new header file and name it Exports.h

    Put following sample code into it, (you'll understand why you need this later on):

    #pragma once
    
    // DLL export macros
    #ifdef COMPILE_DLL
    #define MY_API __declspec(dllexport)
    #elif defined (LINK_DLL)
    #define MY_API __declspec(dllimport)
    #else // Compile LIB or link LIB
    #define MY_API
    #endif // COMPILE_DLL
    

    Include that header into every header file in the DLL project that needs to export simbol.
    Now to export symbol in the DLL you simply put MY_API macro in front of a symbol, few examples:

    extern "C" MY_API int mysym; // exports mysim without name mangling
    MY_API int myint; // exports an int
    class MY_API ClassName {}; // exports a class
    

    Next step is to define the macro in project properties in the following location for both the DLL and EXE projects:

    Property manager (or right click your project and select properties) -> Common properties -> C\C++ -> Preprocessor

    If this is a "DLL" project input following macro:

    COMPILE_DLL
    

    If this is an "EXE" project that links the DLL input following macro instead:

    LINK_DLL
    

    You need to put appropriate macro for both the DLL and EXE project, then rebuild them.
    Notice that if you don't do this then your DLL will be treated as static library, this is intentional because it
    allows you build static library by simply not defining anything in preprocessor in project proeprties.

    As you can see, symbol is either imported or exported depending on macro definition in properties.
    Symbol needs to be exported only when building the DLL, and imported only when linking the DLL.

    Note that every DLL project needs to have it's own copy of Exports.h
    You need to rename MY_API according to DLL project name,

    For example if your dll project is called MySuperDll then rename macro to something like MY_SUPER_API,
    the whole point is that each DLL project must have it's own API export macro name.
    This allows to link multiple DLL's or to link DLL into another DLL optionally in combination with static libraries.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.