动态连接库(Dynamic Link Library,DLL)是一些编译过的可执行程序模块,它包含代c++
码、数据或资源,能够在应用程序中或其余DLL中被调用。动态连接库的文件扩展名通常为.dll,也能够是.drv(设备驱动程序)、.sys(系统文件)和.fon(字体文件)。DLL的应用很是普遍,能够实现多个应用程序问的代码和资源共享,是Windows Embedded Compact 7程序设计中的一个很是重要的组成部分。ide
12.1 dll概述模块化
当使用普通的函数库时,能够在程序连接时将库中的代码拷贝到可执行文件中,这是一种函数
静态连接。而在多个一样的程序执行时,系统将保留许多重复的代码副本,很容易形成内存资源的浪费。若是使用DLL动态连接库,那么在创建应用程序的可执行文件时,就没必要将DLL连接到程序中,只须要在应用程序运行时动态地装载DLL。装载时DLL将被映射到进程的地址空间中,所以使用DLL动态连接并不就是拷贝库代码,只是在程序中记录了函数的入口点和接口,在程序执行时才将库代码装入内存。因此无论多少程序使用了DLL,内存中都将只有该DLL的一个副本,当没有程序使用它时,系统就将它移出内存,减小了对内存和磁盘的要求。因而可知,使用DLL的一个明显的好处就是节省系统资源。测试
除了以上的优势外,使用DLL设计程序还有如下一些优势:字体
· 共享代码、资源和数据。DLL做为一种基于WindoWS的程序模块,不只能够包含可spa
执行代码,还能够包括数据和各类资源等,扩大了库文件的使用范围。操作系统
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的方法步骤。