【windows核心编程】DLL相关(2)
关于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)的更多相关文章
- windows核心编程 DLL技术 【转】
注:本文章转载于网络,源地址为:http://blog.csdn.net/ithzhang/article/details/7051558 本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑 ...
- 《Windows核心编程系列》二十谈谈DLL高级技术
本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...
- 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。
windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...
- 《windows核心编程系列》十七谈谈dll
DLL全称dynamic linking library.即动态链接库.广泛应用与windows及其他系统中.因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要. windows中所有API ...
- 《Windows核心编程》读书笔记 上
[C++]<Windows核心编程>读书笔记 这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对 ...
- C++Windows核心编程读书笔记
转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔 ...
- 【转】《windows核心编程》读书笔记
这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入 ...
- 《windows核心编程系列》二十一谈谈基址重定位和模块绑定
每个DLL和可执行文件都有一个首选基地址.它表示该模块被映射到进程地址空间时最佳的内存地址.在构建可执行文件时,默认情况下链接器会将它的首选基地址设为0x400000.对于DLL来说,链接器会将它的首 ...
- 《windows核心编程系列》十六谈谈内存映射文件
内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件.将文件映射到内存中后,我们就可以在内存中操作他们了 ...
- windows核心编程 - 线程同步机制
线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...
随机推荐
- eclipse导入maven web 项目 但是不显示成web 项目
http://blog.csdn.net/jun55xiu/article/details/9028403 1:导入Maven webapp项目(以extdirectspring-demo为例): i ...
- jackson基于注解的简单使用
Jackson提供了一系列注解,方便对JSON序列化和反序列化进行控制,下面介绍一些常用的注解. 1.@JsonIgnore 此注解用于属性上,作用是进行JSON操作时忽略该属性. 2.@JsonFo ...
- Java学习笔记之:Java引用数据类型之字符串
一.简介 字符串广泛应用在Java编程中,在Java中字符串属于对象,Java提供了String类来创建和操作字符串. 创建字符串最简单的方式如下: String greeting = "H ...
- Android:实现数组之间的复制
System提供了一个静态方法arraycopy(),我们可以使用它来实现数组之间的复制 System.arraycopy(src, srcPos, dst, dstPos, length); src ...
- C# Java间进行RSA加密解密交互(二)
原文:C# Java间进行RSA加密解密交互(二) 接着前面一篇文章C# Java间进行RSA加密解密交互,继续探讨这个问题. 在前面,虽然已经实现了C# Java间进行RSA加密解密交互,但是还是与 ...
- 持久化框架Hibernate 开发实例(二)
1 简述 通过使用Hibernate框架,开发者可以使用面向对象的方式来进行数据库访问,从而取代 以前使用JDBC进行数据库访问的方式.通过使用Hibernate框架,web应用可以通过面向 对象的方 ...
- Nginx+Tomcat+Memcached负载均衡集群服务搭建
操作系统:CentOS6.5 本文档主要讲解,如何在CentOS6.5下搭建Nginx+Tomcat+Memcached负载均衡集群服务器,Nginx负责负载均衡,Tomcat负责实际服务,Memc ...
- PHP命名空间概念解析
1. PHP中的命名空间是什么? 什么是命名空间?“从广义上来说,命名空间是一种封装事物的方法.在很多地方都可以见到这种抽象概念.例如,在操作系统中目录用来将相关文件分组,对于目录中的文件来说,它就扮 ...
- linux源码Makefile详解
1.Makefile的作用 (1)决定编译哪些文件 (2)怎样编译这些文件 (3)怎样连接这些文件,最重要的是它们的顺序如何 2.Linux内核Makefile分类 ***************** ...
- C#中默认的修饰符
参考自Default visibility for C# classes and members (fields, methods, etc)? Classes and structs that ar ...