系列文章目录

[笔记]Windows核心编程《一》错误处理、字符编码
[笔记]Windows核心编程《二》内核对象
[笔记]Windows核心编程《三》进程
[笔记]Windows核心编程《四》作业
[笔记]快乐的LInux命令行《五》什么是shell
[笔记]Windows核心编程《五》线程基础
[笔记]Windows核心编程《六》线程调度、优先级和关联性
[笔记]Windows核心编程《七》用户模式下的线程同步
[笔记]Windows核心编程《八》用内核对象进行线程同步
[笔记]Windows核心编程《九》同步设备I/O和异步设备I/O
[笔记]Windows核心编程《十一》Windows线程池
[笔记]Windows核心编程《十二》纤程
[笔记]Windows核心编程《十三》windows内存体系结构
[笔记]Windows核心编程《十四》探索虚拟内存
[笔记]Windows核心编程《十五》在应用程序中使用虚拟内存
[笔记]Windows核心编程《十六》线程栈
[笔记]Windows核心编程《十七》内存映射文件
[笔记]Windows核心编程《十八》堆栈
[笔记]Windows核心编程《十九》DLL基础
[笔记]Windows核心编程《二十》DLL的高级操作技术
[笔记]Windows核心编程《二十一》线程本地存储器TLS
[笔记]Windows核心编程《二十二》注入DLL和拦截API
[笔记]Windows核心编程《二十三》结构化异常处理

文章目录

  • 系列文章目录
  • 前言
  • 一、DLL模块的显式加载和符号链接
    • 1.1 显式加载DLL模块 LoadLibrary
      • DONT_RESOLVE_DLL_REFERENCES
      • LOAD_LIBRARY_AS_DATAFILE
      • LOAD_WITH_ALTERED_SEARCH_PATH
    • 1.2 显式卸载DLL模块 FreeLibrary
      • GetModuleFileName
    • 1.3 显式链接到一个输出符号
  • 二、DLL的进入点函数 DllMain
    • 2.1 DLL_PROCESS_ATTACH通知
    • 2.2 DLL_PROCESS_DETACH通知
    • 2.3 DLL_THREAD_ATTACH通知
    • 2.4 DLL_THREAD_DETACH通知
    • 2.5 顺序调用DllMain
    • 2.6 DllMain与C/C++运行期库
  • 三、延迟加载DLL
    • 介绍
    • 应用场景
    • 特性
  • 四、函数转发器
  • 五、已知的DLL
  • 六、DLL转移
  • 七、改变模块的位置
  • 八、绑定模块
  • 总结

前言

本章主要讲 与DLL相关的各种操作方法。大多数应用程序不一定需要这些方法,但是它们是非常有用的,所以应该对它们有所了解。

一、DLL模块的显式加载和符号链接

如果线程需要调用D L L模块中的函数,那么D L L的文件映像必须映射到调用线程的进程的。

创造DLL:

1)建立带有输出原型/结构/符号的头文件。
2)建立实现输出函数/变量的 C/C++源文件。
3)编译器为每个 C/C++源文件生成.obj模块。
4)链接程序将生成DLL的 .obj模块链接起来。
5)如果至少输出一个函数/变量,那么链接程序也生成 .lib 文件。

创造EXE:

6)建立带有输入原型/结构/符号的头文件(视情况而定)。
7)建立不引用输入函数/变量的 C/C++源文件。
8)编译器为每个C/C++源文件生成 .obj源文件。
9)链接程序将各个 .obj模块链接起来,生成 .exe文件。
注:DLL的lib文件是不需要的,因为并不直接引用输出符号。.exe 文件不包含输入表。

运行应用程序:

10)加载程序为 .exe创建模块地址空进程的主线程开始执行;应用程序启动运行。

显式加载DLL:

11)一个线程调用LoadLibrary
(Ex)函数,将DLL加载到进程的地址空间这时线程可以调用GetProcAddress以便间接引用DLL的输出符号。

加载DLL方法:

  • 隐式加载:让应用程序的源代码只引用 D L L中包含的符号。这样,当应用程序启动运行时,加载程序就能够隐含加载(和链接)需要的DLL。
  • 显示加载: 在应用程序运行时让应用程序显式加载需要的 D L L并且显式链接到需要的输出符号。换句话说,当应用程序运行时,它里面的线程能够决定它是否要调用 DLL中的函数。该线程可以将D L L显式加载到进程的地址空间,获得 D L L中包含的函数的虚拟内存地址,然后使用该内存地址调用该函数。

显示加载这种方法的优点是一切操作都是在应用程序运行时进行的。

1.1 显式加载DLL模块 LoadLibrary

进程中的线程都可以决定将一个 D L L映射到进程的地址空间,方法是调用下面两个函数中的一个:

HINSTANCE LoadLibrary(PCTSTR pSzDLLPathName);HINSTANCE LoadLibraryEx(PCTSTR pSzDLLPathName,HANDLE hF1le,DWORD dwF1ags
);

作用:这两个函数均用于找出用户系统上的文件映像(使用上一章中介绍的搜索算法),并设法将D L L的文件映像映射到调用进程的地址空间中。

pSzDLLPathName:加载的dll文件的路径。

HINSTANCE:用于标识文件映像映射到的虚拟内存地址。如果DLL不能被映射到进程的地址空间,则返回 NULL。若要了解关于错误的详细信息,可以调用GetLastError。

hFile:保留供将来使用,现在必须是 NULL。

dwFlags:必须将它设置为 0 。
或者设置DONT_RESOLVE_DLL_REFERENCES、
LOAD_LIBRARY_AS_DATAFILE 和LOAD_WITH_ALTERED_SEARCH_PATH等标志的一个组合。

DONT_RESOLVE_DLL_REFERENCES

用于告诉系统将D L L映射到调用进程的地址空间中。

通常情况下,当DLL被映射到进程的地址空间中时,系统要调用 DLL中的一个特殊函数,即 DllMain(本章后面介绍)。该函数用于对 D L L进行初始化。

作用:

  1. DONT_RESOLVE_DLL_REFERENCES标志使系统不必调用DllMain函数就能映射文件映像。
  2. 当 DONT_RESOLVE_DLL_REFERENCES标志被设定时,系统并不自动将该DLL依赖的其他的DLL加载到进程的地址空间中。

LOAD_LIBRARY_AS_DATAFILE

系统只是将D L L映射到进程的地址空间中,就像它是数据文件一样。系统并不花费额外的时间来准备执行文件中的任何代码。

当一个 D L L被映射到进程的地址空间中时,系统要查看D L L中的某些信息,以确定应该将哪些页面保护属性赋予文件的不同的节。如果设定了LOAD_LIBRARY_AS_DATAFILE标志,系统将以它要执行文件中的代码时的同样方式来设置页面保护属性。

由于下面几个原因,该标志是非常有用的:

  • 加载只有资源没有函数的DLL时: 首先,如果有一个 D L L(它只包含资源,但不包含函数),那么可以设定这个标志,使 DLL的文件映像能够映射到进程的地址空间中。
  • 调用加载资源的函数: 然后可以在调用加载资源的函数时,使用LoadLibraryEx函数返回的HINSTANCE值。
  • 加载EXE访问其中资源: 通常情况下,加载一个.exe文件,就能够启动一个新进程,但是也可以使用 LoadLibraryEx函数将.exe文件的映像映射到进程的地址空间中。借助映射的 .exe文件的HINSTANCE值,就能够访问文件中的资源。
    由于.exe文件没有DllMain函数,因此,当调用LoadLibraryEx来加载一个.exe文件时,必须设定LOAD_LIBRARY_AS_DATAFILE标志。

LOAD_WITH_ALTERED_SEARCH_PATH

用于改变 LoadLibraryEx用来查找特定的DLL文件时使用的搜索算法。

如果设定了该标志,那么LoadLibraryEx 函数就按照下面的顺序来搜索文件:

  1. pszDLLPathName参数中设定的目录。
  2. 进程的当前目录。
  3. Windows的系统目录。
  4. Windows目录。
  5. PATH环境变量中列出的目录。

1.2 显式卸载DLL模块 FreeLibrary

当进程中的线程不再需要 D L L中的引用符号时,可以从进程的地址空间中显式卸载 D L L,方法是调用下面的函数:

BOOL FreeLibrary(HINSTANCE hinstD11);

HINSTANCE:HINSTANCE值,以便标识要卸载的 D L L。该值是较早的时候调用LoadLibrary(Ex)而返回的值。

也可以通过调用下面的函数从进程的地址空间中卸载 DLL:

VOID FreeLibraryAndExitThread(HINSTANCE hinstDll,DWORD dwExitCode
);

该函数是在Kernel32.dll中实现的,如下所示:

VOID FreeLibraryAndExitThread(HINSTANCE hinstD11,DWORD dwExitCode){FreeLibrary(hinstDll);ExitThread(dwExitCode);
}

微软创建FreeLibraryAndExitThread原因:

假定你要编写一个 DLL,当它被初次映射到进程的地址空间中时,该 D L L就创建一个线程。当该线程完成它的操作时,它通过调用FreeLibrary函数,从进程的地址空间中卸载该 DLL,并且终止运行,然后立即调用ExitThread。
但是,如果线程分开调用 FreeLibrary和ExitThread,就会出现一个严重的问题。这个问题是调用FreeLibrary会立即从进程的地址空间中卸载 DLL。当调用的FreeLibrary返回时,包含对ExitThread调用的代码就不再可以使用,因此线程将无法执行任何代码。这将导致访问违规,同时整个进程终止运行。

  • LoadLibrary和LoadLibraryEx 这两个函数用于对与特定的库相关的进程使用计数进行递增;
  • FreeLibrary和FreeLibraryAndExitThread这两个函数则用于对库的每个进程的使用计数进行递减。

第一次加载DLL系统将DLL的文件映像映射到调用进程的地址空间中,并将 D L L的使用计数设置为1。

第二次同一进程的线程加载同一DLL,则不会再次映射DLL到进程的地址空间,系统只是将与该进程的D L L相关的使用计数递增1。

GetModuleFileName

调用GetModuleFileName函数线程就能够确定D L L是否已经被映射到进程的地址空间中。

只有当尚未MyLib.dll被映射到进程的地址空间,才LoadLibrary

HINSTANCE hinstDll = GetModu1eHand1e("MyLib"); // DLL extension assumed
if (hinstDll == NULL) {hinstDll = LoadLibrary("MyLib");ll DLL extension assumed}
}

如果只有 DLL的HINSTANCE值,那么可以调用 GetModuleFileName函数,确定 DLL(或. e x e)的全路径名:

DWORD GetModuleFi1eName(HINSTANCE hinstModule,PTSTR pszPathName,DWORD cchPath
);

hinstModule:是D L L(或. e x e)的HINSTANCE 。
pszPathName:是该函数将文件映像的全路径名放入的缓存的地址。
cchPath:用于设定缓存的大小(以字符为计量单位)。

1.3 显式链接到一个输出符号

一旦D L L模块被显式加载,线程就必须获取它要引用的符号的地址,方法是调用下面的函数:

FARPROC GetProcAddress(HINSTANCE hinstD11,PCSTR pszSymbo1Name
);

hinstDll:调用LoadLibrary (Ex)或GetModuleFi1eName函数而返回的,它用于设定包含符号的D L L的句柄。

pszSymbolName:

  1. 以 0结尾的字符串
FARPROC pfn = GetProcAddress(hinstD11,"SomeFuncInDll");
  1. 用于指明你想要其地址的符号的序号
FARPROC pfn = GetProcAddress(hinstDll,MAKEINTRESOURCE(2));

调用GetProcAddress的第一种方法比第二种方法要慢。
但非常不建议使用序号,因为当传递的 序号尚未分配给任何导出的函数,会返回非NULL值,误以为你已经拥有一个有效的地址。

二、DLL的进入点函数 DllMain

如果你创建一个只包含资源的DLL,就不必实现该函数。

如果确实需要在 D L L中接受通知信息,可以实现类似下面的进入点函数:

B00L WINAPI DllMain(HINSTANCE hinstDll,DWORD fdwReason,PVOID fImpLoad){switch (fdwReason){ case DLL_PROCESS_ATTACH://The DLL is being mapped into the process's address space.break;case DLL_THREAD_ATTACH://A thread is being created.break;case DLL_THREAD_DETACH://A thread is exiting cleanly.break;case DLL_PROCESS_DETACH://The DlL is being unmapped from the process's address space.break;}return(TRUE);//Used only for DLL_PROCESS_ATTACH
}

hinstDll:包含了D L L的实例句柄,这个值用于标识D L L的文件映像被映射到进程的地址空间中的虚拟内存地址。
fImpLoad:如果D L L是隐含加载的,那么该参数将是个非 0值,如果D L L是显式加载的,那么它的值是0。
fdwReason:用于指明系统为什么调用该函数。该参数可以使用 4个值中的一个。DLL_PROCESS_ATTACH、DLL_THREAD_DETACH、DLL_THREAD_DETACH、DLL_PROCESS_DETACH。

注意:
必须记住,DLL使用DllMain函数来对它们进行初始化。
当你的DllMain函数执行时,同一个地址空间中的其他DLL可能尚未执行它们的DllMain函数。
这意味着它们尚未初始化,因此你应该避免调用从其他DLL中输入的函数。此外,你应该避免从DllMain内部调用LoadLibrary(Ex)和FreeLibrary函数,因为这些函数会形式一个依赖性循环。
Platform SDK文档说,你的D l l M a i n函数只应该进行一些简单的初始化,比如设置本地存储器(第 2 1章介绍),创建内核对象和打开文件等。你还必须避免调用 User、Shell、ODBC、COM、RPC和套接字函数(即调用这些函数的函数),因为它们的DLL也许尚未初始化 ,或者这些函数可能在内部调用LoadLibrary(Ex)函数,这同样会形成一个依赖性循环。

2.1 DLL_PROCESS_ATTACH通知

  • 当DLL被初次映射到进程的地址空间中时,系统将调用该 DLL的DllM a i n函数,给它传递参数fdwReason的值DLL_PROCESS_ATTACH。
  • 当处理DLL_PROCESS_ATTACH时,D L L应该执行D L L中的函数要求的任何与进程相关的初始化。
  • 当处理DLL_PROCESS_ATTACH时,如果DLL的任何一个DllMain函数返回FALSE,指明初始化没有取得成功,系统便终止整个进程的运行。LoadLibrary就会返回NULL。

2.2 DLL_PROCESS_DETACH通知

  • D L L从进程的地址空间中被卸载时,系统将调用 D L L的DllMain函数,给它传递fdwReason的值DLL_PROCESS_DETACH。当D L L处理这个值时,它应该执行任何与进程相关的清除操作。

注意:
如果因为系统中的某个线程调用了TerminateProcess而使进程终止运行,那么系统将不调用带有DLL_PROCESS_DETACH值的D L L的D l l M a i n函数。这意味着映射到进程的地址空间中的任何D LL都没有机会在进程终止运行之前执行任何清除操作。这可能导致数据的丢失。只有在迫不得已的情况下,才能使用 TerminateProcess函数。


2.3 DLL_THREAD_ATTACH通知

当在一个进程中创建线程时,系统要查看当前映射到该进程的地址空间中的所有 DLL文件映像,并调用每个文件映像的带有DLL_THREAD_ATTACH值的DllMain函数。

2.4 DLL_THREAD_DETACH通知

2.5 顺序调用DllMain

2.6 DllMain与C/C++运行期库

三、延迟加载DLL

介绍

VC++6.0的特性——延迟加载DLL。
它能够使 D L L的操作变得更加容易。这个特性称为延迟加载 D L L。延迟加载的 D L L是个隐含链接的 D L L,它实际上要等到你的代码试图引用 D L L中包含的一个符号时才进行加载。

应用场景

以下情况延迟加载是有用的:

  • 应用初始化加载的DLL过多,导致程序初始化时间过长。使用延迟加载,在程序不同阶段分时加载DLL可以解决这个问题。
  • 新版本系统的函数,在老版本系统中不存在时,使用延迟加载,在加载dll之前判断系统版本,不存在则不运行新函数。

特性

四、函数转发器

函数转发器是D L L的输出节(导出段)中的一个项目,用于将对一个函数的调用转至另一个 DLL中的另一个函数。

五、已知的DLL

操作系统提供的某些D L L得到了特殊的处理。这些D L L称为已知的D L L。它们与其他D L L基本相同,但是操作系统总是在同一个目录中查找它们,以便对它们进行加载操作。

六、DLL转移

D L L转移特性能够强制操作系统的加载程序首先从你的应用程序目录中加载文件模块。只有当加载程序无法在应用程序目录中找到该文件时,它才搜索其他目录。

为了强制加载程序总是首先查找应用程序的目录,要做的工作就是在应用程序的目录中放入一个文件。该文件的内容可以忽略,但是该文件必须称为 AppName.local。例如,如果有一个可执行文件的名字是 SuperApp.exe,那么转移文件必须称为SuperApp.exe.local。

七、改变模块的位置

每个可执行模块和D L L模块都有一个首选的基地址,用于标识模块应该映射到的进程地址空间中的理想内存地址。当创建一个可执行模块时,链接程序将该模块的首选基地址设置为0x0040 0000。如果是DLL模块,链接程序设置的首选基地址是 0x1000 0000。

八、绑定模块

总结

1.LoadLibrary和LoadLibraryEx区别?

[笔记]Windows核心编程《二十》DLL的高级操作技术相关推荐

  1. Windows核心编程 第二十章 DLL的高级操作技术

    第2 0章 D L L的高级操作技术 看了下这章的内容,谈不上高级,都是些常用相关,但是还是有一些细节需要注意. 20.1 DLL模块的显式加载和符号链接 如果线程需要调用D L L模块中的函数,那么 ...

  2. [笔记]Windows核心编程《十九》DLL基础

    系列文章目录 [笔记]Windows核心编程<一>错误处理.字符编码 [笔记]Windows核心编程<二>内核对象 [笔记]Windows核心编程<三>进程 [笔记 ...

  3. [笔记]Windows核心编程《十六》线程栈

    系列文章目录 [笔记]Windows核心编程<一>错误处理.字符编码 [笔记]Windows核心编程<二>内核对象 [笔记]Windows核心编程<三>进程 [笔记 ...

  4. [笔记]Windows核心编程《十三》windows内存体系结构

    系列文章目录 [笔记]Windows核心编程<一>错误处理.字符编码 [笔记]Windows核心编程<二>内核对象 [笔记]Windows核心编程<三>进程 [笔记 ...

  5. Windows核心编程 第十九章 DLL基础

    第1 9章 D L L基础 这章是介绍基本dll,我就记录一些简单应用,dll的坑点以及扩展后面两章会说,到时候在总结. 自从M i c r o s o f t公司推出第一个版本的Wi n d o w ...

  6. Windows核心编程 第十二章 纤程

    第1 2章 纤 程 M i c r o s o f t公司给Wi n d o w s添加了一种纤程,以便能够非常容易地将现有的 U N I X服务器应用程序移植到Wi n d o w s中.U N I ...

  7. Windows核心编程 第十八章 堆栈

    第1 8章 堆 栈 对内存进行操作的第三个机制是使用堆栈.堆栈可以用来分配许多较小的数据块.例如,若要对链接表和链接树进行管理,最好的方法是使用堆栈,而不是第 1 5章介绍的虚拟内存操作方法或第1 7 ...

  8. Windows核心编程 第十五章 在应用程序中使用虚拟内存

    第1 5章 在应用程序中使用虚拟内存 Wi n d o w s提供了3种进行内存管理的方法,它们是: • 虚拟内存,最适合用来管理大型对象或结构数组. • 内存映射文件,最适合用来管理大型数据流(通常 ...

  9. (部分来自windows核心编程)远程注入dll

    {//仅仅支持ANSI路径//find the wndHWND hwnd = FindWindow(NULL,"1.txt - 记事本");//get pid DWORD tid, ...

最新文章

  1. 成功的产品 = 做得好 + 卖得好
  2. 软考程序员2009年下午试题是否有误?
  3. PHP-redis中文文档
  4. 【AutoML】连续可微分架构如何用于网络结构搜索
  5. 大象公会:如何做万人传播的原创内容?
  6. 达摩院携手交通部公路科学研究院 探索国内首个道路智能解决方案
  7. clickhouse原理解析与应用实践_Hybrid App (混合应用) 技术全解析 方案原理篇
  8. 【玩转SQLite系列】目录收藏
  9. transformer bert GPT(未完)
  10. 一个C++程序员生产力,大概相当20个C程序员,200个java程序员?
  11. 九种食物摆脱便秘烦恼 - 生活至上,美容至尚!
  12. 毕设题目:Matlab优化电价
  13. Visual Studio 各版本下载合集vs2005下载+vs2008下载+vs2010下载+vs2012下载
  14. 【AI视野·今日CV 计算机视觉论文速览 第155期】Fri, 6 Sep 2019
  15. Linux一键安装Aria2+Yaaw+FileManager实现BT磁力下载,并在线查看/观看
  16. OCCT学习001-----简介
  17. 计算机考试多少个小时,考驾照科一电脑刷几个小时
  18. 百旺红字发票信息表显示服务器返回为空,红字发票信息表状态详细说明
  19. 理光M2004文件服务器,理光mp2014打印机怎么连接到电脑
  20. ajax页面拼接html,ajax请求拼接html代码

热门文章

  1. c++ 递归算法求全排列
  2. VB.net byval和byref
  3. 为何贩卖焦虑的文章如此受欢迎???
  4. 【零基础强化学习】100行代码教你训练——基于SARSA的CliffWalking爬悬崖游戏
  5. oracle rfs进程过多,DATA GUARD的RFS进程的问题
  6. 游戏中常用的设计模式
  7. 《调研报告》:黑灰产哄抢消费券的两种方式
  8. 搭载固态硬盘的服务器究竟比机械硬盘快多少
  9. Android:浏览图片,点击放大至全屏效果
  10. 大数据研发能力模型(面试准备)