使用DLL中的资源

我很早的时候写过一篇MFC中CDialog与其对话框资源的绑定,但这几天在MFC DLL上做了一些工作后发现当时的理解实在肤浅。说不定过了几年再回头看看目前这篇文章,又觉得本文也是鬼话连篇了吧,哈哈。

使用DLL中的资源面临的一个主要问题是,DLL和EXE中都有资源集,但是程序在运行态到底会去哪个资源集中找常常令我们疑惑。
考虑如下的经典情况:

在新建MFC DLL工程时选中Regular DLL using shared MFC DLL选项,新建一个与MFC自身DLL共享的DLL。在新DLL中新建一个ID名为IDD_DLLDIALOG的对话框资源。在这个DLL中导出一个ShowDialog()函数,内容如下:

extern "C" void Show Dialog()
{
    CDialog dlg(IDD_DLLDIALOG);
    dlg.DoModal();
}

新建一个MFC的EXE工程,导入刚刚新建的DLL工程的lib和dll,将OK按钮的响应函数设置为:  ShowDialog();  编译链接通过后,我们运行起程序来会发现点击OK按钮后什么反应都没有!怎么会这样?我们不是明明在DLL里设定好了IDD_DLLDIALOG吗?请大家注意“在新建MFC DLL工程时选中Regular DLL using shared MFC DLL”这句话,如果我们当时选的是“Regular DLL with MFC statically linked”又会是什么情况呢?又能正常显示了!

使用静态连接的方式访问MFC库DLL,会把MFC库DLL与工程本身一起打包,而共享的方式则会在运行时去系统路径中动态加载MFC库DLL。我们可以这么理解以上的现象:如果新建的DLL使用共享的方式访问库DLL,当EXE程序运行时,实际上有两套上下文系统在运作,而静态连接则只有一套上下文系统了,因此在静态连接时会需要进行模块状态的切换。

当我们新建的是共享方式访问 库DLL的DLL时,要解决上述问题比较通用的做法是在ShowDialog函数里面加上一句AFX_MANAGE_STATE(AfxGetStaticModuleState());

AfxGetStaticModuleState()的原型为:

AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( );

即当前模块的ModuleState指针。

AFX_MANAGE_STATE宏则会以传入的指针为参数去在栈上新建一个类,此类会做哪些事相信聪明的你也能猜到了.没错,它会在构造函数中存下当前的ThreadState所指向的ModuleState指针,再将传入的指针指定为当前ThreadState所指向的指针。等到这个类的实例被析构是,又将其改回去。

令外一种方法是使用AfxGetResourceHander()配合AfxSetResourceHander(HINSTANCE)了(这也正是我在MFC中CDialog与其对话框资源的绑定中介绍的方法)。

如果我们不想在EXE中通过间接的调用DLL导出的ShowDialog(),而是直接想在EXE的OnBnClickedOk()中生成一个使用DLL中对话框资源的对话框呢?这时AFX_MANAGE_STATE(AfxGetStaticModuleState())就派不上用场了(因这它是用在DLL工程中的)。最好的办法是用AfxSetResourceHander(HINSTANCE)配合GetModuleHandle(LPCTSTR)。

GetModuleHandle(LPCTSTR str)将会根据传入的字符串得到相应的模块句柄。注意,EXE和DLL都可以被看作是模块。当传入为空(NULL)时,将得到当前进程(EXE)的HINSTANCE句柄。使用GetModuleHandle要保证相应的dll已经被加载,也许你会说这个条件不是废话么,但是这确实是很容易犯错的地方。

如果是用静态加载DLL的方式(即直接把生成的那个.lib文件导入),而之前又从未调用过这个dll中的任何函数或变量的话,GetModuleHandle只会得到一个空值。因为系统会直到有某个语句调用过dll里面的东西时才会将dll真正加载。

例如:

void CUseDllDlg::OnBnClickedOk()
{		
    HINSTANCE temp = AfxGetResourceHandle();	
    AfxSetResourceHandle(GetModuleHandle(_T("RegDll")));	
    CDialog dlg(4000);	
    dlg.DoModal();	
    AfxSetResourceHandle(temp);
}

上面的这份代码运行时会报错,因为AfxSetResourceHandle在检查入参时发现了个空值。而下面的代码则不会有问题:

void CUseDllDlg::OnBnClickedOk()
{		
    ASSERT(TestOnly() == 1);
    HINSTANCE temp = AfxGetResourceHandle();
    AfxSetResourceHandle(GetModuleHandle(_T("RegDll")));	
    CDialog dlg(4000);
    dlg.DoModal();
    AfxSetResourceHandle(temp);
}

其中TestOnly也是从RegDll中导出的函数,它什么都不做,只是返回一个1。我们使用它只是为了把RegDll加载进来。当然了,如果我们直接使用动态加载的方法,像下面这样直接LoadLibrary那更是没有问题了。

void CUseDllDlg::OnBnClickedOk()
{	
    HMODULE hm= LoadLibrary(_T("..//Debug//RegDll.dll"));	
    HINSTANCE temp = AfxGetResourceHandle();	
    AfxSetResourceHandle(GetModuleHandle(_T("RegDll")));
    CDialog dlg(4000);	
    dlg.DoModal();	
    AfxSetResourceHandle(temp);
}

不过眼尖的读者可能会立即发现其实在上面这种情况下使用GetModuleHandle不是最好的选择。因为直接来个AfxSetResourceHandle(hm)就可解决问题了。

3 thoughts on “使用DLL中的资源

发表评论

电子邮件地址不会被公开。 必填项已用*标注