【问题标题】:.def files C/C++ DLLs.def 文件 C/C++ DLL
【发布时间】:2010-09-26 20:16:11
【问题描述】:

我不明白将 .def 文件与 DLL 一起使用的意义。

似乎它取代了在您的 DLL 代码中使用显式导出的需要(即显式 __declspec(dllexport))但是当我不使用这些时我无法生成 lib 文件,然后在使用 DLL 时会产生链接器问题.

那么在与客户端应用程序链接时如何使用 .defs,它们是否取代了使用头文件或 .lib 文件的需要?

【问题讨论】:

    标签: c++ c dll


    【解决方案1】:

    我发现同时使用 __declspec(dllexport) 和 .def 文件有助于创建可移植 DLL,即可以从使用不同编译器或不同编译器设置编译的代码调用的 DLL。

    只需将 __declspec(dllexport) 放在您的函数声明中,就会导致这些函数被您的 DLL(至少在 Windows 上)“导出”,以便可以从 DLL 外部调用它们。

    但是,将列出所有导出函数的 .def 文件添加到构建中可以让您阻止 Microsoft 编译器(例如)将前导下划线和尾随参数宽度信息添加到导出函数名称(至少在组合时)使用 __stdcall 指令,对于可移植性也很有用)。例如。函数声明

    void foo(int i);
    

    如果您不注意调用约定和 .def 文件的使用,最终可能会导出为“_foo@4”。

    在将 GetProcAddress() 调用作为在运行时显式加载和挂钩到 DLL 的一部分时,保持符号表中导出的函数名称不受此类名称修饰非常方便。即在运行时获取指向上述函数 foo() 的指针(假设它已被导出),理想情况下您只想调用:

    HANDLE dllHandle = LoadLibrary("mydll.dll");
    void* fooFcnPtr = GetProcAddress(dllHandle, "foo");
    

    当然还有一些适当的错误案例检查!

    在构建 DLL 时在函数声明中使用 .def 文件以及 __stdcall、__declspec(dllexport) 和 extern "C" 将确保上述客户端代码适用于各种编译器和编译器设置。

    【讨论】:

    • 为了防止 C++ 函数的名称混淆,您应该将它们声明为 extern "C"
    • @sbi, extern "C" 不会完全阻止名称重整,它只会阻止 C++ 名称重整,留下 C 重整。例如,MSVC 32 位编译器为 stdcallfastcall 函数添加所有这些 @ 装饰,即使使用 extern "C"
    • .def 文件的另一种替代方法是使用#pragma comment(linker, ...),例如如here 所述
    【解决方案2】:

    我的理解是 .def 文件提供了 __declspec(dllexport) 语法的替代方案,另外还有一个好处是能够显式指定导出函数的序号。如果您仅按序号导出某些函数,这可能会很有用,这不会透露有关函数本身的太多信息(例如:许多操作系统内部 DLL 的导出函数仅按序号)。

    请参阅reference page

    请注意,.def 文件中的名称必须与二进制文件中的名称匹配。因此,如果您将 C 或 C++ 与 'extern "C" { ... }' 一起使用,则名称不会被破坏;否则,您必须为用于生成 DLL 的特定版本的编译器使用正确的名称。 __declspec() 函数会自动完成这一切。

    【讨论】:

    • 正如 M. Clooney 所说:“还有什么?”。 +1。作为一名 Windows 开发人员,我的经验是 DEF 文件是“旧方式”,应该被弃用(如果还没有的话)。
    • 是的,.def 文件早于 __declspec 语言扩展。您可以在 .def 文件中执行任何使用 __declspec 或 #pragmas 都无法执行的操作。这包括花哨的链接器技巧,例如重命名或转发 API。
    【解决方案3】:

    对于那些仍然感兴趣的人...为了能够链接到 dll 和 def 文件,您还需要一个 lib 文件。在 Windows 中,这可以使用“LIB”工具从 def 中创建。有关执行此操作的命令行方式的示例,请参见下文。

    lib /machine:i386 /def:sqlite3.def
    

    希望这对其他人有所帮助。

    【讨论】:

    • 其实这行不通。起作用的是以下命令:lib /DEF:sqlite3.def /MACHINE:X86 /SUBSYSTEM:WINDOWS /LTCG /NOLOGO。生成的 .lib 可以很容易地用于动态链接 sqlite3.dll。
    【解决方案4】:

    .DEF 文件在 16 位窗口中更为常见,它们通常是指定应导出哪些符号的唯一方法。

    此外,它们提供了一种按序数值(@1、@2 等)而不是名称指定导出的方法。当性能非常重要时使用这种查找符号的方法,例如在视频驱动程序中。

    【讨论】:

      【解决方案5】:

      我没有过多地使用 DLL,但我的理解是,对于导出的 C++ 函数,您应该使用“__declspec(dllexport)”,而对于导出的 C 函数,您应该编写 .def 文件。这可能是因为 C++ 函数支持重载,而 C 函数不支持。

      【讨论】:

      • 是的。重载能力 = 在 .def 文件中指定令人讨厌的修饰名称。这里 C 和 C++ 之间没有根本区别,但在人体工程学和文化上存在相当大的差异。
      【解决方案6】:

      我的理解是.def文件实际上并没有指定所有api需要导出。它只包含导出的 api 及其序号。如果要实际导出特定的 api,则需要在 api 的定义中指定 __declspec(dllexport) 并在声明中指定 __declspec(dllimport)。

      def 文件的优点是,它可以帮助您保持与已经实现的 dll 的反向兼容性。即它维护 apis 的序数。假设您在 dll 中添加了一个新的 api,然后链接器查看您的 .def 文件为新 wapi 生成序号,以便旧 api 的序号保持不变。

      所以,如果客户端代码使用最新的dll,它不会破坏现有的api。

      【讨论】:

      • OpenSSL 是一个示例项目,它的源代码中没有__declspec(dllexport),而只有一个用于导出的.def 文件。所以我认为你的第一段是不正确的。
      猜你喜欢
      • 2010-09-06
      • 2010-09-16
      • 1970-01-01
      • 2021-07-26
      • 2010-10-10
      • 1970-01-01
      • 1970-01-01
      • 2014-04-26
      相关资源
      最近更新 更多