为什么/何时不需要使用 __declspec( dllimport )?

45
在一个使用 server.dll 和 client.exe 的项目中,我从 server dll 中使用了 dllexport 导出了一个 server 符号,并且没有将其用 dllimport 导入到 client exe 中。
尽管如此,该应用程序链接并启动时都没有出现任何问题。因此,dllimport 不需要使用吗?
详细信息:
我有这个“server”dll:
// server.h
#ifdef SERVER_EXPORTS
  #define SERVER_API __declspec(dllexport)
#else
  #define SERVER_API // =====> not using dllimport!
#endif
class  SERVER_API CServer {
   static long s;
   public:
   CServer();
};

// server.cpp
CServer::CServer(){}

long CServer::s;

还有这个客户端可执行文件:

#include <server.h>
int main() {
   CServer s;
}

服务器的命令行:

cl.exe /Od  /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_USRDLL" 
 /D "SERVER_EXPORTS" /D "_UNICODE" /D "UNICODE" /D "_WINDLL" 
 /Gm /EHsc /RTC1 /MDd /Yu"stdafx.h" 
 /Fp"Debug\server.pch" /Fo"Debug\\" /Fd"Debug\vc80.pdb" 
 /W3 /nologo /c /Wp64 /ZI /TP /errorReport:prompt

cl.exe /OUT:"U:\libs\Debug\server.dll" /INCREMENTAL:NO /NOLOGO /DLL 
/MANIFEST /MANIFESTFILE:"Debug\server.dll.intermediate.manifest" 
/DEBUG /PDB:"u:\libs\Debug\server.pdb" 
/SUBSYSTEM:WINDOWS /MACHINE:X86 /ERRORREPORT:PROMPT 
kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib 
shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib

客户端命令行:

cl.exe /Od /I "..\server" 
 /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" 
 /Gm /EHsc /RTC1 /MDd /Fo"Debug\\" /Fd"Debug\vc80.pdb" /W3 /c /Wp64 /ZI /TP 
 .\client.cpp

cl.exe /OUT:"U:\libs\Debug\Debug\client.exe" /INCREMENTAL 
/LIBPATH:"U:\libs\Debug" 
/MANIFEST /MANIFESTFILE:"Debug\client.exe.intermediate.manifest" 
/DEBUG /PDB:"u:\libs\debug\debug\client.pdb" 
/SUBSYSTEM:CONSOLE /MACHINE:X86 
server.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib 
advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib

1
其实是个好问题。MSDN文档 --- http://msdn.microsoft.com/en-us/library/3y1sfaz2(VS.80).aspx --- 没有告诉我使用extern(带有正确的调用约定和名称重整)和指定导入库是否有任何好处。 - peterchen
3
在类和类成员上使用__declspec(dllexport)非常脆弱。单独的server.dll是什么目的?实际上,类上的__declspec(dllexport)唯一做得好的事情就是在与/delayload:server.dll配对时减少进程启动I/O。任何其他认为的优点(例如想象中的能够在不重新编译应用程序的情况下修补DLL逻辑的能力)都违反了One-Definition-Rule且不可靠。 - Ben Voigt
3
“解耦合”是好的,实际上源代码可以进行解耦。但是,在类和类成员上使用“__declspec(dllexport)”会导致二进制文件紧密耦合。换句话说,如果在任何地方都不使用“__declspec(dllexport)”,而是使用静态库,就可以达到相同程度的耦合,而且部署负担要少得多。 - Ben Voigt
3
@Sergey: 你最好将所有代码放在一个单独的.DLL模块中,只导出对应于7个应用程序的“main”函数的普通C函数(甚至是单个.exe文件的参数,就像busybox一样)。至少在我们所讨论的Windows平台上,Qt不提供二进制兼容性。您必须使用特定编译器和命令行选项构建Qt库,以避免违反ODR。在这种情况下,您最好使用静态库。 - Ben Voigt
2
@marshalcraft 这不是博客,请把你的怒气发泄留给其他地方。谢谢 :)。 - xtofl
显示剩余6条评论
2个回答

60

__declspec(dllimport) 是一个客户端 MSVC 属性,可以用于导入的代码和数据。

对于代码而言,这不是必需的。这是一种优化,一个客户端编译器提示,说明函数调用不是直接的,而是导入的。一个名为 foo() 的函数的导入函数指针将会是 __imp_foo。没有这个提示,一个thunk被创建来加载 __imp_foo 中的地址并跳转到它。有了这个提示,thunk 就被跳过了,通过 IAT1 条目生成了一个间接调用,即 thunk 被内嵌了。这是一个时间优化,而不是空间。

对于从 DLL 中导入的数据,这是必需的。

这篇博客文章详细介绍了这一点。

1: 程序的导入地址表


6
从链接的文章中: <quote>对于数据,它需要保证正确性。</quote> - Ben Voigt
谢谢!你指出了正确的博客文章。不过,你的措辞有点令人困惑 - 我原以为_导入_源代码会导致在_导入_代码中调用__imp_foo的thunk。 - xtofl
我已根据博客文章修复了细节。dllimport 完全在客户端,具有 IAT 而不是 DLL 端,因为它只是导出。感谢您的答案和链接! - legends2k
对于感兴趣的人,翻译一下 Raymond Chen 在 The Old New Thing 上关于 dllimport 的文章。 - legends2k

2

我也对这个有疑问。我还删除了__declspec(dllimport)指令,非常惊讶地看到一个依赖于另一个dll(glib)中函数的dll(gmodule)在没有问题的情况下被编译和运行(特别是在wireshark中)。以下是MS的一句话:

__declspec(dllimport) is ALWAYS required to access exported DLL data.

不知道为什么微软会这样说,因为在其他页面上他们声明该指令是不必要的。无论如何,我的库不仅可以在没有dllimport的情况下运行,而且我已经很久没有看到“__imp”符号了,而以前我经常会碰到它(或它碰到我)。它到底发生了什么?答案在这里:

That's why using __declspec(dllimport) is better: because the linker doesn't generate a thunk if it's not required. There's no thunk and no jmp instruction, so the code is smaller and faster. You can also get the same effect WITHOUT __declspec(dllimport) by using whole program optimization. For more information, see /GL (Whole Program Optimization).

现在有意义了。我在所有项目中都使用/GL(+ /LTCG)。这就是这个主题问题的答案。
when is __declspec( dllimport ) not needed?

当使用整个程序优化时。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接