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.
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)