Basic DLL Theory
来源:互联网 发布:ubuntu php5 安装路径 编辑:程序博客网 时间:2024/05/23 02:04
现在,随着.NET的普及和完善,Visual C++和MFC正在谈出主流。越来越多的人在使用其他的语言开发Windows程序,但是从技术角度而言,Visual C++依然是最强大的编程语言(工具)之一,特别是对于开发Windows程序,而MFC作为应用程序框架类库,已经成为了Windows程序设计的C++封装典范。潘爱民老师曾说过,“学语言应该学习C++,要开发Windows应用程序应该看看MFC”。C++是程序语言的宝库,MFC是开发Windows应用程序的技术宝库。但是由于MFC非常庞大,如何有效地学习它是大多数初学者感到困惑的问题。而我认为最好的方法应该是通过,学习兴趣+一本好的教材+积累总结。如果我们不是天才,那么学习任何东西都需要时间的积累,我始终相信天道酬勤这句话,与大家共勉!以前虽然学过动态连接库方面的知识,却不是很系统,想起以前老师总是教导我们说,“好记性不如烂笔头”,我相信只有通过不断的总结,才能够更好地掌握一门知识。
From: Visual C++ 技术内幕(第四版) David Kruglinski 1992年第一版
PS:
u 作者Dave,是一位杰出的程序设计者同时也是旅行家和户外活动爱好者,不幸的是,在1997年4月17日,他在华盛顿州的一个峡谷飞行时不幸遇难,终年49岁。
u 第四版主要针对Visual C++ 5.0版本,以Windows NT 4.0与Windows 95或更高版本的32位Windows为OS平台,以MFC 4.21为基础,全面介绍了各种MFC类库应用程序的开发过程。
如果我们要编写一个标准的模块软件的话,一定会对DLL感兴趣。C++的类就是一种模块,但是,类是创建时(build-time)的模块,而DLL是运行时的模块。我们在编写庞大的EXE程序时,每次作了修改都要重新编译并测试,而现在我们可以编写小的DLL模块,然后单独测试。例如,我们可以把C++类放到一个DLL里,在编译连接后可能只有12KB大小。客户程序在运行时,可以快速装载并连接到DLL上。MS Windows本身的一些主要的功能都使用了DLL。而现在编写DLL也很容易,Win32已经大大简化了编程模型,而且,AppWizard和MFC库对DLL也有了更多的支持。
1 基本DLL理论
Win32是如何把DLL结合到进程里去的呢?记住,进程是一个程序的运行主体,而程序是从磁盘上的EXE文件开始启动的。首先,DLL是磁盘上的一个文件(通常带DLL扩展名),包含全局数据、编译过的函数和资源,它们是进程的一部分。DLL经编译后,装入到一个预置的基地址,如果跟其他的DLL没有冲突的话,文件就被映射到进程中相同的虚拟地址上。DLL有各种导出函数,客户程序(首先装入DLL的程序)导入这些函数。Windows在装入DLL时会对导入和导出作匹配。
说明:Win32 DLL允许导出全局变量,就像导出函数一样。
在Win32中,每一个进程对DLL的可读写全局变量都有自己的私有拷贝。如果我们想在进程间共享内存,我们或者可以使用内存映射文件,或者可以声明一个共享数据区(具体见Jeffrey Richter)。只要DLL申请堆内存,它就从客户进程的堆中进行内存分配。
1.1 导入如何与导出相匹配
DLL包含一个导出函数表,我们可以通过函数的符号化的名字和(可选)称为序号的整数识别这些函数。函数表也包含了函数在DLL内的地址。当客户程序首先装入DLL时,它并不知道它将要调用的函数的地址,但它知道符号名或序号。动态连接的进程然后建立一张表,把客户的调用与DLL里函数的地址连接起来。如果我们编辑并重建了DLL,我们并不需要重建客户程序,除非我们改变了函数名或参数序列。
说明:在简单的情况下,只有一个EXE文件从一个或多个DLL导入函数;而在实际情况下,许多DLL调用了其他DLL里的函数。因此,一个特殊的DLL可以同时有导入和导出。这样做当然没有问题,因为动态连接进程可以控制交叉关联(cross-dependency)。
在DLL代码中,我们必须显式声明导出函数,类似这样:
(另一种办法是在模块定义[DEF]文件中列出所有的导出函数,但这通常很麻烦。)在客户方面,我们需要声明对应的导入函数,类似这样:
如果我们使用了C++,则编译器会为MyFunction产生一个其他语言不能使用的修饰名。这些修饰名很长,编译器根据类名、函数名和参数类型产生修饰名,它们在工程的MAP文件中被列出。如果我们希望使用普通名MyFunction,则必须用下面这种方式书写函数声明:
说明:默认情况下,编译器用__cdecl参数传送约定,这就意味着,调用程序应从栈里弹出参数。有些客户程序可能要求__stdcall约定(它代表了Pascal调用约定),这就意味着,被调用的函数将直接从栈里弹出。因此,我们在DLL导出声明里可能必须使用__stdcall修饰符。
要使客户连接到一个DLL,仅仅有导入声明还不够。客户工程必须为连接器指定导入库(LIB),而且客户程序必须实际调用了DLL的导出函数中的至少一个函数。调用语句必须在程序的可执行路径里。
1.2 隐式连接和显示连接
前面部分基本上介绍的是隐式连接,它是C++程序员为了使用DLL而经常使用的一种方法。当我们创建DLL时,连接器产生一个附加的导入LIB文件,其中包含了每个DLL的导出符号和(可选)序号,但没有代码。LIB文件是DLL的一个代理,它被加到客户程序的工程中。当创建客户(静态连接)时,导入的符号被匹配到LIB文件的导出符号,这样符号(或序号)被绑定进EXE文件里。LIB文件也包含了DLL文件名(但不是全路径名),文件名也被保存到EXE文件中。当客户装载后,Windows找到DLL并进行装载,然后根据符号或序号动态连接。
显示连接对于解释语言(如Microsoft Visual Basic等)更为合适,但如果需要的话,我们也可以在C++中使用。对于显示连接,我们不需要导入文件,而是调用Win32的LoadLibrary函数,指定DLL的路径名作为参数。LoadLibrary返回一个HINSTANCE参数,我们可以在GetProcAddress调用中使用该参数,该调用把一个符号(或序号)转换到DLL中的地址。假定我们有一个DLL导出这样的一个函数:
下面是客户显示连接到函数的一个例子:
对于隐式连接,所有的DLL都在客户被装载的时候被装载,但在显示连接的情况下,我们可以决定什么时候装载和卸除。显示连接允许我们在运行时决定装载哪个DLL,例如,我们有一个DLL带英文字符串资源,另一个DLL带西班牙文字字符串资源,那么应用程序可以在用户选择了一种语言后选择装载适当的DLL。
1.3 符号连接和序号连接
在Win16里,序号连接更为有效,而且也是人们乐意采用的一种方式;在Win32里,符号连接效率有了改进,Microsoft现在推荐这种方式超过了序号连接。然而,MFC库的DLL版本使用了序号连接。一个典型的MFC程序可能会连接MFC DLL中的上百个函数,而序号连接可以使程序的EXE文件很小,因为它没有包含导入函数的长长的符号名。如果我们创建自己的DLL时使用了序号连接,则必须在工程的DEF文件里指定序号。在Win32环境里,DEF文件没有其他太多的用途。
1.4 DLL入口点——DllMain
默认情况下,连接器为DLL指定主入口点 _DllMainCRTStartup。当Windows加载DLL时,它调用该函数,该函数首先调用全局对象的构造函数,然后调用全局函数DllMain(它假设我们编写了DllMain函数)。DllMain不仅在DLL被连接到进程时被调用,而且在断开进程的连接和其他相应的时候也被调用。下面是DllMain函数的框架:
如果我们没有为DLL编写DllMain函数,则会从运行库里导进一个什么也不做的函数版本。
DllMain函数也在独立线程被启动和终止时被调用,其中的参数dwReason指出了调用的原因。Ritcher的书介绍了所有这些我们该知道的主题。
1.5 实例句柄——装载资源
进程中的每一个DLL都被一个唯一的32位HINSTANCE值所标识。此外,进程本身有一个HINSTANCE值。所有这些实例句柄只有在进程内部才有效,它们代表了DLL或EXE的起始虚拟地址。在Win32里,HINSTANCE和HMODULE值是相同的,两种类型可相互转换。进程(EXE)实例句柄几乎总是0x400000,而装入在默认基地址的DLL的句柄是0x10000000。如果程序使用了多个DLL,则每个都有不同的HINSTANCE值,这或者是因为DLL有不同的基地址(基地址在创建时被指定),或者是因为装载器把DLL代码作了拷贝并进行了重定位。
实例句柄对装载资源特别重要。Win32函数FindResource带一个HINSTANCE参数。EXE和DLL可以拥有各自的资源。如果我们从DLL中获取资源,则必须指定DLL的实例句柄;如果我们从EXE文件中获取资源,则必须指定EXE的实例句柄。
如何获得实例句柄呢?
如果想获得EXE的句柄,我们可以用NULL参数调用Win32的GetModuleHandle函数;如果想获得DLL的句柄,我们可以DLL的名字作为参数调用GetModuleHandle函数。后面我们将看到,MFC库通过顺序查找各个模块来进行资源装载。
1.6 客户程序如何找到DLL
如果用LoadLibrary显示连接DLL的话,我们可以指定DLL的全路径名。如果我们没有指定路径名,或者使用了隐式连接,则Windows将使用下面的搜索序列定位DLL:
1. 包含EXE文件的目录
2. 进程的当前目录
3. Windows的系统目录
4. Windows目录
5. 在Path环境变量里列出的目录
这里有一个很容易掉入的陷阱。我们创建一个DLL工程,然后把DLL文件拷贝到系统目录下,再从一个客户程序运行DLL。这样做当然很好。下一次,我们做了一些修改并重建了DLL,但忘记把DLL文件拷贝到系统目录下。这样,当再次运行客户程序时,它装载的仍是原来的DLL版本。一定要小心!
1.7 调试DLL
Developer Studio使调试DLL很容易,只要从DLL工程启动调试器即可。第一次这样做的时候,调试器会请求给出客户EXE文件的路径。之后,每次从调试器“运行”DLL时,调试器会装入EXE,而EXE用搜索序列找到DLL。这就意味着,我们必须或者设置Path环境变量以指向DLL,或者把DLL拷贝到搜索序列中的目录下。
Note: wcdj 于2010-1-5记 下一次介绍和总结MFC DLL(扩展的和正规的)
- Basic DLL Theory
- Basic Management Theory
- Theory of Basic Human Values
- Basic Wave Theory by Ib Bang
- Basic color schemes - Introduction to Color Theory
- 3D Graphics with OpenGL-Basic Theory
- Basic Theory of Physically-Based Rendering
- Basic Theory of Physically-Based Rendering
- [SinGuLaRiTy-1003] Number Theory-Basic 数论
- 3D Graphics with OpenGL Basic Theory
- OpenGL 基础知识-3D Graphics with OpenGL Basic Theory
- Creating a Windows DLL with Visual Basic
- 利用Visual Basic把ASP编写成DLL
- 利用Visual Basic把ASP编写成DLL
- Visual Basic 编译真正的Dll动态链接库文件
- Dev-C++制作dll文件供Visual Basic调用程序
- Graph Theory
- Probability Theory
- CListCtrl中CheckBox状态的获取与设置
- 短信分割
- oracle 序列
- 好高兴啊
- SOA首次接触
- Basic DLL Theory
- 关于CSDN空间报错引起的
- Flex 4 beta中的视口和滚动功能介绍
- 删掉java注释
- 系统活动信息监测工具——sar
- Professional Assembly Language (Programmer to Programmer)
- 怎样屏蔽集成声卡?
- 文件过滤驱动的开发的文件系统监视问题
- 侦测伺服器性能的一个脚本