【问题标题】:What does `Exporting a function from a DLL` mean?“从 DLL 导出函数”是什么意思?
【发布时间】:2010-12-28 16:43:28
【问题描述】:

我对 dll 了解不多。我正在读一本关于 COM 的书。其中作者指的是Exporting a function from a DLL。他告诉了怎么做,但他没有告诉它是什么或为什么要这样做?

他建议的方法是: a) 用extern "C" 标记函数(不知道为什么?) b) 创建一个 DEF 文件并在此 DEF 文件的 EXPORTS 部分添加函数名称。 再次不知道为什么以及那里到底发生了什么?

我不明白的另一件事是术语symbols/symbol table

  1. Exporting a function from a DLL 是什么意思?
  2. 什么是symbols/symbol table

如果有人能用简单、清晰和详细的术语解释我,我将不胜感激。任何网络链接或教程也非常受欢迎。

编辑:

我在 .NET 中使用过 DLL。在其中我只包括使用命名空间行并将 dll 添加到引用中并且它们工作。这是我知道如何使用 dll 的唯一方法。我不知道在 .net 中使用 dll 与在 COM 中使用它们有何不同。任何人都可以与.NET 相关吗?

【问题讨论】:

标签: .net com dll


【解决方案1】:

.NET 中的 DLL 与本机 DLL 完全不同。 .NET DLL 包含称为 CIL 的字节码,它为其他程序(例如 csc 编译器)提供了足够的信息来计算其中包含的类、类型、接口等。

这与本地 DLL 完全不同。本机 DLL 包含二进制指令和大部分非结构化数据,并且(通常)无法计算出数据 的含义 - 例如,DLL 中的某处可能是两个字节(十六进制)@ 987654321@ 并且无法判断程序是否会将这些解释为字符 C# 或整数 17187 或内存地址,甚至是提供给 CPU 的指令。

那么,继续你的问题:

  1. symbol table 是 DLL 的一段元数据;它告诉编译器/链接器如何将void myDllFunc (int bar) 转换为DLL 中的地址。它基本上是一个查找表。从 DLL 导出函数是您告诉 哪些 函数您希望在该查找表中结束的方式 - 这些是其他代码将能够调用的函数,因为它能够找到他们。请记住 - 如果没有其他信息,就无法知道 从哪里 myDllFunc 开始。
  2. 推荐extern C 是因为名称解析 的过程,特别是C++ 如何处理函数重载。当您有两个功能时:
int square (int x);
double square (double x);

编译器需要某种方式来区分它们——名称“square”现在是模棱两可的,不能解析为单个代码地址。 C++ 通过 name mangling 来处理这个问题——编译器采用你的函数名称square,然后添加一些与函数签名中的类型相对应的魔法字符串。因此,例如,编译器可以将您的两个函数视为:

int int+square+int (int x);
double dbl+square+dbl (double x);

它们现在不再模棱两可了(真正的编译器不使用这种简单的方案)。现在这里有两个问题:

  1. 您想将该函数称为“square”,而不是“int+square+int”,更糟糕的是,
  2. 不同的 C++ 编译器使用不同的名称修饰规则。

为了方便互操作,人们一般将导出的函数标记为extern C,这使得编译器使用C的命名规则,其中函数的名称不会被弄乱。

编辑地址 cmets: 声明函数签名extern C 解决了名称修改问题,因为 C 没有名称修改。由于声明了两个函数,C 可以在不修改名称的情况下逃脱

int square (int x);
double square (double x);

是一个错误;编译器/链接器不必——也不会——处理这种歧义。

Exporting a function from a DLL 无非就是将函数添加到符号表中。这使得 DLL 外部的代码可以调用该函数,因为现在外部代码可以查找函数的开始位置。因此,该函数被“导出”,其他人可以调用它。

【讨论】:

  • 感谢您的精彩解释。 >C 的命名规则,其中函数的名称没有被破坏。
  • 能否请您扩展您的答案以涵盖我关于Exporting a function from a DLL的其他问题
  • 不兼容的名称修改本身不是问题。这是一个针对实际问题的有意解决方案,即缺乏应用程序二进制接口 (ABI) 标准。如果调用约定不兼容(例如异常如何传播)的编译器使用相同的名称修饰方案,它们会成功链接,但程序可能会崩溃。已发布的 C++ ABI 始终(我认为)提供了名称修改方案。
【解决方案2】:

从 DLL 导出函数仅仅意味着 DLL 的使用者将能够调用此函数。

从概念上讲,它类似于将函数添加到 DLL 公开的“接口”。

符号表是一个包含这些导出函数的表,它们的名称、ID 和地址。

试试这个:http://msdn.microsoft.com/en-us/library/z4zxe9k8(VS.80).aspx

【讨论】:

    【解决方案3】:

    正确声明的导出函数如下所示:

    extern "C" __declspec(dllexport)
    void __stdcall Foo() {
      // etc...
    }
    

    声明符按顺序执行以下操作:

    • extern "C" 禁止 C++ 名称修饰。在 32 位机器上,导出的名称将是 _Foo@0,P/Invoke 编组器可以轻松找到这样的名称。没有它,导出的名称将是 ?Foo@@YGXXZ。编组器找不到这样的名称,您必须使用 [DllImport] 属性中的 EntryPoint 属性来帮助它。 C++ 装饰这样的名称以支持方法重载和类型安全的链接。
    • __declspec(dllexport) 提示链接器将函数放置在 DLL 导出表中。它的作用与 .DEF 文件相同,无需维护此类文件。
    • __stdcall 设置调用约定,即参数传递给函数的方式。 32 位代码有 5 种可能的调用约定 (__cdecl, __stdcall, __fastcall, __thiscall, __clrcall)。几乎所有外部软件都假定 __stdcall 为默认值,包括 P/Invoke 编组器。然而,C/C++ 代码的默认值是 __cdecl。 DllImportAttribute.CallingConvention 可用于覆盖托管端的默认值。

    解决此类问题的主要工具是 Dumpbin.exe。使用 /exports 选项从 DLL 上的 Visual Studio 命令提示符运行它。它列出了在 DLL 的导出表中找到的函数的名称。

    你提到了 COM,那是完全不同的故事。 COM 服务器不导出其功能,它使用“类工厂”[原文如此]。进程内服务器仅导出 4 个函数:

    • DllRegisterServer。 Regsvr32.exe 使用它在注册表中注册服务器。
    • DllUnregisterServer。如上,用于移除注册。
    • DllCanUnloadNow。由 COM 管道定期调用以检查服务器是否不再需要并且可以从内存中卸载。
    • DllGetClassObject。这很重要,它在客户端调用 CoCreateObject() 时由 COM 调用。 COM 服务器通过创建 COM coclass 并将接口指针返回到所请求的接口来实现它。 COM 客户端然后使用它来调用接口上的方法。接口指针指向接口实现的函数地址列表,很像.NET中的接口。

    创建这 4 个导出函数通常是您用来实现 COM 服务器的任何类库的工作。 C++ 的首选武器是 ATL。

    【讨论】:

    • 好吧,我还在学习我没有到达应该使用“类工厂”而不是导出的阶段(在书中)。
    【解决方案4】:

    每个 .dll 文件都有一个服务区,用于存储导出函数的名称及其实现的地址 - 这称为符号表。当消费应用程序想要从 .dll 文件中调用函数时,它会调用 LoadLibrary(),然后调用 GetProcAddress() 来定位函数的地址。符号表用于方便 tghis 查找。如果某个函数不在该表中,GetProcAddress() 将找不到它,消费应用程序将无法调用它。

    【讨论】:

      【解决方案5】:
      1. DLL 是一个初步近似的函数集合,可以做一些有趣的事情。其中一些函数是供 DLL 的用户使用的,另一些是供其他函数在内部使用的,而作者并不打算让 DLL 的用户访问。在决定 DLL 中的哪些函数应该被 DLL 的用户看到后,您可以通过导出它们使它们可见。
      2. 符号是赋予二进制文件(DLL、EXE 等)中的函数和其他东西块的名称。符号表是二进制文件中的数据结构,用于存储这些名称的集合,并为链接器和其他工具提供从名称到命名 blob 的映射。

      您可以使用 VS 附带的 dumpbin 实用程序查看导出的函数,或者(更容易)通过您可以下载 here 的depends.exe 工具查看导出的函数。

      【讨论】:

      • 在 .NET 中,类被编译为 DLL(如果我们将适当的参数传递给编译器)。所有私人成员都是不可见的,公共成员是可访问的,对吗?在 C++ 中不就是这种情况吗? DLL 用户可以访问的私有成员不可见和公共成员。我还编辑了我的帖子。请检查一次。
      • 不,.Net 程序集绑定在 DLL(和 EXE)中,但它们与传统 DLL 完全不同。在 DLL 级别和在 Assembly 公开的元数据彼此完全不相关;作为一般规则,我不认为程序集将任何符号导出为 DLL export.s 在 C++ 中,与 C 一样,符号仅在您这样说时才通过 DLL 导出,无论是使用 .def 文件还是通过标记导出的函数(包括成员函数)作为 __declspec(dllexport)。
      【解决方案6】:

      只是添加 2c,因为作为非英语母语人士,我很难学习“从 DLL 导出函数”这个概念。

      当你用 __declspec(dllexport) 它本质上意味着函数的定义来自 DLL 而不是来自它。我仍然无法弄清楚为什么人们使用“从 DLL 导出函数”而不是“将函数导出到 DLL”

      “Export A to B”表示 A 要去 B。 “从 B 导入 A”是指 A 来自 B。 但是“从B导出A”我真的不知道。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-07-17
        • 1970-01-01
        • 2011-07-08
        • 1970-01-01
        • 1970-01-01
        • 2011-05-14
        • 1970-01-01
        相关资源
        最近更新 更多