- e: g4 M( ]$ e9 b+ F
, b J* l! N3 r) @: [6 p7 ]* h. l: w$ K- Z8 M" ]- L
在NX OPEN中使用MFC对话框的方法和原理.pdf
(152.88 KB, 下载次数: 122)
0 ]% V) g: f& t# Q! d4 Q$ G" p3 A. X8 P0 t. F
$ k7 s; ^7 _' @" K
1. 概述NX OPEN的入口函数一般为ufusr 或者ufsta等等,如下:(灰色第的字为VC++的代码以示区别,下同) Extern DllExport void ufusr( char *parm, int *returnCode, int rlen ) { /* Initialize the API environment */ ugSession session( true ); try { //函数主体 } /* Handle errors */ catch ( const UGException &exception ) { processException( exception ); } } 而程序的入口函数为Main或者WinMain,所以NX OPEN的二次开发的程序就好像NX主程序模块调用了名字为ufusr 或者ufsta的DLL动态连接库模块模块,关于模块的定义见下面有关内容,调用方式是__declspec(dllexport)。所以我们想调用带 MFC对话框的NX二次开发程序就好像NX主程序模块调用MFC规则DLL。 2. DLL的分类MFC 动态连接库DLL分为两类: (1)静态链接到MFC 的规则DLL 静态链接到MFC的规则DLL与MFC库(包括MFC扩展 DLL)静态链接,将MFC库的代码直接生成在.dll文件中。在调用这 种DLL的接口时,MFC使用DLL的资源。因此,在静态链接到MFC 的规则DLL中不需要进行模块状态的切换。使用这种方法生成的规则DLL其程序较大,也可能包含重复的代码。 (2)动态链接到MFC 的规则DLL 动态链接到MFC 的规则DLL 可以和使用它的可执行文件同时动态链接到 MFC DLL 和任何MFC扩展 DLL。在使用了MFC 共享库的时候,默认情况下,MFC使用主应用程序的资源句柄来加载资源模板。这样,当DLL和应用程序中存在相同ID的资源时(即所谓的资源重复问题 ),系统可能不能获得正确的资源。因此,对于共享MFC DLL的规则DLL,我们必须进行模块切换以使得MFC能够找到正确的资源模板。 我们可以在Visual C++中设置MFC规则DLL是静态链接到MFC DLL还是动态链接到MFC DLL。如图1,依次选择Visual C++的project -> Settings -> General菜单或选项,在Microsoft Foundation Classes中进行设置。
" t2 U. ~* u( D/ @. R/ K% j
) O3 y7 t0 y; u7 A. y6 m5 `5 g$ A
3. 共享MFC DLL的规则DLL的模块切换3.1 模块的定义一段可执行的程序,其程序代码,数据,资源被加载到内存中了,这种加载可以是操作系统来做,也可以是已被加载到内存中的模块来做。这段程序进入内存后,系统会为之建立一个数据结构来管理,这个数据结构在WINDOWS中,就是PE文件头。活动在内存中的EXE文件和活动在内存中的DLL文件,它们都是模块。 3.2 MFC模块:使用了MFC库的模块就叫MFC模块。MFC模块的状态也是一个数据结构,它里面储存了许多与模块相关的数据,DLL中所包含的对话框是存于资源模板中的,而资源模板是存于这个MFC模块的状态数据结构中的。 任何应用程序进程本身及其调用的每个DLL模块都具有一个全局唯一的HINSTANCE句柄,它们代表了DLL在进程 虚拟空间中的起始地址。进程本身的模块句柄一般为0x400000,而DLL模块的缺省句柄为0x10000000。如果程序同时加载了多个DLL,则每个DLL模块 都会有不同的HINSTANCE。应用程序在加载DLL时对其进行了重定位。 3.3 模块切换的原因调用共享MFC 规则DLL(或MFC扩展DLL)就涉及到HINSTANCE句柄问题,HINSTANCE句柄对于加载资源特别重要。 每个MFC DLL都有其自己的资源, 资源的ID在resource.h文件中会有定义,如下的宏: //DLL中对话框的ID #define IDD_DLL_DIALOG 2000 而这些资源的ID可能会有重复,所以应用程序需要通过资源模块的切换来找到正确的资源。如果应用程序需要来自于模块资源,就应将资源模块句柄指定为DLL的模块句柄, 一个MFC程序运行时,默认状态下,当它需要资源时,如它需要一个对话框,那么它会从自身的模块状态中找到资源模板,然后从资源模板中找到相应的对话框。然而,当一个MFC的应用程序在运行时,可能要加载多个MFC模块。譬如程序NX.exe在运行时,加载了UgOpen.dll。在UgOpen.dll中提供了一个输出函数ShowDlg,函数体定义如下: void ShowDlg(void) { CDlg *pDlg = new CDlg; PDlg->Create(IDD_DLL_DIALOG,NULL); pDlg->ShowWindow(SW_SHOW); } 我们可以看到,UgOpen.dll中肯定是存在一个ID为IDD_DLL_DIALOG的对话框模板,按照上面所说的,这个对话框模板被存到了UgOpen.dll的状态模块中了。当NX.exe中调用这个输出函数时,运行到pDlg->Create(IDD_DLL_DIALOG,NULL)时,它就会去自己的模块状态中去查找标号为IDD_DLL_DIALOG的对话框模板,结果有可能找得到,也有可能找不到。假如NX.exe的模块状态中有一个对话框模板的ID恰好等于2000时,就能找到,但弹出的对话框并不是你在UgOpen.dll中定义的那个。当NX.exe的模块状态中没有ID恰好等于IDD_DLL_DIALOG的对话框时,那就是找不到,程序会报错。 NX OPEN不能使用MFC对话框的根源在于:NX主程序模块与调用带MFC对话框的NX OPEN DLL共享MFC DLL,而程序程序总是默认使用NX主程序模块的资源,所以我们必须进行资源模块句柄的切换,其实现方法有三: 方法一 在DLL接口函数中使用:AFX_MANAGE_STATE(AfxGetStaticModuleState());当NX程序调用UgOpen.dll时,倘若在ShowDlg的入口点处,把NX程序默认的自身模块状态切换成UgOpen.dll的模块状态,这样再查找标号为IDD_DLL_DIALOG的对话框时,就会从UgOpen.dll的模块状态中查找了,结果就能找到。一般我们在整个入口函数前加入AFX_MANAGE_STATE(AfxGetStaticModuleState())就可以了 我们将接口函数改为: Extern DllExport void ufusr( char *parm, int *returnCode, int rlen ) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); /* Initialize the API environment */ UgSession session( true ); try { //函数主体 } /* Handle errors */ catch ( const UgException &exception ) { processException( exception ); } } 只要添加了AFX_MANAGE_STATE(AfxGetStaticModuleState())我们就可以自由添加MFC的对话框了。 AfxGetStaticModuleState是一个函数,其原型为: AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( ); 该函数的功能是在栈上(这意味着其作用域是局部的)创建一个AFX_MODULE_STATE类(模块全局数据也就是模块状态)的实例,对其进行设置,并将其指针pModuleState返回。 AFX_MODULE_STATE类的原型如下: // AFX_MODULE_STATE (global data for a module) class AFX_MODULE_STATE : public CNoTrackObject { public: #ifdef _AFXDLL AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion); AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion,BOOL bSystem); #else AFX_MODULE_STATE(BOOL bDLL); #endif ~AFX_MODULE_STATE(); CWinApp* m_pCurrentWinApp; HINSTANCE m_hCurrentInstanceHandle; HINSTANCE m_hCurrentResourceHandle; LPCTSTR m_lpszCurrentAppName; … //省略后面的部分 } AFX_MODULE_STATE类利用其构造函数和析构函数进行存储模块状态现场及恢复现场的工作,类似汇编中call指令对 pc指针和sp寄存器的保存与恢复、中断服务程序的中断现场压栈与恢复以及操作系统线程调度的任务控制块保存与恢复。 //AFX_MANAGE_STATE是一个宏,其原型为: AFX_MANAGE_STATE( AFX_MODULE_STATE* pModuleState ) 该宏用于将pModuleState设置为当前的有效模块状态。当离开该宏的作用域时(也就离开了pModuleState所指向栈上对 象的作用域),先前的模块状态将由AFX_MODULE_STATE的析构函数恢复。 方法一,最为常用也最为简单,后面两种方法,只做简单介绍。。。。 ~9 i" @3 ^+ k# X" k M
: M/ X) S! ~$ Z$ P; L6 ^" B7 o: d( C3 J
/ D. v' K2 \$ t7 f
|