dll模块资源切换

大部分开发过程中我们都直接使用C或者C++开发基于WindowsSDK的Win32 Dll,但是有些场合,我们可能需要用到MFC的一些类库来简化我们的开发过程和周期,为此,我们需要开发MFC扩展dll。
使用MFC扩展Dll可能缩短我们的开发周期。MFC里面提供了很多的封装好的类库,使用起来非常方便。但是有些东西有其优点就有其弊端。这里将要谈到的就是MFC扩展dll可能遇到的一个问题:资源句柄的切换。
在程序中,我们可能包括资源,代码中我们可能要加载资源。加载资源有一系列API函数,例如LoadBitmap LoadICon等,这些函数都有一个共同点:需要知道资源所在模块的句柄。只有将资源所在的模块加载之后,获取到资源所在模块句柄,才能调用这些函数获取指定资源。例如LoadBitmap函数原型:
HBITMAP LoadBitmap(
HINSTANCE hInstance, // handle to application instance
LPCTSTR lpBitmapName // name of bitmap resource
);
LoadIcon函数原型如下:
HICON LoadIcon(
HINSTANCE hInstance,
LPCTSTR lpIconName
);
这类函数的第一个参数都是包含指定资源的模块的句柄。
MFC扩展Dll和基于MFC程序一样,大部分都被MFC封装了。例如CBitmap封装了Bitmap位图相关的操作,CMenu封装了菜单的相关操作。CBitmap提供了成员函数LoadBitmap来获取指定资源。原型如下:
BOOL LoadBitmap(
LPCTSTR lpszResourceName
);
BOOL LoadBitmap(
UINT nIDResource
);
我们看到,这些成员函数都没有资源所在模块的句柄参数了,那么调用此函数框架怎么知道从哪个模块来获取资源呢?
一般的MFC对话框程序或者SDI MDI程序中由于只有一个应用程序,获取资源,框架自动从exe模块中找。但是如果程序中存在多个模块,我们的目的不是从exe中找,而是从指定模块中找,那怎么办?经常LoadBitmap载入指定位图资源失败,但是明明这个资源我们添加到资源文件中了。用GetLastError()获取错误码得知是指定资源不存在的错误;有些时候,指定的是这个资源,结果获取的是另外一个资源。种种都让人很迷惑,下面就分析一下本质原因以及解决办法。
举个例子:写个dll,dll中有个对话框,假设对话框id为IDD_TIPS;dll中提供一个导出函数ShowDlg如下:
void WINAPI _declspec(dllexport) ShowDlg(UINT nId)
{
CDialog dlg(nId);
dlg.DoModal();
}
现在dll被一个exe程序调用,正好这个exe文件的资源中也存在这么一个id为IDD_TIPS的对话框,那么在exe中调用dll的导出函数想弹出 dll中的tips对话框时,结果就大出我们意料之外,呈现在我们面前的不是dll中的IDD_TIPS对话框,而是exe中的IDD_TIPS对话框。
下面给出一个简单Sample,dll是MFC规则dll而不是MFC扩展dll,先简单讲述一下MFC规则dll,然后转入正题。
首先我们看看MFC规则dll和MFC扩展dll的不同。
(1)MFC规则dll和MFC扩展dll都可以使用MFC封装类
(2)MFC规则dll和普通dll一样,不能以MFC类为接口,但是MFC扩展dll不同,他的接口可以是MFC类对象。

对于我们来说,使用MFC规则dll和MFC扩展dll作为sample,效果差别不大。
首先我们建立一个MFC规则dll,注意,框架生成的MFC规则dll代码和MFC exe程序差不多,一个由WinApp派生的类,并且为此类定义了一个全局的对象。另外,在程序中有Begin_message_map | End_message_map对。这些我们不用关注。在资源编辑器中我们为MFC规则dll添加一个对话框,对话框ID设置为IDD_TIPS;然后在主程序文件文件中(也就是定义了全局CWinApp派生类对象的文件)添加一个导出函数:
extern "C"
void __declspec(dllexport) ShowDlg(UINT nID)
{
CDialog dlg(nID);
dlg.DoModal();s
}

创建一个测试程序,这里我们简单起见就直接创建一个基于dialog的程序。为对话框添加一个对话框,对话框ID也设置为IDD_TIPS,和前面dll中的对话框id相同。另外,在dialog中添加一个按钮,为按钮添加点击处理函数,当用户点击的时候我们想让其调用dll中的导出函数ShowDlg,然后弹出dll中的Tips对话框,按钮点击处理函数如下:
void CVC6PrjDlg::OnShowDlg()
{
ShowDlg(IDD_TIPS);
}
当然,我们需要包含前面dll的导入库文件,并且加入导入函数ShowDlg函数的声明
#pragma comment(lib, ".\MFCExtDll\Debug\MFCExtDll.lib")
extern "C"
void __declspec(dllimport)
ShowDlg(UINT nID);
编译运行,点击exe的按钮,弹出一个对话框。仔细看下,发现此对话框并不是dll中的对话框,而是exe中的对话框,怎么回事呢?我们调用的是dll中的导出函数,传入的id是dll中的对应对话框的id,怎么把exe中的对应此id的对话框给弹出来了?
这里涉及到资源句柄的切换。
前面说过,任何一个资源都有该资源模块的加载,根据资源模块的句柄找到对应的资源。模块的句柄是模块加载到进程地址空间的起始地址。MFC框架帮我们做了很多封装,对我们来说,调用本exe的资源加载函数,都没有携带模块参数。那么MFC怎么知道在哪个模块中查找资源呢。特别上面的例子:dll中包含一个 IDD_TIPS的对话框资源,同时,exe文件中也包含一个IDD-TIPS的对话框资源,此时框架会总是从exe的资源模块中查找资源。所以导致上面出现的问题。
同样的此类问题还可能发生在:在基于MFC支持的ATL项目中,使用LoadBitmap等查找资源函数加载资源总是失败,不是对应的资源不存在,而是查找的模块不对。在ATL中调用CBitmap的LoadBitmap函数,没有指定资源句柄,框架使用的资源模块总是指的exe本身的资源模块。而我们知道,atl写的activex控件大部分嵌入在ie里头,这样的话,LoadBitmap相当于在ie的资源模块中查找资源,而不是在activex控件自身模块中查找,这样肯定会查找失败。
解决此类的问题根本在于修正当前的资源模块句柄。
MFC提供了一个宏 AFX_MANAGE_STATE(GetStaticModuleState())可以帮我们解决问题。在函数的入口,调用此行语句,用于将当前的模块句柄切换到该代码被所在模块的句柄。
修改一下dll的ShowDlg()函数:
extern "C" __declspec(dllexport) void ShowDlg(UINT nID)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CDialog dlg(nID);
dlg.DoModal();
return;
}
rebuild dll,再次运行exe,点击按钮,发现这次弹出来的是dll中的对话框。

另外,你还可以直接手动设置模块句柄,记得在修改之后,函数退出部分,需要将其设置回来。最好的办法还是用MFc提供的宏AFX_MANAGE_STATE(AfxGetStaticModuleState())来进行模块句柄的切换。
最后,如果你纯粹的使用SDK API来实现,那么不存在问题,因为在api中会指定资源所在的模块句柄。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值