📄 mfc教程7_ mfc的dll.htm
字号:
<P
align=justify>该类DLL应用程序动态链接到MFC,它输出的函数仅可以被使用MFC且动态链接到MFC的应用程序使用。和规则DLL相比,有以下不同:</P>
<OL>
<P align=justify>
<LI>它没有一个从CWinApp派生的对象;
<P></P>
<P align=justify></P>
<LI>它必须有一个DllMain函数;
<P></P>
<P align=justify></P>
<LI>DllMain调用AfxInitExtensionModule函数,必须检查该函数的返回值,如果返回0,DllMmain也返回0;
<P></P>
<P align=justify></P>
<LI>如果它希望输出CRuntimeClass类型的对象或者资源(Resources),则需要提供一个初始化函数来创建一个CDynLinkLibrary对象。并且,有必要把初始化函数输出。
<P></P>
<P align=justify></P>
<LI>使用扩展DLL的MFC应用程序必须有一个从CWinApp派生的类,而且,一般在InitInstance里调用扩展DLL的初始化函数。
<P></P></LI></OL>
<P align=justify>为什么要这样做和具体的代码形式,将在后面9.4.2节说明。</P>
<P align=justify></P>
<P align=justify>MFC类库也是以DLL的形式提供的。通常所说的动态链接到MFC
的DLL,指的就是实现MFC核心功能的MFCXX.DLL或者MFCXXD.DLL(XX是版本号,XXD表示调试版)。至于提供OLE(MFCOXXD.DLL或者MFCOXX0.DLL)和NET(MFCNXXD.DLL或者MFCNXX.DLL)服务的DLL就是动态链接到MFC核心DLL的扩展DLL。</P>
<P align=justify>其实,MFCXX.DLL可以认为是扩展DLL的一个特例,因为它也具备扩展DLL的上述特点。</P>
<OL>
<OL>
<P align=justify>
<LI><A name=_Toc445889074></A><A name=_Toc445782477></A><A
name=_Toc452640938></A><A name=_Toc457299036></A><B>DLL的几点说明</B>
<P></P></LI></OL></OL>
<OL>
<P align=justify>
<LI>DLL应用程序的入口点是DllMain。
<P></P>
<P align=justify>对程序员来说,DLL应用程序的入口点是DllMain。</P>
<P
align=justify>DllMain负责初始化(Initialization)和结束(Termination)工作,每当一个新的进程或者该进程的新的线程访问DLL时,或者访问DLL的每一个进程或者线程不再使用DLL或者结束时,都会调用DllMain。但是,使用TerminateProcess或TerminateThread结束进程或者线程,不会调用DllMain。</P>
<P align=justify>DllMain的函数原型符合DllEntryPoint的要求,有如下结构:</P>
<P align=justify>BOOL WINAPI DllMain (HANDLE hInst, </P>
<P align=justify>ULONG ul_reason_for_call,LPVOID lpReserved)</P>
<P align=justify>{</P>
<P align=justify>switch( ul_reason_for_call ) {</P>
<P align=justify>case DLL_PROCESS_ATTACH:</P>
<P align=justify>...</P>
<P align=justify>case DLL_THREAD_ATTACH:</P>
<P align=justify>...</P>
<P align=justify>case DLL_THREAD_DETACH:</P>
<P align=justify>...</P>
<P align=justify>case DLL_PROCESS_DETACH:</P>
<P align=justify>...</P>
<P align=justify>}</P>
<P align=justify>return TRUE;</P>
<P align=justify>}</P>
<P align=justify>其中:</P>
<P align=justify>参数1是模块句柄;</P>
<P
align=justify>参数2是指调用DllMain的类别,四种取值:新的进程要访问DLL;新的线程要访问DLL;一个进程不再使用DLL(Detach
from DLL);一个线程不再使用DLL(Detach from DLL)。</P>
<P align=justify>参数3保留。</P>
<P align=justify>如果程序员不指定DllMain,则编译器使用它自己的DllMain,该函数仅仅返回TRUE。</P>
<P
align=justify>规则DLL应用程序使用了MFC的DllMain,它将调用DLL程序的应用程序对象(从CWinApp派生)的InitInstance函数和ExitInstance函数。</P>
<P align=justify>扩展DLL必须实现自己的DllMain。</P>
<P align=justify></P>
<LI>_DllMainCRTStartup
<P></P>
<P align=justify>为了使用“C”运行库(CRT,C Run time
Library)的DLL版本(多线程),一个DLL应用程序必须指定_DllMainCRTStartup为入口函数,DLL的初始化函数必须是DllMain。</P>
<P align=justify>_DllMainCRTStartup完成以下任务:当进程或线程捆绑(Attach)到DLL时为“C”运行时的数据(C
Runtime Data)分配空间和初始化并且构造全局“C++”对象,当进程或者线程终止使用DLL(Detach)时,清理C Runtime
Data并且销毁全局“C++”对象。它还调用DllMain和RawDllMain函数。</P>
<P align=justify>RawDllMain在DLL应用程序动态链接到MFC
DLL时被需要,但它是静态的链接到DLL应用程序的。在讲述状态管理时解释其原因。</P>
<P align=justify></P>
<LI>DLL的函数和数据
<P></P>
<P align=justify>DLL的函数分为两类:输出函数和内部函数。输出函数可以被其他模块调用,内部函数在定义它们的DLL程序内部使用。</P>
<P align=justify>虽然DLL可以输出数据,但一般的DLL程序的数据仅供内部使用。</P>
<P align=justify></P>
<P align=justify></P>
<LI>DLL程序和调用其输出函数的程序的关系
<P></P></LI></OL>
<P align=justify>DLL模块被映射到调用它的进程的虚拟地址空间。</P>
<P align=justify>DLL使用的内存从调用进程的虚拟地址空间分配,只能被该进程的线程所访问。</P>
<P align=justify>DLL的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。</P>
<P align=justify>DLL使用调用进程的栈。</P>
<P
align=justify>DLL定义的全局变量可以被调用进程访问;DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量,则需要使用同步机制;对一个DLL的变量,如果希望每个使用DLL的线程都有自己的值,则应该使用线程局部存储(TLS,Thread
Local Strorage)。</P>
<OL>
<OL>
<P align=justify>
<LI><A name=_Toc445889075></A><A name=_Toc445782478></A><A
name=_Toc452640939></A><A name=_Toc457299037></A><B>输出函数的方法</B>
<P></P></LI></OL></OL>
<OL>
<P align=justify>
<LI>传统的方法
<P></P>
<P align=justify>在模块定义文件的EXPORT部分指定要输入的函数或者变量。语法格式如下:</P>
<P align=justify>entryname[=internalname] [@ordinal[NONAME]] [DATA]
[PRIVATE]</P>
<P align=justify>其中:</P>
<P align=justify>entryname是输出的函数或者数据被引用的名称;</P>
<P align=justify>internalname同entryname;</P>
<P align=justify>@ordinal表示在输出表中的顺序号(index);</P>
<P align=justify>NONAME仅仅在按顺序号输出时被使用(不使用entryname);</P>
<P align=justify>DATA表示输出的是数据项,使用DLL输出数据的程序必须声明该数据项为_declspec(dllimport)。</P>
<P align=justify>上述各项中,只有entryname项是必须的,其他可以省略。</P>
<P
align=justify>对于“C”函数来说,entryname可以等同于函数名;但是对“C++”函数(成员函数、非成员函数)来说,entryname是修饰名。可以从.map映像文件中得到要输出函数的修饰名,或者使用DUMPBIN
/SYMBOLS得到,然后把它们写在.def文件的输出模块。DUMPBIN是VC提供的一个工具。</P>
<P align=justify>如果要输出一个“C++”类,则把要输出的数据和成员的修饰名都写入.def模块定义文件。</P>
<P align=justify></P>
<LI>在命令行输出
<P></P>
<P align=justify>对链接程序LINK指定/EXPORT命令行参数,输出有关函数。</P>
<P align=justify></P>
<LI>使用MFC提供的修饰符号_declspec(dllexport)
<P></P></LI></OL>
<P
align=justify>在要输出的函数、类、数据的声明前加上_declspec(dllexport)的修饰符,表示输出。MFC提供了一些宏,就有这样的作用,如表7-2所示。</P>
<P align=center>表7-2 MFC定义的输入输出修饰符</P>
<P align=left>
<TABLE cellSpacing=1 cellPadding=7 width=406 border=1>
<TBODY>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>宏名称 </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>宏内容 </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_CLASS_IMPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_API_IMPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_DATA_IMPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_CLASS_EXPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_API_EXPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<DIR>
<P align=justify>AFX_DATA_EXPORT </P></DIR></TD>
<TD vAlign=top width="55%">
<P align=justify>__declspec(dllexport) </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<P align=justify>AFX_EXT_CLASS </P></TD>
<TD vAlign=top width="55%">
<P align=justify>#ifdef _AFXEXT</P>
<P align=justify>AFX_CLASS_EXPORT</P>
<P align=justify>#else</P>
<P align=justify>AFX_CLASS_IMPORT </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<P align=justify>AFX_EXT_API </P></TD>
<TD vAlign=top width="55%">
<P align=justify>#ifdef _AFXEXT</P>
<P align=justify>AFX_API_EXPORT</P>
<P align=justify>#else</P>
<P align=justify>AFX_API_IMPORT </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<P align=justify>AFX_EXT_DATA </P></TD>
<TD vAlign=top width="55%">
<P align=justify>#ifdef _AFXEXT</P>
<P align=justify>AFX_DATA_EXPORT</P>
<P align=justify>#else</P>
<P align=justify>AFX_DATA_IMPORT </P></TD></TR>
<TR>
<TD vAlign=top width="45%">
<P align=justify>AFX_EXT_DATADEF </P></TD>
<TD vAlign=top width="55%"> </TD></TR></TBODY></TABLE>
<P></P>
<P align=justify></P>
<P
align=justify>像AFX_EXT_CLASS这样的宏,如果用于DLL应用程序的实现中,则表示输出(因为_AFX_EXT被定义,通常是在编译器的标识参数中指定该选项/D_AFX_EXT);如果用于使用DLL的应用程序中,则表示输入(_AFX_EXT没有定义)。</P>
<P
align=justify>要输出整个的类,对类使用_declspec(_dllexpot);要输出类的成员函数,则对该函数使用_declspec(_dllexport)。如:</P>
<P align=justify>class AFX_EXT_CLASS CTextDoc : public CDocument</P>
<P align=justify>{</P>
<P align=justify>…</P>
<P align=justify>}</P>
<P align=justify></P>
<P align=justify>extern "C" AFX_EXT_API void WINAPI InitMYDLL();</P>
<P align=justify></P>
<P align=justify>这几种方法中,最好采用第三种,方便好用;其次是第一种,如果按顺序号输出,调用效率会高些;最次是第二种。</P>
<P align=justify>在“C++”下定义“C”函数,需要加extern “C”关键词。输出的“C”函数可以从“C”代码里调用。</P>
<HR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0>
<TBODY>
<TR>
<TD align=middle><A href="http://www.vczx.com/tutorial/mfc/mfc6.php"
target=_self>上一章</A> <A href="http://www.vczx.com/tutorial/mfc/mfc.php"
target=_self>回目录</A> <A href="http://www.vczx.com/tutorial/mfc/mfc8.php"
target=_self>下一章</A></TD></TR></TBODY></TABLE>
<P> </P>
<P align=justify></P></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -