【问题标题】:C++: Include vs LoadLibrary()C++:包含与 LoadLibrary()
【发布时间】:2013-11-19 16:17:41
【问题描述】:

我很难理解为什么 C++ 中同时需要 #include 和 LoadLibrary()。在 C++ 中,“#include”强制预处理器将 #include 行替换为您所包含的文件的内容(通常是包含声明的头文件)。据我了解,这使我能够在标头所属的外部库中使用我可能想要的例程。

为什么我需要 LoadLibrary()?我不能只#include 库本身吗?

顺便说一句:在我更熟悉的 C# 中,如果我想在我的程序中使用该 DLL 中的类型或例程,我只需添加对 DLL 的引用。我不需要 #include 任何东西,因为 .NET 框架显然会自动搜索所有引用的程序集以查找我想要使用的例程(由命名空间指定)

非常感谢您。

编辑:使用了“定义”一词,但意思是“声明”。现已修复。

编辑 2:很难选择一个答案,很多好的答案。感谢所有贡献。

【问题讨论】:

  • 在 Windows 平台上的 C/C++ 中一个更常见的模式是,您包含 header/s 以导入声明(函数、类等)。您链接到静态库或导入库 (.lib)。如果是导入库,您的应用程序将在启动后尝试动态链接 DLL。使用 LoadLibrary 肯定是一种有效的模式,但不如简单地使用将您的应用与动态库联系起来的导入库那么普遍。

标签: c++ include loadlibrary


【解决方案1】:

C++ 使用完全独立的编译模型;你甚至可以编译 针对尚未编写的代码。 (这经常发生在 大型项目。)当你包含一个文件时,你所做的就是 告诉编译器函数等存在。你不 提供一个实现(内联函数和 模板)。为了执行代码,您必须提供 实现,通过将其链接到您的应用程序。这 可以通过几种不同的方式发生:

  • 你有源文件;你把它们和你的一起编译 源,并链接到生成的对象中。

  • 你有一个静态库;你必须链接它。

  • 您有一个动态库。在这里,你必须做的将 取决于实现:在Windows下,你必须链接 针对 .lib 存根,并将 .dll 放在 运行时会在您执行时找到它。 (把它放在同一个 目录,因为您的应用程序通常是一个很好的解决方案。)

我不太明白您需要致电LoadLibrary。这 我唯一需要的时候是我故意避免 直接使用库中的任何内容,并希望加载它 有条件地,使用GetProcAddr 来获取 我需要的功能。

编辑:

自从我被要求澄清“链接”:程序翻译 (从源代码到可执行文件)发生在多个 脚步。在传统术语中,每个翻译单元都是 “编译”成一个目标文件,其中包含一个图像 机器指令,但有未填充的空间用于外部 参考。例如,如果您有:

extern void function();

在您的来源中(可能通过包含标题),并且您 调用function,编译器会留下地址字段 呼叫指令空白,因为它不知道在哪里 功能将被定位。链接是采取一切的过程 的目标文件,并填写这些空白。中的一个 目标文件将定义function,链接器将 在内存映像中建立实际地址,并填写 空格指的是function,地址为function 在那个图像中。结果是完整的内存映像 可执行。在我工作的早期系统上:字面意思。这 操作系统只需将可执行文件直接复制到内存中, 然后跳进去。诸如虚拟内存和共享之类的东西, 编写受保护的代码段使这更多 今天很复杂,但对于静态链接的库或对象 文件(我上面的前两个案例),差异不在于 很棒。

现代系统技术有些模糊了界限。为了 例如,大多数 Java(我认为是 C#)编译器不会生成 经典的目标文件,带有机器码,而是字节码, 以及上面的编译和链接阶段,直到 运行。一些 C++ 编译器也只生成字节码, 将在代码“链接”时编译。这是为了 允许跨模块优化。以及所有现代系统 支持动态链接:留一些空白地址 空白直到执行时间。动态链接可以是隐式的 或显式:当它是隐式时,链接阶段将插入 信息到有关它的库的可执行文件中 需要,在哪里可以找到它们,操作系统将链接它们, 隐式地,无论是在加载可执行文件时,还是在需要时, 由尝试使用未填充之一的代码触发 地址槽。当它是明确的时,你通常没有任何 在您的代码中显式引用了该名称。如果是 function,例如,你不会有任何代码 直接调用function。但是,您的代码将加载 动态库使用LoadLibrary(或Unix下dlopen), 然后使用GetProcAddr(或 dlsys),并通过指针间接调用函数 收到了。

【讨论】:

  • 谢谢詹姆斯。如果有人想了解更多关于 LoadLibrary() 的信息,请查看下面@Damon 的帖子。詹姆斯的快速问题:我没有完全理解“链接”方面(“链接反对它”)。你认为你可以编辑你的帖子来澄清这一点吗?再次感谢。
【解决方案2】:

#include 指令与所有预处理器功能一样,只是一个文本替换。文本“#include”被替换为该文件的内容。

通常(但不一定),这用于包含一个头文件,该文件声明您要使用的函数,即告诉编译器(在预处理器之后运行)一些函数如何您打算使用的名称,它们采用的参数以及返回类型是什么。它没有定义函数实际在做什么。

您还需要这些函数的实现。通常,如果您没有在程序中实现它们,则将此任务留给链接阶段。您将程序依赖的库列表提供给链接器,并且链接器通过某种实现定义的方式(例如“导入库”)确定“使其工作”需要做什么。链接器将生成一些胶水代码并将一些信息写入可执行文件,这将使加载程序自动加载所需的库。一切都“正常工作”,而无需您做一些特别的事情。

但是,在某些情况下,您希望推迟链接器阶段并手动“完全动态地”加载而不是自动加载。这时您必须致电LoadLibrary()GetProcAddress。前者将 DLL 带入内存并进行一些设置(例如重定位),后者为您提供要调用的函数的地址。 代码中的#include 仍然是必需的,因此编译器知道如何处理该指针。否则,你当然可以通过它的地址调用获得的函数,但是不可能以有意义的方式调用函数

想要手动加载库(使用LoadLibrary)的一个原因是它更安全。如果您将程序与库链接,但找不到该库(或找不到符号),那么您的应用程序将无法启动,用户将看到或多或少的模糊错误消息。
如果LoadLibrary 失败或GetProcAddress 不起作用,您的程序原则上仍然可以运行,尽管功能有所减少。

另一个使用LoadLibrary 的例子可能是从不同的 库中加载一个函数的替代版本(有些程序以这种方式实现“插件”)。该函数对编译器的“外观”与包含文件中定义的一样,但行为可能不同,就像加载的二进制文件中的任何内容一样。

【讨论】:

    【解决方案3】:

    #include 仅引入源代码:编译器的符号声明。库(或 DLL)是目标代码:使用 LoadLibrary 或链接到 lib 文件来引入目标代码。

    【讨论】:

      【解决方案4】:

      LoadLibrary() 使代码模块从磁盘加载到您的应用程序内存空间中以供执行。这允许在运行时动态加载代码。例如,如果您要使用的代码被编译成静态链接库,则您不会使用 LoadLibrary()。在这种情况下,您将向链接器提供包含代码的 .lib 文件的名称,它会在链接时得到解析 - 代码链接到您的 .exe 并且 .lib 不按顺序与 .exe 一起分发让它执行。

      LoadLibrary() 创建对外部 DLL 的依赖,该外部 DLL 必须存在于提供给方法调用的路径上,以便 .exe 正确执行。如果 LoadLibrary() 失败,您必须确保您的代码能够适当地处理它,方法是正常退出或提供一些其他执行替代方案。您必须为链接器提供一个 .lib 文件,就像为上面的静态库提供的一样。然而,这个 .lib 文件不包含代码,只是驻留在 .dll 中的实际代码的入口点。

      在这两种情况下,您都必须#include 要执行的代码的标头。编译器需要这样做,以便根据标头提供的类型信息正确构建函数调用签名。

      C# 程序集包含类型信息和 IL。单个引用就足以满足对标头信息和绑定到代码本身的需求。

      【讨论】:

        【解决方案5】:

        #include 是静态的,替换是在编译时完成的。 LoadLibrary() 允许您在运行时加载 DLL,例如基于用户输入。

        【讨论】:

          猜你喜欢
          • 2012-12-07
          • 1970-01-01
          • 2015-11-14
          • 2011-12-12
          • 2011-09-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多