我在理解为什么在C ++中需要#include和LoadLibrary()时遇到了一些麻烦。在C ++中,“#include”强制预处理器将#include行替换为您包含的文件的内容(通常是包含声明的头文件)。据我所知,这使我能够在头文件所属的外部库中使用我可能想要的例程。
为什么我需要LoadLibrary()?我不能只#include图书馆本身吗?
正如旁注:在我更熟悉的C#中,如果我想在程序中使用该DLL中的类型或例程,我只需添加对DLL的引用。我没有#include任何东西,因为.NET框架显然会自动搜索所有引用的程序集以查找我想要使用的例程(由命名空间指定)
非常感谢你。
编辑:使用“定义”一词,但意思是“声明”。现在修复了。
编辑2:艰难地选择一个答案,很多好的回复。感谢您的所有贡献。
答案 0 :(得分:4)
C ++使用完全独立的编译模型;你甚至可以编译 反对尚未编写的代码。 (这经常发生在 大项目。)当你包含一个文件时,你所做的就是 告诉编译器存在函数等。你不 提供一个实现(内联函数和 模板)。为了执行代码,您必须提供 实现,通过将其链接到您的应用程序。这个 可以通过几种不同的方式发生:
你有源文件;你和他们一起编译它们 来源,并在结果对象中链接。
你有一个静态库;你必须链接它。
您有一个动态库。在这里,你必须做的事情 取决于实现:在Windows下,您必须链接 针对.lib存根,并将.dll放在某处 运行时会在执行时找到它。 (把它放在一起 目录作为您的应用程序通常是一个很好的解决方案。)
我不太明白你需要致电LoadLibrary
。该
只有当我故意避免的时候,我才需要这个
直接使用库中的任何内容,并希望加载它
有条件地,使用GetProcAddr
来获取地址
我需要的功能。
编辑:
因为我被要求澄清“链接”:程序翻译 (从源到可执行文件)发生在许多 脚步。传统上讲,每个翻译单元都是 “编译”成一个目标文件,其中包含一个图像 机器指令,但外部没有填充空间 引用。例如,如果您有:
extern void function();
在您的来源(可能是通过包含标题)和您
调用function
,编译器将保留地址字段
调用指令空白,因为它不知道在哪里
功能将位于。链接是获取所有人的过程
目标文件的填充,并填写这些空白。其中一个
目标文件将定义function
,链接器将定义
在内存映像中建立实际地址,并填写
空白引用function
,地址为function
在那个图像。结果是完整的记忆图像
可执行文件。在我早期的系统上工作:字面意思。该
操作系统只是将可执行文件直接复制到内存中,
然后跳进去。像虚拟内存和共享的东西,
写保护代码段使这更多一点
今天很复杂,但对于静态链接的库或对象
文件(我上面的前两个案例),差异不是那个
大。
现代系统技术在某种程度上模糊了这些线条。对于
例如,大多数Java(我认为是C#)编译器都没有生成
经典目标文件,带有机器码,而是字节码,
并且上面的编译和链接阶段不会发生
运行。一些C ++编译器也只生成字节码
将在代码“链接”时编译。这样就完成了
允许跨模块优化。和所有现代系统
支持动态链接:留下一些空白地址
空白直到执行时间。动态链接可以是隐含的
或显式:当它是隐式时,链接阶段将插入
关于库的可执行文件的信息
需要,在哪里找到它们,操作系统会链接它们,
隐式地,无论是在加载可执行文件时,还是按需加载,
由试图使用其中一个未填充的代码触发
地址槽。如果它是明确的,你通常没有任何
显式引用代码中的名称。如果是
function
,例如,上面你没有任何代码
直接称为function
。但是,您的代码会加载
使用LoadLibrary
(或Unix下的dlopen
)的动态库,
然后使用GetProcAddr
(或。)请求名称的地址
dlsys
),并通过指针间接调用该函数
收到了。
答案 1 :(得分:1)
#include
仅引入源代码:编译器的符号声明。库(或DLL)是目标代码:使用LoadLibrary或链接到lib文件以引入目标代码。
答案 2 :(得分:1)
与所有预处理器功能一样,#include
指令仅仅是文本替换。文本“#include”将替换为该文件的内容。
通常(但不一定),这用于包含一个头文件,声明你想要使用的函数,即告诉编译器(在预处理器之后运行)一些函数如何您打算使用的是命名,它们采用什么参数,以及返回类型是什么。它没有定义函数实际执行的操作。
您还需要这些功能的实现。通常,如果您未在程序中实现它们,则将此任务留给链接阶段。您将程序所依赖的库列表提供给链接器,链接器通过一些实现定义的方式(例如“导入库”)来实现“使其工作”所需要做的事情。链接器将生成一些粘合代码并将一些信息写入可执行文件,这将使加载程序自动加载所需的库。一切都“正常”,你不必做一些特别的事情。
但是,在某些情况下,您希望推迟链接器阶段并手动“完全动态”加载而不是自动加载。这是您必须致电LoadLibrary()
和GetProcAddress
时的情况。前者将DLL带入内存并进行一些设置(例如重定位),后者为您提供要调用的函数的地址。
代码中的#include
仍然是必需的,因此编译器知道如何处理该指针。否则,您当然可以通过其地址调用获得的函数,但是无法以有意义的方式调用函数。
人们想要手动加载库(使用LoadLibrary
)的一个原因是它更安全。如果您将程序链接到库并且找不到库(或找不到符号),则您的应用程序将无法启动,并且用户将看到或多或少的模糊错误消息。
如果LoadLibrary
失败或GetProcAddress
不起作用,您的程序原则上仍可运行,但功能会降低。
使用LoadLibrary
的另一个例子可能是从不同的库加载一个替代版本的函数(某些程序以这种方式实现“插件”)。函数“看起来”与包含文件中定义的编译器相同,但可能表现不同,如加载的二进制文件中的任何内容。
答案 3 :(得分:0)
LoadLibrary()使代码模块从磁盘加载到应用程序内存空间以供执行。这允许在运行时动态加载代码。例如,如果要使用的代码编译为静态链接库,则不会使用LoadLibrary()。在这种情况下,您将提供包含链接器代码的.lib文件的名称,并在链接时解析 - 代码链接到.exe,而.lib不按.exe顺序分发让它执行。
LoadLibrary()创建对外部DLL的依赖关系,该外部DLL必须存在于提供给方法调用的路径上,以便.exe正确执行。如果LoadLibrary()失败,您必须确保您的代码将适当地处理它,方法是正常退出或提供其他执行替代方法。您必须像上面的静态库一样向链接器提供.lib文件。但是.lib文件不包含代码,只是驻留在.dll中的实际代码的入口点。
在这两种情况下,您必须#include要执行的代码的标头。编译器需要这样才能根据标头提供的类型信息正确构建函数调用签名。
C#程序集包含类型信息和IL。单个引用足以满足头信息和绑定代码本身的需要。
答案 4 :(得分:-3)
#include
是静态的,替换是在编译时完成的。 LoadLibrary()
允许您在运行时加载DLL,例如基于用户输入。