动态链接库(Dynamic Link Library,DLL)是一些编译过的可执行程序模块,它包含代
码、数据或资源,可以在应用程序中或其他DLL中被调用。动态链接库的文件扩展名一般为.dll,也可以是.drv(设备驱动程序)、.sys(系统文件)和.fon(字体文件)。DLL的应用非常广泛,可以实现多个应用程序问的代码和资源共享,是Windows Embedded Compact 7程序设计中的一个非常重要的组成部分。
12.1 dll概述
当使用普通的函数库时,可以在程序链接时将库中的代码拷贝到可执行文件中,这是一种
静态链接。而在多个同样的程序执行时,系统将保留许多重复的代码副本,很容易造成内存资源的浪费。如果使用DLL动态链接库,那么在建立应用程序的可执行文件时,就不必将DLL链接到程序中,只需要在应用程序运行时动态地装载DLL。装载时DLL将被映射到进程的地址空间中,因此使用DLL动态链接并不就是拷贝库代码,只是在程序中记录了函数的入口点和接口,在程序执行时才将库代码装入内存。所以不管多少程序使用了DLL,内存中都将只有该DLL的一个副本,当没有程序使用它时,系统就将它移出内存,减少了对内存和磁盘的要求。由此可见,使用DLL的一个明显的好处就是节省系统资源。
除了以上的优点外,使用DLL设计程序还有以下一些优点:
・ 共享代码、资源和数据。DLL作为一种基于WindoWS的程序模块,不仅可以包含可
执行代码,还可以包括数据和各种资源等,扩大了库文件的使用范围。
DLL提供了共享资源的途径,例如位图、字体或者图表等都可以放到一个资源文件中,
并直接连接到应用程序中。如果将这些资源都放到DLL中,那么许多应用程序都可以直接使
用,而不必在内存里重复装入这些数据。
在16位的Windows中,DLL有自己的数据段,因此所有需要调用同一个DLL的应用程
序都能够访问同一个全局变量和静态变量。但是在32位的系统中,情况就不同了。因为DLL
的映像被映射到每个进程的地址空间,该DLL的所有数据将属于映射到的进程。值得注意的
是,尽管进程间不能共享DLL的数据,但是同一个进程的所有线程则可以共享。由于线程间
相互独立,因此在访问某一DLL全局变量时,要注意保持同步,以免冲突。
尽管DLL的映像被映射到每个进程的地址空间时,该DLL的所有数据将属于映射到的进
程,这也并不意味着没有办法在进程间共享DLL的数据。利用内存映射文件就可以实现,只要将数据存储到内存映射的共享区,那么一切需要调用DLL的应用程序都可以读取这些存储在内存中的共享区域的数据。
・ 可将系统模块化,方便升级。DLL技术对于开发大型软件系统也大有好处。如果使
用一个执行文件完成一个大型系统,那么程序将会很庞大,而且还可能存在许多重复
的功能。而如果将程序分成一系列的主程序和DLL,则可以减少开发的工作量并加
快开发的速度。而且,如果开发过程中就将一些功能模块做成DLL,那么在需要对
系统进行升级的时候,只需要升级个别DLL,然后用新的DLL文件覆盖掉旧的DLL
文件就.-1以Y,如此一来,就不需要对整个系统进行重新编译和链接,极大地方便了
系统的升级和维护。
・ 隐藏实现的细节。在某些情况下,用户可能想隐藏例程实现的细节,DLL就是一个
非常不错的实现方法。DLL的例程可以被应用程序访问,而不显示其中的代码细节。
还有很重要的一点就是DLL与语言无关。
12.2 dll的调用
12.2.1 静态调用
静态调用指由编译系统完成对DLL的加载并且在应用程序结束时对DLL进行释放。静态
调用相对于动态调用而言,其优点是简单实用,弊端就是不够灵活。
在Visual C++中静态调用DLL也非常简单,首先将动态连接库的.LIB文件加入到应用程
序的工程中,然后在使用DLL函数的文件里引用DLL的头文件即可。
静态调用不需要调用LoadLibrary和FreeLibrary,这是因为开发人员在建立一个DLL文
件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每一个DLL导出函
数的符号名和可选的标识号,但是并不包含实际的代码,LIB文件会作为DLL的替代文件被
编译到应用程序项目中。当开发人员通过静态链接方式编译并生成应用程序时,应用程序中的
调用函数与LIB文件中的导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。LIB
文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件
内部。当应用程序运行过程中需要加载DLL文件时,Windows将根据这些信息查寻并加载
DLL,然后通过符号名或标识号实现对DLL函数的动态链接。当加载应用程序的EXE文件时,
所有被应用程序调用的DLL文件都将被加载在到内存中。可执行程序直接通过函数名调用
DLL的输出函数,其调用方法与调用程序内部的其他的函数相同。
12.2.2 动态调用
动态调用是由开发人员使用APl函数手工加载和卸载DLL,以达到调用DLL的目的。动
态调用较之静态调用,在使用上更为复杂,但却能更加有效地使用内存,因此是编写大型应用
程序的重要方式。
动态调用是指在应用程序中使用LoadLibrary函数或MFC提供的AfxLoadLibrary函数显
式地调入所需的动态连接库,动态连接库的文件名即上面两个函数的参数,然后再使用
GetProeAddress获取所需引入的函数。完成以上操作后,就可以像使用本应用程序自定义的函数一样来调用引入函数了。在应用程序退出之前,应该使用FreeLibrary函数或MFC提供的
AfxFreeLibrary函数来释放动态连接库。
动态调用DLL的第l步就是调用LoadLibrary函数来加载DLL。该函数的定义如下:
HINSTANCE LoadLibrary( LPCTSTR ipLibFi leName);
参数lpLibFileName用于指定DLL的文件名,并且该文件名可以包含文件名的目录。如
果不包含文件名的目录,那么该函数将遵循下面的搜索顺序来定位DLL。
・ 包含EXE文件的目录
・ 进程的当前工作目录
・ Windows系统目录
・ Windows目录
・ 列在Path环境变量中的一系列目录
成功加载DLL后,函数将返回指向该DLL的句柄,否则将返回NULL。
成功执行第1步(加载DLL)之后,就可以执行第2个步骤了。执行该步骤的目的就是
获取DLL里的输出函数接口,可以通过GetProcAddress函数来实现该目标。GetProcAddress
函数的定义如下:
FARPROC GetProcAddress(
HMODULE hModule,
LPCWSTR lpprocName);
・ 参数hModule用于指定DLL旬柄,即LoadLibrary函数的返回值。
・ 参数lpProcName指定想要得到的函数名称。
如果函数执行成功,那么将返回指定函数的地址指针,否则返回NULL。如果DLL里有
N个需要获取的输出函数,就需要执行GetProcAddress函数N次,来获取这N个函数的地址。
获取DLL里的输出函数之后,直接调用输出函数即可。
动态调用DLL的最后一个步骤就是当不再使用DLL里的输出函数时,调用FreeLibrary
函数释放DLL,该函数的定义如下:
BOOL FreeLibrary(
HMODULE hLibModule)j
参数hLibModule用于指定DLL句柄,即LoadLibrary函数的返回值。
如果该函数成功的释放了DLL,将返回TRUE,否则返回FALSE。
12.3 dll的创建
本节将分别介绍如下3种类型的DLL动态链接库创建方法和创建过程。
● Windows Embedded Compact 7 DLL。
Windows Embedded Compact 7是指不使用MFC创建的DLL。Windows Embedded Compact 7导出函数通常使用标
准C接口,这些函数可以被MFC或非MFC应用程序调用。
・ MFC常规DLL(动态连接MFC)。 .
MFC常规DLL是使用MFC创建的,其导出函数也通常使用标准C接口,它们可以被
MFC或非MFC应用程序调用。按照与MFC链接方式的不同,MFC常规DLL又可以分为动
态连接和静态连接两种,前者使用MFC的动态链接库(即共享版本),后者使用MFC的静态
链接版本。
・ 纯资源DLL。
纯资源DLL只包含共享的资源,如菜单、字符串、图标、位图以及声音等。
下面就分别介绍以上3类DLL动态链接库的创建方法。
12.3.1 Windows Embedded Compact 7中 dll的创建
新建一个基于“Win32智能设备项目’’的项目,将项目名称设为MyCEDLL,实现页面
如图12-1所示。
图12-1创建动态函数链接库
选择yincheng_OS,如图12-2
图12-2选择程序开发环境yincheng_OS
选择DLL,如图12-3
图12-3选择程序类
单击“finish”按钮就完成了MyCEDLL工程的创建。
下面先来了解一下非MFC的DLL工程实现原理。
首先,每个DLL必须有一个入口点,这就如同使用C语言编写的应用程序必须有一个
WINMAlN函数一样。DllMain是一个缺省的入口函数,它负责初始化(Initialization)和结束
(Termination)工作。当一个新的进程或者该进程的新的线程访问DLL,以及访问DLL的每一
个进程或者线程不再使用此DLL时,都会调用DllMain函数。但是有一种特殊情况,那就是如
果使用TerminateProcess或TerminateThread方法结束进程或线程,就不会调用DllMain函数。
用户只需要打开MyCEDLL.cpp文件,就可以看到DllMain函数的实现,它的函数原型
如下:
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
・ 参数Moudle是动态库被调用时所传递来的一个指向自己的句柄
・ 参数ul reason for call是一个说明动态库被调用原因的标志。当进程或者线程装载、
卸载动态链接库的时候,操作系统便调用入口函数,并说明动态链接库被调用的原因。
该参数可以取如下所示的值:
>DLL PROCESS ATTACHt进程被创建。
>DLL THREAD ATTACH:线程被创建。
>LL PROCESS DETACH:进程被停止。
>LL THREAD DETACH:线程被停止。
・ 参数lpReserved是一个被系统所保留的参数。
在理解了DllMain函数的实现原理后,就要接着考虑输出函数的实现方法了。
输出函数需要在函数名称前加上修饰符declspec(dllexport),表示输出。此外,还有一种
修饰符extem”C”declspec(dllexport),它也表示输出,而且该类DLL不仅可以被c++调用,
还可以被c调用。在c++下定义c函数时,需要加上extem”C”关键词,用extern”C”来指明
该函数使用C的编译方式,输出的c函数可以从c代码里调用,extern”C”使得在C++中使用C编译方式成为可能。
本示例向导完成后,会自动导出3种类型示例符号:一个是导出了一个“C++类”、一个
是导出了一个“全局变量”、另外一个导出了一个“函数”。读者可以效仿示例进行添加自定义
的导出符号。这3个示例符号定义如下:
//此类是从MyCEDLL.dll导出的
class MYCEDLL_API CMyCEDLL{
public:
CMyCEDLL(void);
//TODO:在此添加您的方法
);
extern MYCEDLL_API int nMyCEDLL;
函数实现代码中的修饰符MYCEDLL_APl其实就是_declspec(dllexport),因为在
MyCEDLL.h文件中含有如下宏代码:
ifdef MYCEDLL―EXPORTS
#define MYCEDLL―API declspec(dllexport)
#else
#define MYCEDLL―API declspec(dllimport)
#endif
MYCEDLL_API int fnMyCEDLL(void);
下面就来为MyCEDLL.dll动态链接库增加一个输出函数TestDll。
首先在MyCEDLL.h头文件中添加TestDll函数的声明,代码如下:
extern”C”MYCEDLL_API void TestDll(void);
然后在MyCEDLL.cpp文件中添加如下所示的TestDll函数的实现代码:
extern "C" MYCEDLL_API void TestDll(void)
{
MessageBox(NULL,_T("此信息来自DLL"),_T("测试所编DLL"),MB_OK);
}
完成以上操作后,一个简单的DLL就编写完了。按下F5编译就会生成MyCEDLL.dll文件,将此文件下载到yincheng.OS\RelDir\VirtualPC_x86_Release目录下。在下面的一个小节中,将以MyCEDll.dll为例,介绍静态调用和动态调用该DLL的方法步骤。