[笔记]Windows核心编程《十九》DLL基础(二)

简介: [笔记]Windows核心编程《十九》DLL基础(二)

创建DLL模块步骤

若要创建D L L模块,必须执行下列操作步骤:

  1. 必须创建一个头文件,它包含你想要从D L L输出的函数原型、结构和符号。当使用DLL时,也是需要头文件的。
  2. 要创建一个.cpp文件,主要编写函数实现。对外隐藏。
  3. 编译.cpp为.obj。
  4. 链接所有.obj,产生一个DLL映象文件。该映像文件(即模块)包含了用于 DLL的所有二进制代码和全局 /静态数据变量。
  5. 生成一个.lib文件,lib文件包含所有已输出函数和变量的符号名。

创建可执行模块步骤

  1. 包含DLL模块的头文件。
  2. 创建Cpp文件,并引用dll模块所包含的函数和变量。
  3. 编译,生成.obj模块。
  4. 链接程序便将所有的. o b j模块的内容组合起来,生成一个可执行的映像文件。一旦DLL和可执行模块创建完成,一个进程就可以执行。

运行可执行模块时,操作系统的加载程序执行步骤

  1. 加载程序为新进程创建一个虚拟地址空间:
  1. exe被映射到新进程的地址空间。
  2. 加载程序对可执行模块的输入节进行分析。对于该节中列出的每个DLL名字,加载程序递归找出用户系统上的DLL模块,再将DLL以及DLL所需的映射到进程的地址空间。

注意,由于DLL模块可以从另一个DLL模块输入函数和变量,因此DLL模块可以拥有它自己的输入节(即记录了所需的DLL的函数名和变量符号)。若要对进程进行全面的初始化,加载程序要分析每个模块的输入节,并将所有需要的DLL模块映射到进程的地址空间。

如你所见,对进程进行初始化,DLL越多越费时间。

三、 创建D L L模块

D L L可以输出以下几种类型到其他模块。:

  • 变量
  • 函数
  • C / C + +类

注意:

1.应当避免输出变量。因为这会删除你的代码中的一个抽象层,使它更加难以维护你的 D L L代码。

2.应避免输出C + +类。只有当DLL模块开发人员和EXE开发人员使用的编译工具相同时,才可输出类。

下面的代码说明了应该如何对单个头文件进行编码,以便同时包含可执行文件和 D L L的源代码文件:

/****************************************
 Module:MyLib.h
****************************************/
#ifdef MYLIBAPI
//MYLIBAPI should be defined in all of the DLL's sourcel / code modules before this header file is included.1l A11 functions/variab1es are being exported.
#e1se
//This header file is included by an EXE source code module.ll Indicate that a11 functions/variables are being imported.
#define MYLIBAPI extern "C" __declspec(d11import)
// Define any data structures and symbo1s here.
// Define exported variables here. ( NOTE: Avoid exporting variables.)
MYLIBAPI int g_nResult;
//Define exported function prototypes here.
MYLIBAPI int Add( int nLeft. int nRight) ;
/****************************************
 Module:MyLib.cpp
****************************************/
//Include the standard windows and C-Runtime header files here.
#include <windows.h>
// This DLL source code file exports functions and variab1es.
#define MYLIBAPI extern "c" __dec1spec(d11export)
//Include the exported data structures,symbo1s,functions,and variables.
#include "MyLib.h"
//Place the code for this DLL source code file here.int g...nResult;
int Add( int nLeft, int nRight) {
  g_nResu1t = nLeft + nRight;return( g nResu1t);
}

在MyLib.h头文件的前面使用__declspec(dllexport)作用是:

  • 当编译器看到负责修改变量、函数或 C + +类的__declspec(dllexport)时,它就知道该变量、函数或C + +类是从产生的DLL模块输出的。

注意,MYLIBAPI标志要放在头文件中要输出的变量的定义之前和要输出的函数前面。

MYLIBAPI标志包含了extern “C” 修改符作用是:

  • 使用C风格导出函数

注意:

只有当你编写C++代码而不是直接编写C代码时,才能使用这个修改符。

C + +编译器可能会改变函数和变量的名字,extern “C”,就可以告诉编译器不要改变变量名或函数名,这样,变量和函数就可以供使用 C、C + +或任何其他编程语言编写的可执行模块来访问。

补充:

c++编译器允许同名函数(重载)

c编译器不允许重载。

例如:void foo(int n,int m);

void foo(int n);

C++导出成_foo_int_int和_foo_int,链接器读入时不会报错。

C都导出成_foo_;导致链接会提示函数重命名;

3.1 输出的真正含义是什么

查看dll的输出节

dumpbin.exe -exports xxx.dll

RVA:这一列下面的数字用于指明在 D L L文件映像中的什么位置能够找到输出符号的位移量。

hint(提示码):列可供系统用来改进代码的运行性能,在此并不重要。

3.2 创建用于非Visual C++工具的D L L

当使用同一个编译器供应商的工具,创建DLL是不需要做额外工作的。

当使用不同供应商的工具时,则需要做一些额外工作。

调用规则 __stdcall(WINAPI)

当使用 __stdcall将C函数输出时,Microsoft的编译器就会改变函数的名字,设置一个前导下划线,再加上一个 @符号的前缀,后随一个数字,表示作为参数传递给函数的字节数。

例如,下面的函数是作为DLL的输出节中的_MyFunc@8输出的:

_declspec(dllexport) LONG __stdcall MyFunc(int a. int b);
• 1

其他编译器工具创建exe时 使用Microfost编译器创建DLL需要注意

若要使用与其他编译器供应商的工具链接的 M i c r o s o f t的工具创建一个可执行模块,必须告诉Microsoft的编译器输出没有经过改变的函数名。

有两种方法:

  • 第一种方法是为编程项目建立一个.def文件,并在该.def文件中加上类似下面的EXPORTS节:
EXPORTS
  MyFunc # 输出函数的最终名
• 1
• 2
  • 如果想避免使用.def文件,可以使用第二种方法输出未截断的函数版本。在 D L L的源代码模块中,可以添加下面这行代码:
#pragma comment(1inker,"/export:MyFunc=_MyFunc@8")
• 1

这行代码使得编译器发出一个链接程序指令,告诉链接程序,一个名叫MyFunc的函数将被输出,其进入点与称为_MyFunc@8的函数的进入点相同。

第二种方法没有第一种方法容易,因为你必须自己截断函数名,以便创建该代码行。

另外,当使用第二种方法时,DLL实际上输出用于标识单个函数的两个符号,即 MyFunc和_MyFunc@8,而第一种方法只输出符MyFunc。第二种方法并没有给你带来更多的好处,它只是使你可以避免使用 .def的文件而已。

四、创建可执行模块

查看输入节

Visual Studio的DumpBin . e x e实用程序(带有- i m p o r t s开关),能够看到模块的输入节的样子。下面是 Calc.exe文件的输入节的一个代码段。

C:\WINNT\SYSTEM32>DUMPBIN -imports Calc.EXE
Dump of file C:\Windows\System32\calc.exe
File Type: EXECUTABLE IMAGE
  Section contains the following imports:
    SHELL32.dll
             1400021B0 Import Address Table
             1400028C0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference
                         1AE ShellExecuteW
    KERNEL32.dll
             140002148 Import Address Table
             140002858 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference
                         224 GetCurrentThreadId
                         2F2 GetSystemTimeAsFileTime
                         310 GetTickCount
                         4D4 RtlCaptureContext
                         220 GetCurrentProcessId
                         4E2 RtlVirtualUnwind
                         5BE UnhandledExceptionFilter
                         57D SetUnhandledExceptionFilter
                         21F GetCurrentProcess
                         59C TerminateProcess
                         451 QueryPerformanceCounter
                         4DB RtlLookupFunctionEntry
    msvcrt.dll
             1400021F0 Import Address Table
             140002900 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference
                          90 __setusermatherr
                         17D _initterm
                          57 __C_specific_handler
                         382 _wcmdln
                         127 _fmode
                          D2 _commode
                          2F ?terminate@@YAXXZ
                          C1 _cexit
                          9D __wgetmainargs
                          AE _amsg_exit
                          55 _XcptFilter
                         432 exit
                          8E __set_app_type
                         10E _exit
    ADVAPI32.dll
             140002128 Import Address Table
             140002838 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference
                         122 EventSetInformation
                         129 EventWriteTransfer
                         121 EventRegister
    api-ms-win-core-synch-l1-2-0.dll
             1400021E0 Import Address Table
             1400028F0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference
                          2D Sleep
    api-ms-win-core-processthreads-l1-1-0.dll
             1400021D0 Import Address Table
             1400028E0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference
                          20 GetStartupInfoW
    api-ms-win-core-libraryloader-l1-2-0.dll
             1400021C0 Import Address Table
             1400028D0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference
                          14 GetModuleHandleW
  Summary
        1000 .data
        1000 .pdata
        1000 .rdata
        1000 .reloc
        5000 .rsrc
        1000 .text

,这些 DLL是SHELL32.dll、

msvcrt.dll、ADVAPI32.dll、KERNEL32.dll、GDI32 . dll和User32.dll。

(我的运行的是win10 会有些dll依赖的区别)

五、运行可执行模块

当一个可执行文件被启动时,操作系统加载程序将为该进程创建虚拟地址空间。然后,加载程序将可执行模块映射到进程的地址空间中。加载程序查看可执行模块的输入节,并设法找出任何需要的DLL,并将它们映射到进程的地址空间中。

搜索DLL顺序是:

  1. 包含可执行映像文件exe的目录。
  2. 进程的当前目录。
  3. Windows系统目录。
  4. Windows目录。
  5. PATH环境变量中列出的各个目录。

应该知道其他的东西也会影响加载程序对一个 DLL的搜索:

  • 当D L L模块映射到进程的地址空间中时,加载程序要检查每个 D L L的输入节。如果存在输入节(通常它确实是存在的),那么加载程序便继续将其他必要的 DLL模块映射到进程的地址空间中。加载程序将保持对 DLL模块的跟踪,使模块的加载和映射只进行一次(尽管多个模块需要该模块)。

总结

1.为什么静态C/C++ 运行时库(MT)被其他模块释放内存 会报错?

为什么MT模块内存不能相互释放,而MD的却可以

简单讲,MT库编译时各会包含libcmt.lib,然后每个MT库都有一个独立的堆。一个堆的地址到另一个堆的同名地址去释放,自然会有非法访问问题。

2.__stdcall、__cdecl、extern "C"区别 和使用场景。

1. __stdcall:
__stdcall是一种函数调用约定(calling convention),用于指定函数的参数传递和堆栈清除方式。
__stdcall约定要求被调用函数从堆栈中清除其参数,并且函数的参数是从右往左依次入栈。
__stdcall常用于Windows API函数,如LoadLibrary和MessageBox。
在使用__stdcall约定声明函数时,可以使用__declspec(dllexport)或__declspec(dllimport)来指定函数的导出或导入。
2. __cdecl:
__cdecl也是一种函数调用约定,但与__stdcall不同,它要求调用者清除堆栈,并且函数的参数是从左往右依次入栈。
默认情况下,C++函数都是按照__cdecl约定进行调用的,可以省略__cdecl宏定义。
__cdecl约定在函数的定义和声明中都可以使用。
extern "C":
3. extern "C"是用于指定C语言风格的函数名和参数名的链接约定。
C++编译器对函数和变量进行了名称修饰(name mangling),以支持函数重载和命名空间等特性,而C语言没有支持这些特性,所以需要使用extern "C"来取消C++的名称修饰。
C语言和C++语言的函数之间可以通过extern "C"进行链接,以实现互相调用。
使用场景:
- 如果你在使用Windows API函数,应该注意使用__stdcall约定声明和调用这些函数。
- 如果你定义的函数需要与其他C语言或第三方库进行交互,可以使用extern "C"来取消名称修饰,确保链接成功。
- 对于一般情况下的函数声明和定义,默认使用__cdecl约定即可,不需要额外指定。

相关文章
|
3月前
|
缓存 网络协议 数据安全/隐私保护
[运维笔记] - (命令).Windows server常用网络相关命令总结
[运维笔记] - (命令).Windows server常用网络相关命令总结
191 0
|
3月前
|
存储 Java C语言
Windows 下 JNI 调用动态链接库 dll
Windows 下 JNI 调用动态链接库 dll
63 0
|
3月前
|
存储 Java C++
Windows 下 JNA 调用动态链接库 dll
Windows 下 JNA 调用动态链接库 dll
41 0
|
2天前
|
API C++ Windows
windows编程入门_链接错误的配置
windows编程入门_链接错误的配置
8 0
|
2月前
|
Windows
火山中文编程 -- 第一个windows程序
火山中文编程 -- 第一个windows程序
12 0
|
2月前
|
编译器 API Windows
windows编程基础
windows编程基础
13 0
|
2月前
|
Windows
win32编程 -- windows绘图操作
win32编程 -- windows绘图操作
20 0
|
3月前
|
网络协议 Linux C语言
005.在Windows下编程让效率起飞
windows开发Linux方式: 先用编辑器编写源代码 然后进入Linux 系统,使用gcc编译器(后面会讲),对源代码进行编译运行。 熟练后推荐使用VS2019 开发Linux C++ 程序 将自己的Ip地址设为静态IP
40 1
|
3月前
|
存储 Ubuntu 开发工具
ffmpeg笔记(二)windows下和ubuntu-16.04下ffmpeg编译
ffmpeg笔记(二)windows下和ubuntu-16.04下ffmpeg编译
|
4月前
|
Linux API C++
音视频windows安装ffmpeg6.0并使用vs调试源码笔记
音视频windows安装ffmpeg6.0并使用vs调试源码笔记
117 0