VC程序在Win32环境下动态链接库(DLL)编程原理

本文详细讲述了VC程序在Win32环境下动态链接库(DLL)编程原理。分享给大家供大家参考。具体分析如下:

一般比较大的应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。其中可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE文件中,会产生一些问题:一个缺点是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;另一个缺点是,在编写大的EXE程序时,在每次修改重建时都必须调整编译所有源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。

Windows系统平台上提供了一种完全不同的较有效的编程和运行环境,你可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。Microsoft Windows自己就将一些主要的系统功能以DLL模块的形式实现。例如IE中的一些基本功能就是由DLL文件实现的,它可以被其它应用程序调用和集成。

一般来说,DLL是一种磁盘文件(通常带有DLL扩展名),它由全局数据、服务函数和资源组成,在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分。如果与其它DLL之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL模块中包含各种导出函数,用于向外界提供服务。Windows在加载DLL模块时将进程函数调用与DLL文件的导出函数相匹配。

在Win32环境中,每个进程都复制了自己的读/写全局变量。如果想要与其它进程共享内存,必须使用内存映射文件或者声明一个共享数据段。DLL模块需要的堆栈内存都是从运行进程的堆栈中分配出来的。
DLL现在越来越容易编写。Win32已经大大简化了其编程模式,并有许多来自AppWizard和MFC类库的支持。

一、导出和导入函数的匹配

DLL文件中包含一个导出函数表。这些导出函数由它们的符号名和称为标识号的整数与外界联系起来。函数表中还包含了DLL中函数的地址。当应用程序加载DLL模块时时,它并不知道调用函数的实际地址,但它知道函数的符号名和标识号。动态链接过程在加载的DLL模块时动态建立一个函数调用与函数地址的对应表。如果重新编译和重建DLL文件,并不需要修改应用程序,除非你改变了导出函数的符号名和参数序列。
简单的DLL文件只为应用程序提供导出函数,比较复杂的DLL文件除了提供导出函数以外,还调用其它DLL文件中的函数。这样,一个特殊的DLL可以既有导入函数,又有导入函数。这并不是一个问题,因为动态链接过程可以处理交叉相关的情况。
在DLL代码中,必须像下面这样明确声明导出函数:

代码如下:

__declspec(dllexport) int MyFunction(int n);

但也可以在模块定义(DEF)文件中列出导出函数,不过这样做常常引起更多的麻烦。在应用程序方面,要求像下面这样明确声明相应的输入函数:

代码如下:

__declspec(dllimport) int MyFuncition(int n);

仅有导入和导出声明并不能使应用程序内部的函数调用链接到相应的DLL文件上。应用程序的项目必须为链接程序指定所需的输入库(LIB文件)。而且应用程序事实上必须至少包含一个对DLL函数的调用。

二、与DLL模块建立链接

应用程序导入函数与DLL文件中的导出函数进行链接有两种方式:隐式链接和显式链接。所谓的隐式链接是指在应用程序中不需指明DLL文件的实际存储路径,程序员不需关心DLL文件的实际装载。而显式链接与此相反。
采用隐式链接方式,程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。
显式链接方式对于集成化的开发语言(例如VB)比较适合。有了显式链接,程序员就不必再使用导入文件,而是直接调用Win32 的LoadLibary函数,并指定DLL的路径作为参数。LoadLibary返回HINSTANCE参数,应用程序在调用GetProcAddress函数时使用这一参数。GetProcAddress函数将符号名或标识号转换为DLL内部的地址。假设有一个导出如下函数的DLL文件:

代码如下:

extern "C" __declspec(dllexport) double SquareRoot(double d);

下面是应用程序对该导出函数的显式链接的例子:

代码如下:

typedef double(SQRTPROC)(double);
HINSTANCE hInstance;
SQRTPROC* pFunction;
VERIFY(hInstance=::LoadLibrary("c://winnt//system32//mydll.dll"));
VERIFY(pFunction=(SQRTPROC*)::GetProcAddress(hInstance,"SquareRoot"));
double d=(*pFunction)(81.0);//调用该DLL函数

在隐式链接方式中,所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载在到内存中;但如果采用显式链接方式,程序员可以决定DLL文件何时加载或不加载。显式链接在运行时决定加载哪个DLL文件。例如,可以将一个带有字符串资源的DLL模块以英语加载,而另一个以西班牙语加载。应用程序在用户选择了合适的语种后再加载与之对应的DLL文件。

三、使用符号名链接与标识号链接

在Win16环境中,符号名链接效率较低,所有那时标识号链接是主要的链接方式。在Win32环境中,符号名链接的效率得到了改善。Microsoft现在推荐使用符号名链接。但在MFC库中的DLL版本仍然采用的是标识号链接。一个典型的MFC程序可能会链接到数百个MFC DLL函数上。采用标识号链接的应用程序的EXE文件体相对较小,因为它不必包含导入函数的长字符串符号名。

四、编写DllMain函数

DllMain函数是DLL模块的默认入口点。当Windows加载DLL模块时调用这一函数。系统首先调用全局对象的构造函数,然后调用全局函数DLLMain。DLLMain函数不仅在将DLL链接加载到进程时被调用,在DLL模块与进程分离时(以及其它时候)也被调用。下面是一个框架DLLMain函数的例子。

代码如下:

HINSTANCE g_hInstance;
extern "C" int APIENTRY DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)
{
if(dwReason==DLL_PROCESS_ATTACH)
{
TRACE0("EX22A.DLL Initializing!/n");
//在这里进行初始化
}
else if(dwReason=DLL_PROCESS_DETACH)
{
TRACE0("EX22A.DLL Terminating!/n");
//在这里进行清除工作
}
return 1;//成功
}

如果程序员没有为DLL模块编写一个DLLMain函数,系统会从其它运行库中引入一个不做任何操作的缺省DLLMain函数版本。在单个线程启动和终止时,DLLMain函数也被调用。正如由dwReason参数所表明的那样。

五、模块句柄

进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识。进程自己还有一个HINSTANCE句柄。所有这些模块句柄都只有在特定的进程内部有效,它们代表了DLL或EXE模块在进程虚拟空间中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同的,这个两种类型可以替换使用。进程模块句柄几乎总是等于0x400000,而DLL模块的加载地址的缺省句柄是0x10000000。如果程序同时使用了几个DLL模块,每一个都会有不同的HINSTANCE值。这是因为在创建DLL文件时指定了不同的基地址,或者是因为加载程序对DLL代码进行了重定位。
模块句柄对于加载资源特别重要。Win32 的FindResource函数中带有一个HINSTANCE参数。EXE和DLL都有其自己的资源。如果应用程序需要来自于DLL的资源,就将此参数指定为DLL的模块句柄。如果需要EXE文件中包含的资源,就指定EXE的模块句柄。
但是在使用这些句柄之前存在一个问题,你怎样得到它们呢?如果需要得到EXE模块句柄,调用带有Null参数的Win32函数GetModuleHandle;如果需要DLL模块句柄,就调用以DLL文件名为参数的Win32函数GetModuleHandle。

六、应用程序怎样找到DLL文件

如果应用程序使用LoadLibrary显式链接,那么在这个函数的参数中可以指定DLL文件的完整路径。如果不指定路径,或是进行隐式链接,Windows将遵循下面的搜索顺序来定位DLL:

1. 包含EXE文件的目录,
2. 进程的当前工作目录,
3. Windows系统目录,
4. Windows目录,
5. 列在Path环境变量中的一系列目录。

这里有一个很容易发生错误的陷阱。如果你使用VC++进行项目开发,并且为DLL模块专门创建了一个项目,然后将生成的DLL文件拷贝到系统目录下,从应用程序中调用DLL模块。到目前为止,一切正常。接下来对DLL模块做了一些修改后重新生成了新的DLL文件,但你忘记将新的DLL文件拷贝到系统目录下。下一次当你运行应用程序时,它仍加载了老版本的DLL文件,这可要当心!

七、调试DLL程序

Microsoft 的VC++是开发和测试DLL的有效工具,只需从DLL项目中运行调试程序即可。当你第一次这样操作时,调试程序会向你询问EXE文件的路径。此后每次在调试程序中运行DLL时,调试程序会自动加载该EXE文件。然后该EXE文件用上面的搜索序列发现DLL文件,这意味着你必须设置Path环境变量让其包含DLL文件的磁盘路径,或者也可以将DLL文件拷贝到搜索序列中的目录路径下。

希望本文所述对大家的VC程序设计有所帮助。

(0)

相关推荐

  • windows服务器下IIS6/7下PHP 无法加载 php_curl.dll 等动态链接库

    问题: PHP 在 IIS6 或 IIS7 中无法加载 php_curl.dll 动态链接库. 解决方法: 在PHP手册中有相关说明:php_curl.dll:CURL,客户端 URL 库函数库:需要:libeay32.dll,ssleay32.dll(已附带) 所以只要将 libeay32.dll,ssleay32.dll 这两个库复制到 %WINDOWS% 目录下即可. PS:网上有很多人说将以上两个库复制到 %SYSTEM32%,而这完全不起作用,文章只会COPY,又不经考证,简直就是误人

  • 在Visual Studio中用C++语言创建DLL动态链接库图文教程

    什么是DLL(动态链接库)? DLL是一个包含可由多个程序同时使用的代码和数据的库.例如:在Windows操作系统中,Comdlg32 DLL执行与对话框有关的常见函数.因此,每个程序都可以使用该DLL中包含的功能来实现"打开"对话框.这有助于促进代码重用和内存的有效使用.这篇文章的目的就是让你一次性就能了解和掌握DLL. 为什么要使用DLL(动态链接库)? 代码复用是提高软件开发效率的重要途径.一般而言,只要某部分代码具有通用性,就可以将它构造成相对独立的功能模块并在之后的项目中重复

  • VC创建DLL动态链接库的方法

    本文实例讲述了VC创建DLL动态链接库的方法.分享给大家供大家参考.具体实现方法如下: 头文件中声明 t.h #ifdef DLL_API #else #define DLL_API extern "C" _declspec(dllimport) #endif DLL_API int a(int cnt); DLL_API int b(int cnt); .h文件引入 #define DLL_API extern "C" _declspec(dllexport) #

  • C++编写DLL动态链接库的步骤与实现方法

    本文实例讲述了C++编写DLL动态链接库的步骤与实现方法.分享给大家供大家参考,具体如下: 在写C++程序时,时常需要将一个class写成DLL,供客户端程序调用.这样的DLL可以导出整个class,也可以导出这个class的某个方法. 一.导出整个class 方法很简单,只需要在类的头文件中class和类名之间加上_declspec(dllexport),同时在另外一份提供给客户端调用程序使用的类的头文件中class和类名之间加上_declspec(dllimport).为了能让客户端程序和D

  • Python 调用VC++的动态链接库(DLL)

    1. 首先VC++的DLL的导出函数定义成标准C的导出函数: 复制代码 代码如下: #ifdef LRDLLTEST_EXPORTS #define LRDLLTEST_API __declspec(dllexport) #else #define LRDLLTEST_API __declspec(dllimport) #endif extern "C" LRDLLTEST_API int Sum(int a , int b); extern "C" LRDLLTE

  • VC程序在Win32环境下动态链接库(DLL)编程原理

    本文详细讲述了VC程序在Win32环境下动态链接库(DLL)编程原理.分享给大家供大家参考.具体分析如下: 一般比较大的应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作.其中可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用.在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE文件中,会产生一些问题:一个缺点是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费:另一个缺点是

  • Qt程序中调用C#编写的dll(推荐)

    1.打开Visual Studio,新建一个C#的Class Library项目(这里选择的是.Net Framework 4),项目名为CSharpDll. 2.由于默认没有引入Forms等UI库,先在reference中添加引用System.Windows.Forms以便可以在测试中使用MessageBox等. 3.最终C#编写的dll的源代码如下图所示,命名空间为CSharpDll,公共类为CSharpClass. using System; using System.Collection

  • C语言创建动态dll和调用dll(visual studio 2013环境下)

    第一部分:创建动态dll库. 1.打开visual studio 创建一个控制台应用程序. 2.选择DLL,空项目. 3.点击源文件,创建一个main.c文件 4.在main.c中写入一个简单的函数,内容如下: __declspec(dllexport) int mymax(int a,int b){ return a + b; } 5.编译生成. 6.在项目的目录有dll和lib两个生成好的文件. 第二部分:在新建项目中使用dll. 7.新建一个c的控制台应用程序UseDll,把Dll.dll

  • linux环境下java程序打包成简单的hello world输出jar包示例

    本文实例讲述了linux环境下java程序打包成简单的hello world输出jar包.分享给大家供大家参考,具体如下: 1. linux必须已安装java环境,测试 #javac -version javac 1.8.0_20 说明java环境已安装好了 2. 建立目录helloworld,在目录下建立helloworld.java文件 #cd ~ ~# mkdir helloworld ~# cd helloworld ~/helloworld# vi helloworld.java he

  • 微信小程序web-view环境下H5跳转小程序页面方法实例代码

    目录 引言 1.在H5页面引入JSSDK 2.跳转至小程序页面方法 总结 引言 一般的,web-view组件的src属性指定的H5页面之间,可以正常的采用超级链接a标记对进行页面之间的条转.但是web-view页面要想通过手指触碰返回小程序页面,就无法使用超级链接a标记了.那么这个问题应该如何解决呢? 1.在H5页面引入JSSDK 首先需要在H5页面中引入JSSDK,它可以让H5页面的js文件执行微信小程序的部分API命令.H5页面引入JSSDK的代码如下所示. <script src="

  • ASP.NET/C#中如何调用动态链接库DLL

    动态链接库(也称为DLL,即为"Dynamic Link Library"的缩写)是Microsoft Windows最重要的组成要素之一,打开Windows系统文件夹,你会发现文件夹中有很多DLL文件,Windows就是将一些主要的系统功能以DLL模块的形式实现. 动态链接库是不能直接执行的,也不能接收消息,它只是一个独立的文件,其中包含能被程序或其它DLL调用来完成一定操作的函数(方法.注:C#中一般称为"方法"),但这些函数不是执行程序本身的一部分,而是根据进

  • 关于.net环境下跨进程、高频率读写数据的问题

    一.需求背景 1.最近项目要求高频次地读写数据,数据量也不是很大,多表总共加起来在百万条上下. 单表最大的也在25万左右,历史数据表因为不涉及所以不用考虑, 难点在于这个规模的热点数据,变化非常频繁. 数据来源于一些检测设备的采集数据,一些大表,有可能在极短时间内(如几秒钟)可能大部分都会变化, 而且主程序也有一些后台服务需要不断轮询.读写某种类型的设备,所以要求信息交互时间尽可能短. 2.之前的解决方案是把所有热点数据,统一加载到共享内存里边,到也能够支撑的住(毫秒级的),但是由于系统架构升级

  • 解决.net framework 4.0环境下遇到版本不同编译不通过的方法详解

    本文内容:1.问题引出2.问题解决3.原因分析 最近部门的开发环境都更新到了WIN7+.NET framework4+VS2010上,在体验新技术和新环境带给我们提高效率的方式方法的同时也带来了一些兼容性的问题:这几天项目闲暇时在研究SQLite,在做实验的时候碰到个问题,代码编译通过执行时反复异常中断,查到后面原来是SQLite.dll是在framework2.0环境下编译的而现在的运行环境是framework4.0,所以就出现了运行异常,如图: 由于以后的开发过程中,可能会引用一些第三方的组

  • 木马程序Trojan-Spy.Win32.Agent.cfu清除方法

    木马程序Trojan-Spy.Win32.Agent.cfu 该样本程序是一个使用Delphi编写的程序,程序采用MEW 1.x加壳企图躲避特征码扫描,长度为67,908字节,图标为windows默认图标,病毒扩展名为exe,主要传播途径网页挂马.文件捆绑.黑客攻击. 病毒分析 该样本程序被激活后释放systen.dll文件到%systemroot%\system32目录下,释放451062.dll文件(该文件名为6位或7位随机数字)到%systemroot%目录下,运行批处理删除自身: 添加注

  • PHP用mb_string函数库处理与windows相关中文字符及Win环境下开启PHP Mb_String方法

    PHP用mb_string函数库处理与windows相关中文字符 昨天想批处理以前下载的一堆文件,把文件里的关键内容用正则匹配出来,集中处理.在操作文件时遇到一个问题,就是windows操作系统中的编码问题. 我们都知道windows中(当然是中文版),文件名和文件内容等编码都是gbk,而我们在开发过程中,IDE里的编码则是UTF-8,(这里不讨论为什么等等问题, 只考虑怎么把编码转变成一样的)所以导致我写的UTF-8编码的正则模式字符串中的中文在gbk编码的文件中并不能正确匹配. 一开始,我并

随机推荐