windows核心编程---DLL基础

-DLL
1.windows API函数都在DLL中.
2.三个最重要的DLL
Kernel32.dll:管理内存,进程,线程
User32.dll:执行用户界面相关,入窗口,窗口消息
GDI32.dll:绘制图像,显示文字。
AdvAPI32.dll:安全性,注册表,事件日志。
ComDlg32.dll:通用对话框控件。
ComCtl32.dll:通用控件
3.本章介绍,如何创建自定义DLL。

-DLL优势
1.可被动态载入进程地址空间。
2.模块化
3.内存映射+页面复制,使所有应用通过使用关联到DLL的同一个内存映射对象,而共享此DLL在内存的唯一一份页面。
4.DLL可包含对话框模板,字符串,图标,位图之类的资源。
5.windows的某些特性只有DLL才能使用。
5.1.SetWindowsHookEx/SetWinEventHook要求挂钩通知函数在DLL。
5.2.COM对象须放在DLL。
5.3.ActiveX控件须放在DLL。

-DLL和进程的地址空间
1.
所有源文件编译完成后,须给链接器指定/DLL开关。
2.
在应用(或其它DLL)能调用一个DLL中的函数前,须将该DLL的文件映像映射到调用进程的地址空间。
隐式链接,显示链接。

-纵观全局
1.构建DLL
a.头文件,待导出函数原型,结构和符号的声明。
b.源文件。函数实现和变量。
c.编译器,将源文件生成为.obj文件。
d.链接器,合并.obj,生成dll。
e.导出了函数或变量时,链接器生成.lib。

2.构建EXE
a.头文件,待导出函数原型,结构和符号的声明。
b.源文件。函数实现和变量。
c.编译器,将源文件生成为.obj文件。
d.链接器,合并.obj,使用.lib来解析对导入和函数和变量的引用,进而生成exe。

1.可把从DLL中导入函数和变量的模块称为:可执行模块。
导出函数和变量的模块称为:DLL模块。

-构建DLL
1.头文件,包含想要在DLL中导出的函数原型,结构和符号。
DLL的所有源文件须包含这个头文件。
可执行文件同样需要此头文件。

2.源文件。实现导出的函数和变量。
构建可执行模块时,不需要这些源文件。

3.编译器让每个源文件处理为一个.obj模块。

4.链接器合并所有.obj模块内容,产生一个DLL映像文件。这个DLL映像文件包含DLL中所有二进制代码及全局/静态变量。
执行可执行模块时,需要dll文件。

5.DLL源文件输出了函数或变量时,链接器还生成.lib文件。
构建可执行模块时,需要此.lib文件。

-构建可执行模块
1.在所有引用了导出的函数,变量,数据结构或符号的源文件中,须包含由DLL开发人员创建的头文件。
2.在源文件实现可执行模块的函数和变量。
3.编译器让每个源文件处理为一个.obj模块。
4.链接器合并所有.obj模块,产生一个exe文件。
这个映像文件或模块包含了可执行文件中所有的二进制代码以及全局/静态变量。可执行模块还包含一个导入段。列出了要导入的DLL模块和需要从此模块导出的函数和变量的符号名。

可执行模块和DLL模块都构建完毕,进程就可执行了。

5.试图执行可执行模块时,执行加载程序。
加载程序:
为进程创建虚拟地址空间
将可执行模块映射到新进程地址空间
解析可执行模块的导入段。对导入段的每个DLL,加载程序对其定位,并将其映射到进程的地址空间中。
DLL模块有自己的导入段,并需将DLL导入段的DLL模块也映射到进程的地址空间。

-构建DLL模块
1.一个DLL可导出变量,函数,C++类。
只有当导出C++类的模块使用的编译器与导入C++类的模块使用的编译器相同时,才可导出C++类。

2.步骤
2.1.先创建一个头文件,来包含要导出的变量和函数。和需用到的符号和数据结构。
DLL的所有源文件都应包含这个头文件。
须分发这个头文件,使需要导入这些变量或函数的源文件可包含该头文件。

2.2.// 例:编写头文件,使可执行模块和DLL源文件都能包含它

// Module:MyLib.h
#ifdef MYLIBAPI
// 在所有DLL源代码模块包含此头文件前,MYLIBAPI应被定义。
// 所有被导出的函数和变量
#else
// 此头文件被一个EXE源码模块包含
// 指明所有被导入的函数和变量
#define MYLIBAPI extern "C" __declspec(dllimport)
#endif

// 定义数据结构和对象
// 定义导出变量
MYLIBAPI int g_nResult;

// 定义导出函数原型
MYLIBAPI int Add(int nLeft, int nRight);
// DLL源文件(需要引用导出符号的源文件)
// Module:MyLibFile1.cpp
#include <windows.h>
#define MYLIBAPI extern "C" __declspec(dllexport)
#include "MyLib.h"
int g_nResult;

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

如果编译器看到一个变量,函数或C++类是用__declspec(dllexport)修饰的,那么它知道应在生成的DLL模块中导出该变量,函数或C++类。
编译器在解析头文件时,会记住导出的变量和函数。源文件中变量和函数的实现不在需要被__declspec(dllexport)修饰。

2.3.
extern “C”,在编写C++代码时,需使用。
因为,C++编译器通常会对函数名和变量名进行改编。
C编译器不会对函数名和变量名进行改编。

extern “C”用来告诉编译器不要对变量名或函数名进行改编,这样用C,C++或任何编程语言编写的可执行模块都可访问该变量或函数。

2.4.可执行文件的源文件如何使用此头文件
仅仅,在需用到DLL导出的函数或变量的源文件中包含此头文件。
由于此时引用的函数或变量被extern “C” __declspec(dllimport)修饰,编译器就知道可执行文件的源文件要从DLL模块中导入一些变量和函数。

-何为导出
当Microsoft的C/C++编译器看到用这个修饰符修饰的变量,函数原型或C++类的时候,会在生成的.obj文件中嵌入一些额外的信息。当链接器在链接DLL所有的.obj文件时,会解析这些信息。

链接器会检测与导出的变量,函数和类有关的嵌入信息,并生成一个.lib文件。这个.lib文件列出了该DLL导出的符号。
在链接可执行模块时,如果可执行模块引用了该DLL导出的符号,则,需要此.lib文件。链接器还在生成的DLL文件中嵌入一个导出符号表(导出段)。这个符号表列出了导出的变量,函数和类的符号名,链接器还保存了相对虚拟地址(每个符号可在DLL模块中的何处找到)。

DumpBin.exe + -exports,来查看一个DLL的导出段。

// 用法举例:
C:\Windows\System32>DUMPBIN -exports c2.DLL

Microsoft (R) COFF/PE Dumper Version 12.00.31101.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file c2.dll

File Type: DLL

  Section contains the following exports for c2.dll

    00000000 characteristics
    545468F2 time date stamp Sat Nov 01 13:00:34 2014
        0.00 version
           1 ordinal base
           5 number of functions
           5 number of names

    ordinal hint RVA      name

          1    0 00119E88 DllGetObjHandler
          2    1 00300547 _AbortCompilerPass@4
          3    2 0011AC97 _CloseTypeServerPDB@0
          4    3 0030064D _InvokeCompilerPass@12
          5    4 0011D1BB _InvokeCompilerPassW@16

  Summary

      119000 .data
        4000 .idata
       26000 .reloc
        1000 .rsrc
      38C000 .text
       13000 .tls

-为非Visual C++工具包创建DLL
由于C++类的名称改变问题,须在编译DLL模块和可执行模块时,使用同一个编译器。
当使用 __stdcall(WINAPI)修饰时,Microsoft的C编译器也会对C函数的名称进行改编。

为了用Microsoft的工具包来构建一个能与其它编译器厂商的工具包链接的DLL,须告诉Microsoft编译器不要对导出的函数名进行改编。
方法一:
1.为项目创建一个.def文件,并在.def文件中包含类似下面的:

EXPORTS
    MyFunc

当Microsoft链接器解析这个.def文件时,会发现_MyFunc@8和MyFunc都被导出,链接器会用MyFunc来导出函数。

用Microsoft的工具包来构建可执行文件并链接到一个DLL的时候,如果该DLL包含的符号名未经改编,那么链接器会试图链接到名为_MyFunc@8的函数,失败时,链接器会试图链接到名为MyFunc的函数。

方法二:
在DLL源文件中添加一行类似下面的代码

#pragma comment(linker, "/export:MyFunc=_MyFunc@8")

此时,实际导出了MyFunc,_MyFunc@8两个符号。
@后数字是函数参数的字节数。

-构建可执行模块
// 可执行模块,源文件

// Module:MyExeFile1.cpp

#include <windows.h>
#include <strsafe.h>
#include <stdlib.h>

// 包含导入数据结构,对象,函数,变量
#include "MyLib\MyLib.h"

int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    int nLeft = 10, nRight = 25;
    TCHAR sz[100];
    StringCchPrintf(sz,
    _countof(sz),
    TEXT("%d + %d = %d"),
    nLeft,
    nRight,
    Add(nLeft, nRight));

    MessageBox(NULL,
    sz,
    TEXT("Calculation"),
    MB_OK);
    return 0;
}

编译可执行模块源文件时,MYLIBAPI在MyLib.h中被定义为__declspec(dllimport),
如果编译器看到一个变量,函数或C++类是用__declspec(dllimport)来修饰的,
那么它会知道应该从某个DLL模块中导入该符号。

编译器不需确定导入符号来自那个DLL。
链接器须确定代码中引用的导入符号来自那个DLL。须将DLL的.lib文件传给链接器。

-何为导入
链接器在解决导入符号时,会在可执行模块中嵌入一个特殊的段:导入段。
导入段列出了模块所需的DLL,从每个DLL模块中引用的符号。

Microsoft (R) COFF/PE Dumper Version 12.00.31101.0
Copyright (C) Microsoft Corporation.  All rights reserved.
Dump of file lib.exe
File Type: EXECUTABLE IMAGE

  Section contains the following imports:

    KERNEL32.dll
                403000 Import Address Table
                4030F8 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                  1C9 GetCommandLineW
                  303 GetVersion
                  267 GetModuleHandleW
                  29D GetProcAddress
                  367 IsDebuggerPresent
                   FE DecodePointer
                  2D6 GetSystemTimeAsFileTime
                  20E GetCurrentThreadId
                  20A GetCurrentProcessId
                  42D QueryPerformanceCounter
                  121 EncodePointer
                  36D IsProcessorFeaturePresent

    MSVCR120.dll
                。。。
                2A2 _fmode

  Summary

        1000 .data
        1000 .idata
        1000 .reloc
        1000 .rsrc
        1000 .text

-运行可执行模块
1.为进程创建虚拟地址空间。
2.把可执行模块映射到进程的地址空间。
3.对可执行模块DLL依次定位并将它们映射到进程的地址空间。

导入段只包含DLL的名称。
加载程序,须在用户的磁盘上搜索DLL。
搜索顺序:
1.可执行文件所在目录。
2.windows的系统目录。
3.16位的系统目录。
4.windows目录。
5.进程的当前目录。
6.PATH环境变量所列出的目录。

加载程序将DLL模块映射到进程的地址空间中,会同时检查每个DLL的导入段,导入段DLL类似被加载。加载程序会对载入的DLL模块记录,一个模块被多次引用时,也只会载入和映射一次。
加载程序将所有DLL模块都载入并映射到进程地址空间后,查看每个模块的导入段,对导入段的每个符号,加载程序检查对应DLL的导出段,确保符号存在。如符号存在,该符号的RVA+DLL基地址,就是其在进程的虚拟地址。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raindayinrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值