码迷,mamicode.com
首页 > 其他好文 > 详细

动态链接库 —— Dll 基础

时间:2019-01-04 22:34:39      阅读:208      评论:0      收藏:0      [点我收藏+]

标签:windows   工程   有用   系统   创建   执行文件   简单   导入   下划线   

1. DLL 的初识

  在 windows 中,动态链接库是不可缺少的一部分,windows 应用程序程序接口提供的所有函数都包含在 DLL 中,其中有三个非常重要的系统 DLL 文件,分别为 Kernel32.dllUser32.dllGDI32.dll,下面说下这三个重要的 DLL 的用途:

  • Kernel32.dll:包含的函数用来管理内存、进程以及线程。
  • User32.dll:包含的函数用来执行与用户界面相关的任务,如创建窗口和发送消息。
  • GDI32.dll:包含的函数用来绘制图像和显示文字。

当然,windows 还有其它一些 DLL,用来执行更加专门的任务。比如下面一些 DLL:

  • AdvAPI32.dll:包含的函数与对象的安全性、注册表的操控以及事件日志有关。
  • ComDlg32.dll:包含了一些常用的对话框(如打开文件和保存文件)。
  • ComCtl32.dll:支持所有常用的窗口控件。

2. 为何使用 DLL

下面简要说下使用 DLL 的一些理由:

  • 它们扩展了应用程序的特性。
  • 它们简化了项目管理。
  • 它们有助了节省内存。
  • 它们促进了资源的共享。
  • 它们促进了本地化。
  • 它们有助于解决平台间的差异。
  • 它们可以用于特殊目的(比如 HOOK 安装某些挂钩函数)。

3. DLL 和进程的地址空间

  创建 DLL 比创建应用程序简单,DLL 中通常没有用来处理消息循环或创建窗口的代码,DLL 只不过是一组源代码模块,生成 DLL 文件时,需给链接器指定 \DLL 开关,这个开关会使链接器在生成的 DLL 文件映像中保存一些与可执行文件略微不同的信息,这样 windows 加载器在加载它们时容易将它们区分开(PE 文件头结构中的文件属性字段会指出)。

  如果一个应用程序或者是另外的 DLL 想去调用 DLL 里的函数,则必须将该 DLL 映射到调用进程的地址空间去,可以通过两种方式来调用,分别是隐式调用和显示调用,这两种调用方式以后会说到。

  一旦系统将一个 DLL 的文件映像映射到调用进程的地址空间之后,进程中的所有线程就可以调用该 DLL 中的函数了。记住,当线程调用 DLL 中的一个函数的时候,该函数会在线程栈中取得传给它的参数,并使用线程栈来存放它需要的局部变量。此外,该 DLL 中的函数创建的任何对象都为调用线程或调用进程所拥有 —— DLL 绝对不会拥有任何对象。

4. 纵观全局

技术分享图片

以上为 DLL 创建过程及应用程序隐式链接到 DLL 的过程,概括了各组件是如何结合到一起的。构建一个 DLL 步骤:

  • 必须先创建一个头文件,在其包含我们想要在 DLL 中导出的函数原型、结构以及符号。
  • 创建 C/C++ 源文件来实现想要在 DLL 模块导出的函数和变量。
  • 在构建该 DLL 模块的时候,编译器会对每个源文件进行处理并产生一个 .obj 模块(每一个源文件对应一个 .obj 模块)。
  • 当所有 .obj 模块都创建完毕后,链接器会将所有 .obj 模块的内容合并起来,产生一个单独的 DLL 映像文件。
  • 如果链接器检测到 DLL 的源文件输出了至少一个函数或变量,那么链接器还会生一个 .lib 文件,这个 .lib 文件非常小,这是因为它不包含任何函数或变量。它只是列出了所有被导出的函数和变量的符号名。

一旦 DLL 构建完成后,那么我们就可以去构建一个可执行模块来调用 DLL 中的函数和变量了,具体调用过程如下:

  加载程序先为新的进程创建一个虚拟地址空间,并将可执行模块映射到新进程的地址空间中。加载程序接着解析可执行文件的导入段,也就是 PE 中的导入表,对导入表列出的每个 DLL,加载程序会在用户的系统中对该 DLL 模块进行定位,并将该 DLL 映射到进程的地址空间中。还要注意的一点就是,由于 DLL 模块可以从其它 DLL 模块中导入函数和变量,因此 DLL 模块可能有自已的导入表并需要将它所需的 DLL 模块映射到进程的地址空间中,这一过程可能会耗费更长的时间。一旦加载程序将可执行模块和所有的 DLL 模块映射到进程的地址空间之后,进程的主线程可以开始执行,这样应用程序就能够运行了。

4.1 构建 DLL 模块

打开 VS,我这里用的是 VS2015,新建项目,在 Visual C++ 选项卡下选择 Win32,右侧选择 Win32 控制台应用程序,然后给一个名称,如下:
技术分享图片
点击确定后,选择 DLL,附加选择空项目,如下:
技术分享图片
建立好之后,再建立一个头文件和一个源文件,如下:
技术分享图片
然后以 MyDll.h 文件中输入如下代码:

#pragma once

// extern "C" 修饰符只有在编写 C++ 代码的时候,才会用到此修饰符
// 在编写 C 代码时不应该使用该修饰符,C++ 编译器通常会对函数名和变量名进行改编
// 如果一个 DLL 是用 C++ 编写的,而可执行文件是用 C 编写的,在构建 DLL 时
// 编译器会对函数名进行改编,但是在构建可执行文件时,编译器不会对函数名进行改编
// 当链接器试图链接可执行文件时,会发现可执行文件引用了一个不存在的符号并报错
// extern "C" 用来告诉编译器不要对变量名或函数名进行改编
// 那么这样用 C、C++ 或任何编程语言编写的可执行模块都可以访问该变量或函数
// 换句话说,是为了防止名称被粉碎
extern "C" __declspec(dllimport) int g_nResult;

extern "C" __declspec(dllimport) int Add(int nLeft, int nRight);

MyDll.cpp 文件中输入如下代码:

#include <windows.h>

#include "MyDll.h"

int g_nResult;

int Add(int nLeft, int nRight)
{
    g_nResult = nLeft + nRight;

    return g_nResult;
}

在代码完成后,点生成解决方案,这样它就会生成 Dll 文件,如下:
技术分享图片
  其中在头文件中还做了部分注释,还有部分说明后面再说,我们先在解决方案下再创建一个新的工程来调用这个 Dll,这个调用是隐式调用,需要用到上图中的 MyDll.dllMyDll.lib 这两个文件,创建好后,再创建一个 cpp 源文件,如下:
技术分享图片
MyDllTest.cpp 文件中输入如下代码:

#include <iostream>

#include "../MyDll/MyDll.h"

#pragma comment(lib, "../Debug/MyDll.lib")

int main()
{
    int nLeft = 10;
    int nRight = 25;

    std::cout << Add(nLeft, nRight) << std::endl;

    return 0;
}

然后我们去编译链接它,输出如下:
技术分享图片
  程序运行后得出了正确的答案,说明调用 Dll 中的 Add 函数成功,接下来要说明下代码中的意思。extern "C" 这个修饰符已在代码注释中说明,但这里还需要补充一下额外知识,C 编译器在对函数编译后,函数名不会发生改变,而 C++ 编译器不同,它在对函数编译后会在原函数名的基础上加上一个下划线,在最后面加上 @ 符号,其后跟上一个该函数形参所占用的总共字节数,比如:

__declspec(dllexport) LONG __stdcall MyFunc(int a, int b);

  经过 C++ 编译器编译后,该函数名会发生改变,变为 _MyFunc@8,那 C++ 编译器为什么要这么做呢?原因是在 C++ 中,存在函数重载,而在 C 中不存在函数重载,所以在 C 中无需对函数名称进行粉碎,为了让 C++ 编译器不对函数名改编,需加下 extern "C",其实方法也不止这一种,还可以在你项目下建立一个 .def 文件,写下如下代码:

EXPORTS
    MyFunc

  接下来要说的是 __declspec(dllimport) 修饰符,当编译器看到用这个修饰符修饰的变量、函数原型或 C++ 类的时候,会在生成的 .obj 文件中嵌入一些额外的信息。当链接器在链接 Dll 所有的 .obj 文件时,会解析这些信息。
  另外,在链接 Dll 的时候,链接器会检测到这些与导出的变量、函数或类有关的嵌入信息,并生成一个 .lib 文件。这个 .lib 文件列出了该Dll 导出的符号。在链接任何可执行模块的时候,只要可执行模块引用了该 Dll 导出的符号,这个 .lib 文件当然是必需的。

动态链接库 —— Dll 基础

标签:windows   工程   有用   系统   创建   执行文件   简单   导入   下划线   

原文地址:https://www.cnblogs.com/importthis/p/10222526.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!