2024年3月6日发(作者:)

创建显示对话框的DLL

现在最常看见的关于DLL的问题就是如何在DLL中使用对话框,这是一个很普遍的关于如何在DLL中使用资源的问题。这里我们从Win32 DLL和MFC DLL两个方面来分析并解决这个问题。

一.Win32 DLL

在Win32 DLL中使用对话框很简单,你只需要在你的DLL中添加对话框资源,而且可以在对话框上面设置你所需要的控件。然后使用DialogBox或者 CreateDialog这两个函数(或相同作用的其它函数)来创建对话框,并定义你自己的对话框回调函数处理对话框收到的消息。

1)在VC菜单中File->New新建一个命名为UseDlg的Win32 Dynamic-Link Library工程,下一步选择A simple DLL project。

2)在VC菜单中Insert->Resource添加一个ID为IDD_DLG_SHOW的Dialog资源,将此Dialog上的Cancel按钮去掉,仅保留OK按钮。再添加一个ID为IDD_ABOUTBOX的对话框,其Caption为About。保存此资源,将资源文件命名为 。并将resource.h和加入到工程里面。

3)在中包含resource.h,并添加如下代码:

HINSTANCE hinst = NULL;

HWND hwndDLG = NULL;

BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM

lParam);

BOOL CALLBACK AboutProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM

lParam);

extern "C" __declspec(dllexport) void ShowDlg();

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID

lpReserved )

{

switch(ul_reason_for_call)

{

case DLL_PROCESS_ATTACH:

hinst = (HINSTANCE)hModule;

case DLL_PROCESS_DETACH:

break;

}

return TRUE;

}

extern "C" __declspec(dllexport) void ShowDlg()

{

hwndDLG = CreateDialog(hinst,MAKEINTRESOURCE(IDD_DLG_SHOW),

NULL,(DLGPROC)DlgProc);

ShowWindow(hwndDLG, SW_SHOW);

}

BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM

lParam)

{

switch(message)

{

case WM_INITDIALOG:

return TRUE;

case WM_COMMAND:

if(LOWORD(wParam)==IDOK)

DialogBox(hinst,MAKEINTRESOURCE(IDD_ABOUTBOX),

hDlg,(DLGPROC)AboutProc);

return TRUE;

case WM_CLOSE:

DestroyWindow(hDlg);

hwndDLG = NULL;

return TRUE;

}

return FALSE;

}

BOOL CALLBACK AboutProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM

lParam)

{

switch(message)

{

case WM_CLOSE:

EndDialog(hDlg,NULL);

hwndDLG = NULL;

return TRUE;

}

return FALSE;

}

4)编译生成和。

二.MFC DLL

在MFC应用程序框架中,MFC定义了多种状态信息,例如模块状态、进程状态、线程状态等,这些状态信息是MFC程序特有的,普通Win32程序并没有这引些状态的定义。

在Windows中模块的含义:一个EXE文件或者一个DLL。但只有MFC程序才有模块状态、进程状态、线程状态这些概念,因为这些数据结构是在MFC中定义的,而不操作系统定义的!

MFC定义的“模块状态”包括这样一些信息:当前模块的路径、用来加载资源的Windows实例句柄、指向当前CWinApp或者CWinThread对象的指针、OLE模块的引用计数、Windows对象与相应的MFC对象之间的映射。

MFC通过也为每个线程定义了“线程状态”,像“模块状态”一样是通过TLS技术实现的,在此不再详述。但我们要知道“线程状态”里定义了指向有效模块状态的指针,每当进入某个模块时应该使它指向有效模块状态,这对维护应用程序全局状态和每个模块状态的完整性来说是非常重要的。

在我们调用加载一个DLL后,如果要使用该DLL中的资源,必须把当前线程的“线程状态”中的“有效模块指针”指向此DLL的模块状态。

按照MFC库的链接方法,一个MFC DLL有两种使用MFC库的方法:“静态链接到MFC库的DLL”和“动态链接到MFC库的DLL”。下面我们就按照这两种类型的MFC DLL来介绍如何切换当前模块状态以正确的在MFC DLL中使用资源。

1、“静态链接到MFC库的”DLL

如果采用“静态链接MFC库”的方式,则会在“链接”时被放到这个模块中,便不存在“MFC库共享”的问题,所以MFC库会默认使用它所在模块()的资源。这样DLL中的资源便会被正确使用,从而显示出所要对话框。但使用这种方法的缺点是DLL程序将会变大,而且会在程序中留下重复代码。

2、“动态链接到MFC库的”DLL

在使用“共享MFC库”的方式创建DLL时,因为MFC库和是独立的两个模块,MFC库还可能会被任意的其它模块调用,所以它无法得知该使用哪个模块中的对话框资源。幸好MFC为每个线程对象定义的“线程状态”有一个指向“有效模块状态”的指针,MFC库便可以利用这个指针找到正确的“模块状态”,进而打到正确的模块。

但主线程“线程状态”中的“有效模块状态指针”最初是指向中“EXE”的模块状态,在我们要使用DLL中的对话框资源时必须更改此指针,使其指向DLL的模块状态;在我们不使用DLL中的对话框资源时,还要把它值给改回去,使其指向"EXE"模块状态,这样它才能正确使用"EXE"中的资源。

那么如何更改线程的“有效模块状态”指针呢?我们可以通过如下两种方式切换模块状态:

方法一:在每个导出函数的第一语句前加上宏:AFX_MANAGE_STATE(AfxGetStaticModuleState())

AfxGetStaticModuleState是一个MFC的全局函数,访函数的功能是返回该函数所在模块的“模块状态”,注意“模块状态”是在该模块导入时由MFC代码创建的,生存期直到该模块被卸载。

AFX_MANAGE_STATE是一个宏,其定义为:#define AFX_MANAGE_STATE(p)

AFX_MAINTAIN_SATATE2 _ctlState(p); 含义为创建一个AFX_MAINTAIN_SATATE2类的对象_ctlState, p是AFX_MAINTAIN_SATATE2类构造函数的参数。在AFX_MAINTAIN_SATATE2的构造函数中把当前线程的“线程状态”中的“有效模块指针”指向p, 在AFX_MAINTAIN_SATATE2的析构函数中把当前线程的“线程状态”中的“有效模块指针”指向原来的“模块状态”,因为_ctlState是在调用函数在栈中创建的,因此当该函数调用结束后,_ctlState生命同期也结束了,会执行析构函数。这是一具很巧妙的使用!

方法二:在每个导出函数的第一语句前加上代码:

HINSTANCE save_hInstance = AfxGetResourceHandle();

AfxSetResourceHandle(theApp.m_hInstance);

在每个导出函数的最后加上代码:

AfxSetResourceHandle(save_hInstance);

方法二有点麻烦,但是有一点好处是可以在完成使用资源的任务之后就可以立即恢复资源句柄。而AFX_MANAGE_STATE(AfxGetStaticModuleState());的方法只能等函数的作用空间结束之后才恢复资源句柄。由于可执行文件必须重画工具条等原因,因此建议只要有可能就必须恢复资源句柄,否则可能会遇到许多问题

方法三:由应用程序自身切换

//获取EXE模块句柄

HINSTANCE exe_hInstance = GetModuleHandle(NULL);

//或者HINSTANCE exe_hInstance = AfxGetResourceHandle();

//获取DLL模块句柄

HINSTANCE dll_hInstance = GetModuleHandle("");

AfxSetResourceHandle(dll_hInstance); //切换状态

ShowDlg(); //此时显示的是DLL的对话框

AfxSetResourceHandle(exe_hInstance); //恢复状态

方法三中的Win32函数GetModuleHandle可以根据DLL的文件名获取DLL的模块句柄。如果需要得到EXE模块的句柄,则应调用带有Null参数的GetModuleHandle。方法三与方法二的不同在于方法三是在应用程序中利用AfxGetResourceHandle和AfxSetResourceHandle进行资源模块句柄切换的。

对于MFC Extension DLL(using shared MFC DLL)类型的MFC DLL,切换当前模块状态的方法与Regular DLL using shared MFC DLL类型的MFC DLL所使用的方法很相似,这里不再举例实现。二者不同的地方如下:

在MFC扩展DLL中使用AFX_MANAGE_STATE(AfxGetStaticModuleState());时,会产生如下错误:

() : error LNK2005: __pRawDllMain already defined in

() : error LNK2005: _DllMain@12 already defined in

() : error LNK2005: __pRawDllMain already defined in

因此在MFC扩展DLL中需要将AFX_MANAGE_STATE(AfxGetStaticModuleState());换成AFX_MANAGE_STATE(AfxGetAppModuleState());才能正确切换当前模块状态。

在MFC 扩展DLL中使用AfxGetResourceHandle和AfxSetResourceHandle的方法与在Regular DLL using shared MFC DLL类型的MFC DLL中所使用的方法相同。并且,DLL模块的句柄可以通过MFC提供的DlgextentDLL这个结构的hModule成员来获得。即使用

AfxSetResourceHandle(e);语句。

注意:

1、Windows API要创建对话框,就必须找到正确的对话框资源,这是本文的核心。

2、显示对话框的DLL有时会出现bug: 在Debug状态下显示后就死在那里!

这时只有在Release状态下进行调试。