1、动态链接库和ActiveX开发,第09章,本章主要内容,9.1 编写自己的DLL,9.1.1 DLL概述9.1.2 MFC中DLL的分类9.1.3 DLL中的导入导出函数9.1.4 DLL中的数据与资源9.1.5 DLL与应用程序的链接9.1.6 DLL开发举例,9.1.1 DLL概述,比较大的应用程序都是由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作完成整个软件系统的工作,其中可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序文件中,会带来一些不足:一是增加了应用程序的大小,占用更多的磁盘空间,程序运
2、行时也会消耗较大的内存空间,造成系统资源的浪费;二是在编写大的应用程序时,每次修改重建都必须编译所有的源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。Windows平台提供了一种较为有效的编程和运行环境,开发者可以将独立的程序模块创建为DLL(Dynamic Linkable Library)文件,并可对它们进行单独编译和测试。运行时,只有当EXE程序调用这些DLL模块时,系统才会将它们装载到内存空间,这种方式不仅减少了EXE文件的大小和对内存空间的占用,而且使这些DLL模块同时被多个应用程序使用,Windows自身就将一些主要功能以DLL模块形式实现。,9.1.1 DLL概述,一般
3、来说,DLL是一种二进制文件(通常带有DLL扩展名),它由全局数据、服务函数和资源组成。在运行时被系统加载到进程的虚拟地址空间中,成为调用进程的一部分。如果与其它DLL没有冲突,该文件通常映射到进程虚拟地址空间的同一地址上。DLL模块中包含各种导出函数,用于向外界提供服务,Windows在加载DLL模块时,将进程中的函数调用与DLL文件的导出函数进行匹配。在Windows环境中,每个进程都复制了自己的读/写全局变量,如果想和其它进程共享内存,必须使用内存映射文件或者声明一个共享数据段,DLL模块需要的内存都是从运行进程的堆栈中分配的。DLL编程越来越容易了,Windows大大简化了其编程模式,
4、而且有各种向导和MFC类库支持,下面主要讨论如何在VS2010环境中使用MFC编写DLL。,9.1.2 MFC中DLL的分类,带静态链接MFC的规则DLL 使用共享MFC DLL的规则DLLMFC扩展DLL,VS2010 MFC DLL向导对话框,1、带静态链接MFC的规则DLL使用,该类DLL采用静态方式连接到MFC的动态连接库,它的特点是在源文件里有一个继承自CWinApp的类。这种DLL应用程序的导出函数可以被任何Windows应用程序调用,因为该动态库包含了它所使用的所有MFC函数。其导出函数具有如下形式: extern “C” EXPORT YourExportedFunction(
5、 ); 如果没有extern “C”修饰,导出函数仅能被C+代码调用,DLL应用程序从CWinApp派生,但没有消息循环。它自动生成def文件,在派生类的InitInstance和ExitInstance成员函数中完成初始化和结束工作。,2. 使用共享MFC DLL的规则DLL,它和静态链接的DLL一样,也是内部使用MFC的DLL,导出函数是标准的C语言接口,可以被MFC和非MFC应用程序使用,具有def文件,在CWinApp派生类的InitInstance和ExitInstance成员函数中进行初始化和结束工作。这种DLL建立时使用的是MFC的动态共享库,但它的导出函数可以被任何Window
6、s应用程序所调用,包括使用MFC的应用程序。所有从DLL导出的函数应该加上如下语句:AFX_MANAGE_STATE(AfxGetStaticModuleState( )该宏用于正确切换MFC模块状态,AfxGetStaticModuleState函数用于获取模块状态,这个宏必须出现在任何调用MFC语句之前,包括声明对象、变量之前,因为它们的构造函数可能会调用MFC DLL,该宏在调用MFC DLL导出函数时保证模块状态的正确以及能访问到有效资源(如DLL中的对话框等),而且MFC模块结束时,自动恢复到以前的模块状态。提示:AFX_MANAGE_STATE宏不能用在静态链接的MFC DLL和M
7、FC扩展DLL中。如果要在动态链接MFC DLL中使用MFC OLE功能,则必须在DLL的InitInstance中调用AfxOleInitModule。同样,如果使用MFC数据库或DAO支持,必须调用AfxDbInitModule,使用MFC Socket支持,则要调用AfxNetInitModule。,3MFC扩展DLL,MFC扩展DLL,用来导出从MFC继承的类,也就是说,用这种类型的动态连接库,可以用来导出一个从MFC继承的类。它的导出函数只可以被使用MFC且动态链接到MFC的应用程序所使用。可以从MFC继承用户所需要的类,并把它提供给客户应用程序,也可给客户应用程序提供MFC或MFC
8、继承类的对象指针。MFC扩展DLL使用MFC动态链接方式创建,而且只能用于MFC类库所编写的应用程序。MFC扩展DLL和前两种DLL不同,它没有从CWinApp派生应用程序类。因此,程序员必须在DllMain函数(DLL的主函数)中为自己的动态链接库编写代码以实现DLL的初始化和结束清理工作。总之,MFC扩展DLL具有几个方面特征:(1)没有应用程序对象(派生自CWinApp);(2)必须有一个DllMain函数;(3)DllMain必须调用AfxInitExtensionModule函数,并且检查其返回值,如果返回0,DllMmain也返回0;(4)如果想导出CRuntimeClass类型的
9、对象或者资源(Resources),则需要提供一个初始化函数用来创建CDynLinkLibrary对象,并且初始化函数也要导出;(5)使用MFC扩展DLL的MFC应用程序必须有一个CWinApp派生类,在该派生类的InitInstance成员函数中调用MFC扩展DLL的初始化函数。,9.1.3 DLL中的导入导出函数,在动态链接库中,定义的函数有两种:导出函数(export function)和内部函数(internal function),导出函数可以被其它模块调用,内部函数只能在动态库内部使用。DLL文件中包含一个导出函数表,这些导出函数通过它们的符号名和整型标识号与外界联系,函数表中还包
10、含了DLL中函数的地址。当应用程序加载DLL模块时,它并不知道调用函数的实际地址,但它知道函数的符号名和标识号,动态链接在加载DLL模块时动态建立一个函数调用与函数地址的对应表,如果重新编译DLL文件,并不需要修改应用程序,除非改变了导出函数的符号名和参数列表。简单的DLL文件只为应用程序提供导出函数,复杂的DLL文件除了提供导出函数外,还调用其它DLL文件中的函数,这样,一个动态链接库既有导出函数,又有导入函数。这并不会产生任何问题,因为动态链接过程可以正确处理交叉引用。,从DLL中导出函数,(1)使用DEF文件(2)使用关键字_declspec(dllexport)(3)使用AFX_EXT
11、_CLASS宏,使用关键_declspec(dllexport)不但能导出函数,而且可以导出数据、类或者类的成员函数,AFX_EXT_CLASS宏主要用来导出类。,(1)使用DEF文件,1模块定义文件(DEF)是由一个或多个用于描述DLL属性的语句组成的文本文件:NAME语句:指出生成DLL的文件名LIBRARY语句:指出DLL的内部名字,这个语句告诉链接器生成的是DLLDESCRIPTION语句:描述DLL的用途STACKSIZE语句:设置堆栈大小SECTIONS语句:设置段属性VERSION语句:给出DLL的版本号; :是注释语句。,(2)使用关键字_declspec(dllexport)
12、,2使用关键字_declspec(dllexport),可以从DLL中导出数据、函数、类或者类的成员函数。在DLL代码中,必须明确声明导出函数。_declspec(dllexport) int MyFunction(int n);相应地,在客户应用程序中,也必须明确声明相应的导入函数:_declspec(dllimport) int MyFuncition(int n);这个关键字不仅可以导出函数,而且可以导出类。用于导出类时,将导出类中所有public成员函数和成员变量。class _declspec(dllexport) CMyExportClass:public CObject / 仅有
13、导入和导出声明,并不能使客户应用程序的函数调用链接到相应的DLL文件,客户应用程序必须为链接程序指定所需要的输入库(LIB文件),而且客户应用程序至少包含对DLL中函数的一次调用。,(3)使用AFX_EXT_CLASS宏,3使用AFX_EXT_CLASS宏,MFC扩展DLL使用AFX_EXT_CLASS宏导出类,链接这种DLL的客户应用程序或者其它DLL,也必须使用这个宏导入类。MFC向导生成的应用程序框架支持在DLL和客户应用程序中使用这个宏。利用AFX_EXT_CLASS宏,MFC扩展DLL和客户应用程序就可以使用相同的头文件,给开发工作带来了便利。这个宏可以导出整个类,如:class A
14、FX_EXT_CLASS CMyExportClass:putlic CObject.也可以导出类中的某个成员:class CExampleDialog: public CDialog public: AFX_EXT_CLASS CExampleDialog (); AFX_EXT_CLASS int DoModal(); . / .,9.1.4 DLL中的数据与资源,DLL既可以导出函数、类,也可以导出数据和资源供客户应用程序使用。导出数据。数据导出一般采用以下两种方法。(1)使用DEF文件的DATA关键字导出(2)用_declspec(dllexport)关键字导出,(1)使用DEF文件的
15、DATA关键字导出,(1)使用DEF文件的DATA关键字导出,在DEF文件导出语句中,除了NONAME标志外,还有一个DATA标志,表明前面导出的不是函数,而是一个数据。例如:在DLL中定义了一个变量:int nVariable=0;在DEF文件中用下列语句导出:EXPORTSnVariable 5 DATA在客户应用程序中,用以下语句来使用DLL导出的数据:extern int nVariable; / 必须和DLL导出的名字相同printf(“Dll中的nVariable=%d”,*(int*)nVariable);这种方法使用的并不是变量本身,而是DLL中导出变量的指针,应用程序必须通过
16、强制指针类型转换来使用,在客户应用程序中也可以使用_declspec(dllimport)关键字来导入DLL导出的变量。,提示:在Visual C+早期版本中,用户也可以使用CONSTANT关键字导出数据,用法和DATA关键字一样。在VS2010中,建议使用DATA关键字。,(2)用_declspec(dllexport)关键字导出,用_declspec(dllexport)关键字导出,可以不使用DEF文件,在源程序中使用_declspec(dllexport)关键字来修饰要导出的变量。在客户应用程序中使用_declspec(dllimport)关键字来导入DLL中导出的变量。可以看出,使用_
17、declspec(dllexport)和_declspec(dllimport)导出导入数据是最简单的方法。DLL和客户应用程序一样,也可以拥有自己的资源。如果编写的DLL程序仅仅是用于存放共享资源,供其它程序使用,也就是说,在DLL中只有资源,如:图标、位图、字符串、声音、视频、对话框等,那么,这种DLL就属于纯资源DLL。使用这种纯资源DLL可以缩减可执行文件的大小,而且DLL中的资源可以被其它应用程序所共享,提高了性能。纯资源DLL编写比普通DLL编写要简单得多,首先创建一个WIN32 DLL项目,注意,不是MFC DLL,然后创建一个资源文件(.RC),并将其添加到资源DLL项目中,最
18、后添加一个初始化DLL的C+文件。,(2)用_declspec(dllexport)关键字导出,extern CBOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID )return 1;这是纯资源DLL所必需的代码,保存这个文件为C+源文件,编译这个资源DLL。在客户应用程序中调用这个DLL时,使用LoadLibrary函数装入资源DLL,使用FindResource和LoadResource装入各种资源,或者使用下列的特定的资源装入函数装入特定的资源:FormatMessage、LoadAccelerators、Lo
19、adBitmapLoadCursor、LoadIcon、LoadMenu、LoadString当资源使用结束时,应用程序必须调用FreeLibrary函数释放资源。,9.1.5 DLL与应用程序的链接,DLL不能单独执行,必须和客户应用程序链接才能使用。客户应用程序的导入函数与DLL文件中的导出函数有2种链接方式:隐式链接(Implicit Linking)和显式链接(Explicit Linking)。所谓隐式链接是指在应用程序中不必指明DLL文件的存储路径,程序员也不必关心DLL文件的实际装载,而显式链接正好相反。1. 隐式链接方式(Implicit Linking)2. 显式链接方式(E
20、xplicit Linking),1. 隐式链接方式(Implicit Linking),程序员在建立DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件,该文件包含了每一个导出函数的符号名和可选的标识号,但并不包含实际代码。LIB文件作为DLL的替代文件被编译到客户应用程序项目中。当通过动态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中的导出符号相匹配,这些符号或标识号就进入到生成的EXE文件中。LIB文件中也包含了对应的DLL文件名(不是完整的路径名),链接程序将其存储在EXE文件内部。在应用程序运行过程中,需要加载DLL文件时,Windows就会根据这些信息发现并
21、加载DLL,然后通过符号名或标识号实现对DLL中函数的动态链接。,2. 显式链接方式(Explicit Linking),这种方式对于集成化的开发语言(例如VC、VB)比较适合。有了显式链接,使用DLL的程序就不必使用导入文件,而是用LoadLibrary或MFC提供的AfxLoadLibrary显式的将需要的动态链接库装入内存,这两个函数的参数就是动态链接库的文件名,接下来用GetProcAddress()函数得到导出函数的地址,也就是导入的函数,GetProcAddress函数将符号名或标识号转换为DLL内部地址。至此,就可以像使用应用程序自身的函数一样,自由地调用导入函数了,在应用程序退
22、出之前,需要用FreeLibrary或MFC提供的AfxFreeLibrary函数释放动态链接库。例如,有这样一个导出函数:extern C _declspec(dllexport) double SquareRoot(double d);下面的代码就是在客户应用程序中对该导出函数的显式链接。typedef double(SQRTPROC)(double);HINSTANCE hInstance;SQRTPROC* pFunction;VERIFY(hInstance=:LoadLibrary(c:winntsystem32mydll.dll);VERIFY(pFunction=(SQRTPR
23、OC*):GetProcAddress(hInstance,SquareRoot);double d=(*pFunction)(81.0);/ 调用该DLL中的函数,在隐式链接方式中,所有被应用程序调用的DLL文件都会在应用程序文件加载时被加载到内存,如果采用显式链接方式,程序员就可以决定何时加载DLL、何时卸载DLL。例如,应用程序可以根据用户选择的语种,加载与之对应的DLL文件,对于选择了英语的用户,加载英语字符串资源DLL,对于选择了西班牙语的用户,则加载西班牙语字符串资源。Windows遵循下列的次序来搜索和定位DLL:(1)EXE文件所在目录(2)进程当前工作目录(3)Windows
24、系统目录(4)Windows目录(5)列在Path环境变量中的一系列目录在使用MFC开发DLL相关应用程序时,对于初学者,建议采用隐式链接。,9.1.6 DLL开发举例,在这个实例中,需要开发3个应用程序模块:(1)S9_1是一个DLL程序,它导出一个函数。(2)S9_2 也是一个DLL程序,它导出一个类。(3)S9_3 是一个客户应用程序,在它里面,调用了S9_1、S9_2导出的函数和类。在开发过程中,通过具体操作对本章的基本概念和理论进行详细说明,帮助读者理解和掌握这些概念和技术。,1. S9_1使用“带静态链接MFC规则的DLL导出函数,(1)用MFC DLL向导创建动态链接库项目S9_
25、1。在VS2010主界面菜单上选择“文件|新建|项目”菜单项,打开“新建项目”对话框,在左侧选择Visual C+,右侧选择MFC DLL,项目名称输入S9_1,点击“确定”按钮,弹出“MFC DLL向导”对话框,选中“带静态链接MFC的规则DLL”单选框,如图9-2所示,其余保持默认,点击“完成”按钮,生成MFC DLL应用程序框架。(2)修改所生成项目的头文件S9_1,加入对导出函数的声明。(3)修改所生成项目的实现文件S9_1.cpp,添加导出函数的实现。(4)编译链接,生成动态链接库,所生成的DLL文件在后面的S9_3项目中使用。,2. S9_2使用MFC扩展DLL导出一个类,导出类使
26、用AFX_EXT_CLASS宏。,(1)利用MFC DLL向导创建S9_2动态链接库项目。(2)设计CDrawText类。(3)为CDrawText类添加成员变量。(4)为CDrawText类添加成员函数。(5)编译链接,生成S9_2.DLL动态链接库,供S9_3应用程序使用。,3S9_3项目的创建步骤,(1)创建S9_3项目。(2)将当前项目所需要的S9_1.dll、S9_2.dll、S9_1.lib、S9_2.lib文件加入进来。(3)添加菜单资源,并添加命令消息响应函数。(4)导入S9_1.dll中的函数AddFunction()。(5)向S9_3项目导入S9_2.dll中的类CDraw
27、Text。(6)修改CS9_3Doc类的头文件和C+文件,为使用CDrawText类的对象作准备。(7)使用从S9_2导出的类CDrawText。(8)修改CS9_3View:OnDraw函数。,运行界面,读者可以把S9_2.dll的功能扩充,在编写程序的同时,要注意DLL的特点,只要DLL对外提供的接口(导出函数、类、数据以及供其它程序使用的资源等)保持不变,其内部实现就不会影响客户应用程序的开发和实现。,9.2 编写自己的ActiveX控件,9.2.1 ActiveX控件的事件、属性和方法9.2.2 Active 控件开发实例,9.2.1 ActiveX控件的事件、属性和方法,Active
28、X是Microsoft的一个术语,它指一组包括控件、DLL、Active文档的组件,通常以动态链接库的形式存在,Active控件必须提供属性名、方法名以及参数,以便容器可以存取和改变Active控件的属性,Active控件的数据输入和函数调用必须通过容器,因此Active控件具备以下特点。1属性表示ActiveX控件状态的变量。ActiveX控件的属性有两种访问方式:使用成员变量和使用Get/Set方法,其属性又分为2种:固有属性(Stock properties):固有属性是已经被COleControl类实现了的属性,在VS2010中,应用向导支持的固有属性有:ActiveX控件背景色(Ba
29、ckColor)、标题(Caption)等,固有属性是带有普遍性的一些属性。自定义属性(Custom properties):程序员自己定义的属性,是和具体控件相关的属性。例如,如果编写了播放avi文件的控件,则当前播放的文件名就是自定义属性。2方法:控件容器可以调用的ActiveX控件方法。其方法分为2种:固有方法:已经被COleControl类实现的方法。COleControl类支持两个固有方法:DoClick方法和Refresh方法,其中,DoClick方法用于发送一个Click事件,而Refresh方法则用于立即更新ActiveX控件窗口。自定义方法:程序员自己定义的方法。例如,对于播
30、放avi文件的控件,Play、Stop等均为自定义方法。3事件:由ActiveX控件发送给控件容器的通知消息,ActiveX控件通过事件告诉控件容器某个事件发生了,如属性参数改变、用户单击、双击动作等,容器根据具体事件作出不同的响应,事件也分为2种:固有事件:已经由COleControl定义的事件,共有10种,典型的固有事件如Click事件,该事件为用户单击ActiveX控件时由ActiveX控件发送的消息,COleControl类中还为每个固有事件提供了一个发送事件的函数,这些函数可用于模拟相应的事件。例如,FireClick()函数向控件容器发送Click事件。自定义事件:程序员自己定义的
31、事件。例如,播放avi文件的控件,当avi文件播放完毕后向控件容器发送一个自定义事件播放完毕,用于通知控件容器进行相应处理。,9.2.2 Active 控件开发实例,1创建项目2添加固有属性3添加自定义属性,1创建项目,(1)在VS2010中,通过菜单“文件|新建|项目”,打开“新建项目”对话框,项目模板选择“MFC ActiveX控件”,“名称”输入“S9_4”,选择项目保存位置,如图9-10所示。,(2)单击“确定”按钮,弹出“MFC ActiveX 控件向导”对话框,选择“控件名称”,可以看到所创建的ActiveX控件的名字为S9_4,如图9-11所示,读者也可以修改为有意义的名字,本实
32、例不做修改,全部采用默认值,单击“完成”按钮,完成ActiveX项目的创建。,1创建项目,2添加固有属性,下面给S9_4控件添加设置背景色和前景色的功能。(1)单击“调试|选项和设置”,弹出“选项”对话框,如图9-12所示,选择“Workflow Designer|主题”,单击“新建”,弹出“Theme Configuration”对话框,如图9-13所示。(2)选择属性BackColor,弹出颜色对话框,选择合适的颜色。(3)单击确定按钮,返回到“选项”对话框,为控件增加了一个背景色属性。(4)重复(1)(3),为控件添加前景色属性(ForeColor)。 (5)在控件的C+文件S9_4Ct
33、l.cpp中,添加颜色属性页(6)在控件的OnDraw函数中,添加使用颜色属性的代码:,3添加自定义属性,(1)在VS2010环境的“类视图”标签中展开控件的库文件“S9_4Lib”,如图9-14所示,在“_DS9_4”节点上按鼠标右键,打开快捷菜单,从中选择“添加|添加属性”,打开“添加属性向导”对话框,如图9-15所示,为控件添加一个TextAlign属性,类型为LONG,访问方式为成员变量。(2)单击“完成”按钮,返回VS2010主界面。 (3)修改控件的文本对齐方式的消息响应函数 (4)接下来,需要修改自定义属性的对话框资源,在工作区的“资源视图”中展开对话框资源,双击Dialog|I
34、DD_PROPPAGE_S9_4,打开对话框资源编辑器,删除原有的Static Text控件,添加3个单选按钮和一个分组框控件,将第一个单选按钮的Group属性设为TRUE,其余两个设置为FALSE,也就是将它们三个分为一组,,4为CS9_4Ctrl类添加一些额外变量,(1)在控件的头文件S9_4Ctl.h中添加以下私有成员变量。 (2)在控件的C+文件S9_4Ctl.cpp中,修改构造函数CS9_4Ctrl:CS9_4Ctrl(),对以上4个变量进行初始化。 (3)修改控件的C+文件S9_4Ctl.cpp的CS9_4Ctrl:OnDraw函数,实现控件文本信息的输出。,5为控件添加鼠标移动的
35、消息响应函数,在VS2010开发环境中,通过类向导为CS9_4Ctrl类中添加WM_MOUSEMOVE消息响应函数OnMouseMove(UINT nFlags, CPoint point),并定位的该函数CS9_4Ctrl:OnMouseMove(UINT nFlags, CPoint point),添加以下代码:/void CS9_4Ctrl:OnMouseMove(UINT nFlags, CPoint point) / TODO: Add your message handler code here and/or call defaultm_CurPosition=point.x; /
36、 改变当前的位置Invalidate(); / 重画控件,自动调用OnDraw()函数COleControl:OnMouseMove(nFlags, point);,6为S9_4控件添加自定义事件,(1)在VS2010开发环境中,展开“类视图”中的控件节点“CS9_1Ctrl”,右键单击打开快捷菜单,从弹出的快捷菜单中选择“添加 | 添加事件”,打开“添加事件向导”对话框,如图9-18所示。(2)在“添加事件向导”对话框中,选择自定义事件,添加事件名称为ValuesMax,点击完成,关闭“添加事件向导”对话框。(3)定位到函数void CS9_4Ctrl:OnDraw函数,在函数的末尾处添加以
37、下事件引发代码,6为S9_4控件添加自定义事件,(4)编译链接程序,打开ActiveX Control Test Container工具进行S9_4控件测试。VS2010默认情况下,并没有安装该工具,可通过网络下载该工具(TstCon.exe),并将其复制到VS2010安装路径的Common7Tools目录下。然后通过“工具|外部工具”菜单项的打开“外部工具”对话框,添加ActiveX Control Test Container工具,如图9-19所示。(5)通过“工具|ActiveX Control Test Container”菜单项打开“ActiveX Control Test Container”工具,如图9-20所示,点击“New Control”按钮,弹出“Insert Control”对话框,选择S9_4控件,点击OK按钮。然后可以在ActiveX Control Test Container中测试控件了,如图9-21所示。在S9_4控件中数值为最大值时,在界面下部的事件窗口中会出现一条消息。,运行界面,至此,S9_4控件开发完成,读者可以自己创建一个应用程序,并在项目中使用S9_4控件,或者直接编写html网页来测试这个控件。,