如何在dll中添加资源

长平狐 发布于 2012/10/08 15:16
阅读 850
收藏 0

 

文章转载自http://blog.csdn.net/na_he/article/details/2451822

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

1.Wi n32 DLL

在Win32 DLL中使 用对话框很简单,你只需要在你的DLL中添加对话框资源,而且可 以在对话框上面设置你所需要的控件。然后使用DialogBox 或者CreateDialog这两个函数(或相同作用的其它函数 )来创建对话框,并定义你自己的对话框回调函数处理对话框收到的 消息。下面通过一个具体实例来学习如何在Win32 DLL中使 用对话框,可以按照以下步骤来完成这个例子:

1 )在VC菜单中File->
New新建一个命名为UseD lg的Win32 Dynamic-Link Library工 程,下一步选择A simple DLL project。 < br>
2)在VC菜单中Insert->
Reso urce添加一个ID为IDD_DLG_SHOW的Dialog 资源,将此Dialog上的Cancel按钮去掉,仅保留OK按 钮。再添加一个ID为IDD_ABOUTBOX的对话框,其Ca ption为About。保存此资源,将资源文件命名为UseD lg.rc。并将resource.h和UseDlg.rc加入 到工程里面。

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

H INSTANCE hinst = NULL;

HWN D hwndDLG = NULL;


BOOL CALLBACK DlgProc(HWND hDlg, U INT message,
WPARAM wParam , LPARAM lParam);


BOOL CALLBACK AboutProc(HWND hDlg, UINT message,
WPARAM wPar am, LPARAM lParam);


ex tern "
C"
__declspec( dllexport) void ShowDlg();


BOOL APIENTRY DllMain( HA NDLE hModule,
DWORD ul_reason_for_call,
 LPVOID lpReserved
 )
{
switch(ul_reaso n_for_call)
{
case DLL_PROCESS_ATTACH:
 h inst = (HINSTANCE)hModule;

case DLL_PROCESS_DETACH:
 break;

}
retu rn TRUE;

}

extern "
C"
__declspec(dllex port) void ShowDlg()
{
hwndDLG = CreateDialog(hins t,MAKEINTRESOURCE(IDD_DLG_SHOW ),
NULL,(DLGPROC)DlgProc);

ShowWindow(hwndDLG, SW_ SHOW);

}

BOOL CALL BACK DlgProc(HWND hDlg, UINT m essage,
WPARAM wParam, LPA RAM lParam)
{
switch( message)
{
case WM_ INITDIALOG:
 return TRU E;

case WM_COMMAND:
 if(LOWORD(wParam)==IDOK) < br>DialogBox(hinst,MAKEIN TRESOURCE(IDD_ABOUTBOX),
h Dlg,(DLGPROC)AboutProc);

  return TRUE;

case WM_ CLOSE:
 DestroyWindow(h Dlg);

 hwndDLG = NULL;

 return TRUE;

}
return FALSE;

}


BOOL CALLBACK AboutProc(H WND hDlg, UINT message,
WP ARAM wParam, LPARAM lParam)
{
switch(message)
{
 case WM_CLOSE:
 EndDialog(hDlg,NULL);
 hwndDLG = NULL;

  return TRUE;

}
r eturn FALSE;

}

4)编 译生成UseDlg.dll和UseDlg.lib。
< br>接下来我们建立调用此DLL的应用程序,其步骤如下:

1)在VC菜单中File->
New新建一个 命名为Use的MFC AppWizard(exe)工程,下一 步选择Dialog Based之后点击Finish按钮。

2)在主对话框上面添加一个按钮,之后双击此按钮, 会弹出Add Member Function的对话框,直接点 击OK进入void CUseDlg::OnButton1() 函数。并在此函数内添加一个函数调用:ShowDlg();


3)紧跟在#include语句后面加上如下代 码:

extern "
C"
__declspec(dllexport) void Sh owDlg();

#pragma comment(l ib,"
debug/UseDlg"
) < br>
4)将上面UseDlg工程中生成的UseDlg .dll和UseDlg.lib两个文件复制到Use工程的De bug目录内。

5)编译生成Use.exe。

运行Use.exe,点击Button1 按钮,可以看到一个名称为Dialog的非模态对话框弹出。点击 上面的按钮,可以弹出模态对话框About。运行成功。

让我们来回顾一下在Win32 DLL中使用对话 框的过程。

在DLL中,我们定义了两个对话框资 源:IDD_DLG_SHOW和IDD_ABOUTBOX,并且 导出了函数ShowDlg。在函数ShowDlg之中使用Cre ateDialog函数创建了非模态对话框IDD_DLG_SH OW,并指定了该对话框的回调函数DlgProc。在DlgPr oc之中处理了WM_INITDIALOG、WM_COMMAN D和WM_CLOSE消息,以响应用户对对话框所做的动作。在处 理按钮动作的时候,使用DialogBox函数创建IDD_AB OUTBOX这个模态对话框,指定其回调函数为AboutPro c,并且在AboutProc中处理其相应消息。

在EXE中,我们使用隐式链接的方法来调用DLL,并使用DL L中导出的ShowDlg函数来调用DLL中的对话框。

在Win32 DLL中使用对话框就是这么简单,下 面让我们来看一下在MFC DLL中如何使用对话框。

2.MFC DLL
在MFC DLL中使 用对话框不像Win32 DLL中那么简单,主要是因为MFC程 序中存在一个模块状态(Module State)的问题,也就 是资源重复的问题。(此处的术语模块是指一个可执行程序,或指其 操作不依赖于应用程序的其余部分但使用MFC运行库的共享副本的 一个DLL(或一组DLL)。我们所创建的MFC DLL就是这 种模块的一个典型实例。)

在每个模块(E XE或DLL)中,都存在一种全局的状态数据,MFC依靠这种全 局的状态数据来区分不同的模块,以执行正确的操作。这种数据包括 :Windows实例句柄(用于加载资源),指向应用程序当前的 CWinApp和CWinThread对象的指针,OLE模块引 用计数,以及维护Windows对象句柄与相应的MFC对象实例 之间连接的各种映射等。但当应用程序使用多个模块时,每个模块的 状态数据不是应用程序范围的。相反,每个模块具有自已的MFC状 态数据的私有副本。这种全局的状态数据就叫做MFC模块状态。

模块的状态数据包含在结构中,并且总是可以通过 指向该结构的指针使用。当代码在执行时进入了某一个模块时,只有 此模块的状态为“当前”或“有效”状态时,MFC才能正确的区分 此模块并执行正确的操作。

例如,MFC应用程 序可以使用下面代码从资源文件中加载字符串:

CString str;

str.LoadStrin g(IDS_MYSTRING);


使用这种代 码非常方便,但它掩盖了这样一个事实:即此程序中IDS_MYS TRING可能不是唯一的标识符。一个程序可以加载多个DLL, 某些DLL可能也用IDS_MYSTRING标识符定义了一个资 源。MFC怎样知道应该加载哪个资源呢?MFC使用当前模块状态 查找资源句柄。如果当前模块不是我们要使用的正确模块,那么就会 产生不正确的调用或者错误。

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

1、静态链接到MF C的DLL

静态链接到MFC的规则DLL与M FC库静态链接,则此时MFC库不能共享,所以MFC总是使用它 所链接的DLL的模块状态。这样也就不存在管理模块状态的问题。 但使用这种方法的缺点是DLL程序将会变大,而且会在程序中留下 重复代码。下面给出的例子验证了这一点。本例可以按照以下步骤来 完成:

1)在VC菜单中File->
N ew新建一个命名为DLLStatic的MFC AppWiza rd的工程,下一步选择Regular DLL with MF C statically linked。

2) 在工程中添加一个对话框资源,其ID为:IDD_ABOUTBO X。并在resource.h之中将IDD_ABOUTBOX 的数值改为100。

3)在DLLStatic .cpp中定义如下函数:

void Show Dlg()
{
CDialog dlg( IDD_ABOUTBOX);

dlg.DoMod al();

}

4)在DLLStat ic.def文件中的EXPORTS语句中添加一行:ShowD lg,以导出ShowDlg函数。

5)编译生成 DLLStatic.dll和DLLStatic.lib。 < br>
继续使用上一节中的Use工程,将前面生成的DL LStatic.dll和DLLStatic.lib两个文件复 制到工程的Debug目录内,并将

exter n "
C"
__declspec(dll export) void ShowDlg();

#p ragma comment(lib,"
debug/ UseDlg"
)

这两行改为: < br>
void ShowDlg();

#pr agma comment(lib,"
debug/D LLStatic"
)

编译并运行U se.exe。点击按钮,可以看到DLLStatic中的模态对 话框弹出。

本例中,可以注意到DLL中所定义的 About对话框资源与EXE中所定义的About对话框资源I D完全相同,但是当我们点击Use.exe上面的按钮时,弹出的 是DLL中的模态对话框。说明,当使用静态链接到MFC的规则D LL时,不存在管理模块状态的问题。

2、动态 链接到MFC的DLL

在讨论关于动态链接 到MFC的DLL的模块状态问题之前,先来看一个例子。本例可以 通过如下步骤来完成:

1)在VC菜单中Fil e->
New新建一个命名为DLLShared的MFC AppWizard的工程,下一步选择Regular DLL using shared MFC DLL。

2 )在工程中添加一个对话框资源,其ID为:IDD_ABOUTB OX。并在resource.h之中将IDD_ABOUTBOX 的数值改为100。

3)在DLLShared .cpp中定义如下函数:

void ShowD lg()
{
CDialog dlg(I DD_ABOUTBOX);

dlg.DoModa l();

}

4)在DLLShare d.def文件中的EXPORTS语句中添加一行:ShowDl g,以导出ShowDlg函数。

5)编译生成D LLShared.dll和DLLShared.lib。

继续使用上面的Use工程,将前面生成的DLLSh ared.dll和DLLShared.lib两个文件复制到工 程的Debug目录内,并将

extern & quot;
C"
__declspec(dllexp ort) void ShowDlg();

#prag ma comment(lib,"
debug/DLL Static"
)

这两行改为: < br>
void ShowDlg();

#pr agma comment(lib,"
debug/D LLShared"
)

编译并运行U se.exe。点击按钮,这次你看到了什么?对,没错,这次弹出 的是Use.exe的关于对话框。将上述例子的DLL类型换成M FC Extension DLL(using shared MFC DLL)也会出现相同的问题。

为什么会 出现上面的问题?这是因为在使用了MFC共享库的时候,默认情况 下,MFC使用主应用程序的资源句柄来加载资源模板。虽然我们调 用的是DLL中的函数来显示DLL中的对话框,并且对应的对话框 模板是存储在DLL中的,但MFC仍旧在主应用程序也就是Use .exe中寻找相应的对话框模板。由于在DLL中所定义的对话框 资源ID与主应用程序中所定义的关于对话框的资源ID相同,所以 MFC就把主应用程序中的关于对话框显示了出来。如果二者不同, 则MFC就认为DLL中所定义的对话框资源不存在,dlg.Do Modal会返回0,也就是什么都不会显示。

那 么如何解决上述问题呢?解决办法就是在适当的时候进行模块状态切 换,以保证具有当前状态的模块是我们所需要的模块从而使用正确的 资源。MFC提供了下列函数和宏来完成这些工作:
AfxGetStaticModuleState:这是一个 函数,其函数原型为:

AFX_MODULE_S TATE* AFXAPI AfxGetStaticModul eState( );


此函数在堆栈上构造AF X_MODULE_STATE类的实例pModuleState 并对其赋值后将其返回。在AFX_MODULE_STATE类的 构造函数中,该类获取指向当前模块状态的指针并将其存储在成员变 量中,然后将pModuleState设置为新的有效模块状态。 在它的析构函数中,该类将存储在其成员变量中的指针还原为存贮的 前一个模块状态。

AFX_MANAGE_STA TE:这是一个宏,其原型为:

AFX_MAN AGE_STATE( AFX_MODULE_STATE* p ModuleState )

该宏用于将pMo duleState(指向包含模块全局数据也就是模块状态的AF X_MODULE_STATE结构的指针)设置为当前的即时作用 空间中(the remainder of the immed iate containing scope)的有效模块状态。 在离开包含该宏的作用空间时,前一个有效的模块状态自动还原。

AfxGetResourceHandle:这 个函数的原型为:

HINSTANCE Af xGetResourceHandle( );


该函数返回了一个保存了HINSTANCE类型的、应用程 序默认所加载资源的模块的句柄。

AfxSetR esourceHandle:这个函数的原型为:
void AfxSetResourceHandle( H INSTANCE hInstResource );


该函数将hInstResource所代表的模块设置为 具有当前状态的模块。

通过使用上述四个函数或宏 就可以正确的在动态链接到MFC的DLL中切换模块状态。接下来 我们将通过修改上面出现问题的那个例子来介绍如何使用上述四个函 数或宏。先来看看Regular DLL using shar ed MFC DLL类型:

在上述例子的第三步 的ShowDlg函数的第一条语句前加上如下语句(要确保该语句 在函数实现的第一行):

AFX_MANAGE _STATE(AfxGetStaticModuleState ());


之后重新编译生成DLLShared .dll和DLLShared.lib,并将这两个文件重新拷贝 到Use工程的Debug目录内。这次编译生成Use.exe并 运行,点击按钮,可以看到弹出的时我们在DLL中所加入的那个对 话框,而不再是Use.exe的关于对话框了。

通过上面的讲解,相信你已经知道该语句的作用了。在函数Sho wDlg的第一行加上这么一句后,每次调用DLL的应用程序使用 该函数的时候,MFC库都会自动切换当前模块状态,这样就保证了 资源读取的正确性。

AFX_MANAGE_S TATE(AfxGetStaticModuleState() );
是自动切换当前模块状态,也可以通过使用AfxGetRes ourceHandle和AfxSetResourceHand le来手动切换当前模块状态。具体使用方法如下:
在上述例子的第三步的ShowDlg函数的第一条语句前加上 如下语句(要确保该语句在函数实现的第一行):

HINSTANCE save_hInstance = Af xGetResourceHandle();

Af xSetResourceHandle(theApp.m_hI nstance);


在调用对话框成功之后,也 就是dlg.DoModal();
之后,添加:
AfxS etResourceHandle(save_hInstanc e);


这种方法在进入ShowDlg函数 之后,通过AfxGetResourceHandle来获得并保 存当前状态模块的句柄。然后获得DLL模块的句柄theApp. m_hInstance(当然,也可以使用GetModuleH andle函数来获得DLL模块的句柄),并使用AfxSetR esourceHandle函数来将其设置为当前状态状态。最后 在调用对话框成功之后再用恢复AfxSetResourceHa ndle资源句柄,将当前模块状态恢复。

 这样做有些麻烦,但是有一点好处是可以在完成使用资源的任务之后 就可以立即恢复资源句柄。而AFX_MANAGE_STATE( AfxGetStaticModuleState());
的方法 只能等函数的作用空间结束之后才恢复资源句柄。由于可执行文件必 须重画工具条等原因,因此建议只要有可能就必须恢复资源句柄,否 则可能会遇到许多问题。比如说,如果用户移动DLL的对话框,而 此时资源句柄仍然为DLL的资源,那么程序就会崩溃。最好的恢复 句柄的时机在对话框响应WM_INITDIALOG消息的时候, 因为这时对话框的模板等已经读出了。

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

在MFC扩展D LL中使用AFX_MANAGE_STATE(AfxGetSt aticModuleState());
时,会产生如下错误:

mfcs42d.lib(dllmodul.o bj) : error LNK2005: __pRawDll Main already defined in dllext end.obj
mfcs42d.lib(dllmod ul.obj) : error LNK2005: _DllM ain@12 already defined in dlle xtend.obj
mfcs42d.lib(dllm odul.obj) : error LNK2005: __p RawDllMain already defined in dllextend.obj

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

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

当然,对于动态链接到MFC的DLL,也可以在调用该D LL的MFC应用程序中使用AfxGetResourceHan dle和AfxSetResourceHandle两个函数来切 换当前状态模块。该DLL模块的句柄可以用GetModuleH andle函数来获得。在此不再赘述。


原文链接:http://blog.csdn.net/ithzhang/article/details/7995155
加载中
返回顶部
顶部