Search code examples
c++visual-c++linker

Use case of dllimport in VisualStudio


I always wondered what is the real use case of __declspec(dllimport). I know that building a shared library requires to export its symbols using __declspec(dllexport) and then the user of the library use theses symbols as __declspec(dllimport).

Then, you should build your shared library with a special define which enables dllexport and if the flag is not set, the symbols are defined as dllimport.

However, I never used dllimport at all and it just works.

I have two projects:

ImportExport

Has a small Util class which is build with EXPORTING defined

Util.h:

#ifndef _UTIL_H_
#define _UTIL_H_

#if defined(EXPORTING)
#  define EXPORT    __declspec(dllexport)
#else
#  define EXPORT    // I should use __declspec(dllimport) but client will try out
#endif

class EXPORT Util {
public:
    static void test();
};

#endif // !_UTIL_H_

Then in the source file Util.cpp:

#include <iostream>

#include "Util.h"

void Util::test()
{
    std::cout << "Testing..." << std::endl;
}

Nothing much complicated, as you can see, when the user will use this file, EXPORT will not be defined at all (where it should be defined to dllimport).

The client exe

Main.cpp:

#include <Util.h>

int main(void)
{
    Util::test();

    return 0;
}

Links to ImportExport.lib without any define set, just works. No undefined reference.

I wonder why is the use case of dllimport? Is it present for backward compatibility?

Note: All the code presented was tested on VisualStudio 2012 Express.


Solution

  • Raymond Chen describes the dll import mechanism in detail in this series; summing it up, dllimport for functions is essentially a performance optimization.

    If you don't mark a function as dllimport the compiler and the linker will treat it as a normal function, with "static" function calls resolving it to the stub found in the import library. The stub actually has to fetch the address of the imported function from the IAT and perform a jmp there (i.e. it has to somehow convert the direct call that the compiler generated to an indirect call), so there's some performance penalty in this two-step process.

    dllimport, instead, tells the compiler that it has to generate code for an indirect call via the IAT right from the compilation phase. This reduces the indirections and allows the compiler to cache (locally to the function) the target function address.

    Notice that, as MSDN says, you can omit dllimport only for functions; for data it's always necessary, because there's not a mechanism available for the linker to rework the direct accesses to variables generated by the compiler in indirect ones.

    (all this was particularly relevant in times of "classical" linkers; nowadays, with link-time code generation enabled, all these points can be worked around by simply letting the linker fully generate the function calls/data accesses)