关于DLL的延迟加载

延迟加载DLL,使用的是隐式加载方式,当为exe使用的DLL指定为延迟加载的时候,连接器会将exe的【导入段】中去除该DLL的相关信息,同时在exe中嵌入一个新的【延迟加载段】表示要从该DLL中导入哪些函数。

通过让对延迟加载函数的调用跳转到delayimp.lib中的__delayLoadHelper2函数,来完成对延迟加载的DLL的解析。

当exe中第一次调用了一个延迟加载的DLL中的某个导出函数时,加载器才会将该DLL加载到进程地址空间中。需要注意的是:虽然此时已经加载了DLL,但是未调用到的其他函数,还是需要在调用的时候才能被修复(修复即不再通过__delayLoadHelper2函数来解析)。

延迟加载的DLL也可以卸载,当再次调用该DLL中的函数时,再次加载DLL到进程地址空间。

延迟加载 及支持卸载DLL的步骤,以ADll.dll为例

①项目属性--配置属性--链接器--输入--延迟加载的DLL

输入DLL的名字,注意这里:卸载的时候使用的DLL的名字不能包含路径 并且 大小写必须和这里设置的一样。

/DELAYLAOD:[DLLName]

②项目属性--配置属性--链接器--高级--延迟加载的DLL

选择【支持卸载(/DELAY:UNLOAD)】

③加载DelayImp.lib静态库

__delayLoadHelper2函数和__FUnloadDelayLoadedDLL2函数都在这个静态库中。

#pragma comment(lib, "DelayImp.lib") 

#include <delayimp.h>

④添加DLL的头文件 或者 添加DLL导出函数声明

extern "C" __declspec(dllimport) int __stdcall Add(int a, int b);

 demo

void CUseADll2Dlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码 __try
{
int sum = Add(, );
}
__except()
{
AfxMessageBox(_T("异常"));
}
} void CUseADll2Dlg::OnBnClickedButton2()
{
//检测该DLL是否加载了
HINSTANCE hInst = GetModuleHandle(TEXT("ADll.dll"));
} void CUseADll2Dlg::OnBnClickedButton3()
{
//参数DLL的名字不能包含路径,并且 大小写要和/DELAYLOAD设置的一致
BOOL bRet = __FUnloadDelayLoadedDLL2("ADll.dll");
}

 函数转发器

函数转发器是DLL【输出段】中的一个条目,用来将一个函数调用【转发】到另一个DLL中的另一个函数。

可用【dumpbin  -exports  XX.dll】查看

在DLL中设置函数转发器:

#pragma comment(linker, "/export:本DLL导出函数=其他DLL.函数名")

e.g.

#pragma comment(linker, "/export:Add=SecondDll.Sum")

将本DLL中的导出函数Add【转发】到另一个名为SecondDll的DLL中函数Sum。

经过实验,发现这里需要特别注意:#pragma来表示转发的函数名一定是经过name manling改变后的

如下:

//DLL1中的导出函数,Add
#pragma comment(linker, "/export:_Add@8=Dll2._Sum@8") extern "C" __declspec(dllexport) int __stdcall Add(int a, int b);
//DLL2中的导出函数Sum
extern "C" __declspec(dllexport) int __stdcall Sum(int a, int b)
{
return a + b;
}

一开始,当我试图这样写时:#pragma  comment(lib, "exports:Add=Dll2.Sum)

看起来好像没有问题,可实际上,GetProcAddress总是失败的,经过测试,发现必须使用经过编译器name manling改变过后的名字,从depends查看如下

所以必须使用改变过后的名字才可以,这一点也同样体现在GetProcAddress中,如下

HINSTANCE hInst = LoadLibrary(TEXT("Dll1.dll"));
if (NULL == hInst)
{
AfxMessageBox(_T("LoadLibrary 失败"));
} typedef int (__stdcall * PUNC)(int, int); //必须加上__stdcall PUNC pfnAdd = (PUNC)GetProcAddress(hInst, "_Add@8"); //这里的名字也必须是改编后的
if (NULL == pfnAdd)
{
AfxMessageBox(_T("GetProcAddress失败")); return;
} int sum = pfnAdd(, );
char chBuffer[] = {};
sprintf_s(chBuffer, "result is %d", sum);
AfxMessageBox(CString(chBuffer));

另外,如果想要使用未经改变的名字,那么可以在VS编译器下使用

extern "C"  __declspec(dllexport)   int  __cdecl  Add(int a,  int b);

使用extern  "C" 和  __cdecl调用约定组合方式,在VS下的名字就是Add。

结论:

①DLL函数转发时,转发到的函数也必须是导出函数,两者的调用约定以及名字改编方式最好一致

②在设置转发时,必须使用经过改编后的名字

③在定义导出函数的函数指针时,要加上调用约定,并且GetProcAddress()函数也要用改编后的名字

已知DLL

在注册表HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDlls中列出的那些

在使用LoadLibrary或LoadLibraryEx时,参数中的DLL是否带.dll后缀导致的不同情况:

①带.dll后缀

首先去掉后缀,比如LoadLibrary("xx.dll"),首先去掉后缀,然后去上述注册表路径下查找【名称】为"xx"的项目,然后找到其对应的【数据】,即带.dll后缀的dll名字(这个名字可能和LoadLibrary时使用的不一样),如果在注册表中找不到该【名称】为"xx"的项目,则再去搜索路径依次查找"xx.dll", 如果在注册表中找到了该DLL名字,则去%systemroot%\sytstem32目录中去查找该xx.dll,系统会并且只会该目录下查找该xx.dll,如果找到了则加载进进程地址空间,如果找不到则失败返回。

②不带.dll后缀

按照正常搜索路径搜索DLL

【windows核心编程】DLL相关(2)的更多相关文章

  1. windows核心编程 DLL技术 【转】

    注:本文章转载于网络,源地址为:http://blog.csdn.net/ithzhang/article/details/7051558 本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑 ...

  2. 《Windows核心编程系列》二十谈谈DLL高级技术

    本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...

  3. 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。

    windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...

  4. 《windows核心编程系列》十七谈谈dll

    DLL全称dynamic linking library.即动态链接库.广泛应用与windows及其他系统中.因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要. windows中所有API ...

  5. 《Windows核心编程》读书笔记 上

    [C++]<Windows核心编程>读书笔记 这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对 ...

  6. C++Windows核心编程读书笔记

    转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔 ...

  7. 【转】《windows核心编程》读书笔记

    这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入 ...

  8. 《windows核心编程系列》二十一谈谈基址重定位和模块绑定

    每个DLL和可执行文件都有一个首选基地址.它表示该模块被映射到进程地址空间时最佳的内存地址.在构建可执行文件时,默认情况下链接器会将它的首选基地址设为0x400000.对于DLL来说,链接器会将它的首 ...

  9. 《windows核心编程系列》十六谈谈内存映射文件

    内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件.将文件映射到内存中后,我们就可以在内存中操作他们了 ...

  10. windows核心编程 - 线程同步机制

    线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...

随机推荐

  1. *[hackerrank]Girlfriend & Necklace

    https://www.hackerrank.com/contests/w8/challenges/gneck 有点意思.是DP,最优解包含最优子问题.F(X)=F(X-1)+F(X-3).因为F(X ...

  2. static int和static final int的区别

    1.static变量 按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量:另一种是没有被static修饰的变量,叫实例变量.两者的区别是: 对于静态变量在内 ...

  3. iOS 开发--github的demo

    令人惊讶的是,YYText 虽然代码量很大(超过一万行),但它只是 ibireme 的作品之一.ibireme 利用业余时间完成了 YYKit 工具库,包括: YYModel — 高性能的 iOS J ...

  4. Go语言的优点(oschina讨论)

    Go语言的优点:并发/网络/性能/工具(fmt/pprof/test)/标准库(http/json/log/flags/atomic)/GoogleGo语言垃圾回收器真正致命的缺陷是,会导致整个进程不 ...

  5. 让Maven支持代理

    1.如果你的公司架设了防火墙并设置了HTTP代理服务器来禁止你们直接连接互联网,那么Maven就无法通过代理自动下载依赖包. 为了让Maven能够工作,你需要在Maven的配置文件 settings. ...

  6. C# MySql分页存储过程的应用

    存储过程: 获取范围内的数据 DELIMITER $$ DROP PROCEDURE IF EXISTS `studb`.`GetRecordAsPage` $$ ),), ),)) BEGIN de ...

  7. Huge CSV and XML Files in Python, Error: field larger than field limit (131072)

    Huge CSV and XML Files in Python January 22, 2009. Filed under python twitter facebook pinterest lin ...

  8. LA 3485 (积分 辛普森自适应法) Bridge

    桥的间隔数为n = ceil(B/D),每段绳子的长度为L / n,相邻两塔之间的距离为 B / n 主要问题还是在于已知抛物线的开口宽度w 和 抛物线的高度h 求抛物线的长度 弧长积分公式为: 设抛 ...

  9. BZOJ3850: ZCC Loves Codefires

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=3850 题解:类似于国王游戏,推一下相邻两个元素交换的条件然后排个序就可以了. 代码: #inc ...

  10. NoSQL 数据库系统对比

    虽然SQL数据库是非常有用的工具,但经历了15年的一支独秀之后垄断即将被打破.这只是时间问题:被迫使用关系数据库,但最终发现不能适应需求的情况不胜枚举. 但是NoSQL数据库之间的不同,远超过两 SQ ...