MFC在规则动态库中,创建子对话框

我们在设计平台类应用程序时,常常会受到一些特殊UI的影响,例如,在一个算法平台中,要加入一个特殊算法的配置对话框,这时,平台维护工程师,肯定不想把这个对话框放在平台中,一是当前的对话框压根和当前模块的业务没有任何关系,另外,平台工程师也不想陷入无尽的UI编写中。 
     这时,我们可以把这些特殊的UI放在不同的模块中,平台只要调用就可以了。
     想法固然是好,但在实践中因为对线程状态,模块状态,线程模块状态理解不够深入,常常会令接口使用者在调用时,出现各种调用失败的错误。以下就是一接口设计中常见的错误。

#ifndef _RPRT_HIGHLEVEL_HEADER
#define _RPRT_HIGHLEVEL_HEADER

#ifndef RPRT_HIGHLEVEL_DLLAPI
#define RPRT_HIGHLEVEL_DLLAPI extern "C" __declspec(dllimport)
#endif

RPRT_HIGHLEVEL_DLLAPI int InitPrintModule();
RPRT_HIGHLEVEL_DLLAPI int ReleasePrintModule();
/**获取收集图像对话框
*rc: 给定的ChildDialog的大小和相对于pWnd位置
*pWnd:当前对话框的父窗口
*/
RPRT_HIGHLEVEL_DLLAPI HWND GetChildDialog(CWnd* pParentWnd ,const CRect rc );
/**获取胶片预览对话框
*/
RPRT_HIGHLEVEL_DLLAPI int ShowPreviewDialog();
/**插入一个打印任务
*strFileName为dicom图像的路径,其中gsps路径是本程序默认组合
*/
RPRT_HIGHLEVEL_DLLAPI int PushOneTask(const CString& strFileName);
#endif

其中,我们着重看GetChildDialog导出函数。客户端代码是

CRect rcReigion;
m_Static_Drag.GetClientRect(rcReigion);
m_Static_Drag.MapWindowPoints(this,&rcReigion);
GetChildDialog(this,rcReigion);

动态库中的代码是

在动态库中,GetChildDialog的实现代码如下
CDlgTest* gpDlgPrintCollect = NULL;
RPRT_HIGHLEVEL_DLLAPI HWND GetChildDialog(CWnd* pParentWnd, CRect rc)
{

	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	if (gpDlgPrintCollect == NULL)
	{
		if (pParentWnd == NULL)
		{
			return NULL;
		}
		gpDlgPrintCollect = new CDlgTest(pParentWnd,rc);
		gpDlgPrintCollect->ShowWindow(SW_SHOW);
	}
	return gpDlgPrintCollect->GetSafeHwnd();
}

但,程序在Debug状态下,会引发如下断言
// should also be in the permanent or temporary handle map
CHandleMap* pMap = afxMapHWND();
ASSERT(pMap != NULL);令模块的编写人员,很是郁闷。

自己看注释,实际上很清楚,英文的意思是当前传入的pWnd指针,不在当前线程的临时或者持久的队列中。这个队列又是什么意思呢?
    还得从线程安全说起。因为MFC对象不是线程安全(线程安全有两种理解方式:一种是不能同时被两个线程来调用;另外一种是一个线程不能调用另外线程创建的对象。进一步可证实,第二种情况,也是为了防止第一种情况的出现,即,两个线程不能同时操作一个对象)的。
   为什么MFC的设计者不把这些对象设计成线程安全的?按照微软的原话,原因有二,一是考录到MFC对象的大小(object size),另外,由于执行效率的考虑(performance)。
   微软为了保证MFC对象只有一个线程来调用(为保证对象线程安全,通常的做法是设置访问对象的开关,此开关有用户态的CriticalSection,更有核心态中一些列的Handle:Event,Mutex等;另外的一种方式就是MFC采用的方式,只能由一个线程来调用此对象。MFC中是只能由创建对象的线程来调用此对象),要查当前的MFC对象是否是由当前线程所创建。为了实现此目的,微软的做法是在线程的本地存储(TLS)中,保存线程本地对象表单,这些表单有如下:
HWND (CWnd and CWnd-derived classes)
HDC (CDC and CDC-derived classes)
HMENU (CMenu)
HPEN (CGdiObject)
HBRUSH (CGdiObject)
HFONT (CGdiObject)
HBITMAP (CGdiObject)
HPALETTE (CGdiObject)
HRGN (CGdiObject)
HIMAGELIST (CImageList)
SOCKET (CSocket)
MFC使用AFX_MODULE_THREAD_STATE对象,把上述的对照表单都包装了起来。
     这时,可能你要说,当前的调用pParentWnd和gpDlgPrintCollect是同一个线程进行创建的啊。可你要在看AFX_MODULE_THREAD_STATE,从名字上就能猜出,这个量是和线程有关系,同时和当前模块也是有关系的。
    你可以google这边文章<<How to Provide Your Own DllMain in an MFC Regular DLL>>,看文章的最后段落,MFC CWnd objects, CDC objects, CMenu objects, GDI objects, and CImageList objects are restricted to a per-thread, per-module basis. In other words, MFC objects created in one module or thread cannot be passed to and/or used in a different module or thread.也就是说不要在MFC的规则动态库中传递诸如,CWnd CDC CMenu GDI CImageList对象。
   所以,解决办法应当修改接口为:自己看注释,实际上很清楚,英文的意思是当前传入的pWnd指针,不在当前线程的临时或者持久的队列中。这个队列又是什么意思呢?
    还得从线程安全说起。因为MFC对象不是线程安全(线程安全有两种理解方式:一种是不能同时被两个线程来调用;另外一种是一个线程不能调用另外线程创建的对象。进一步可证实,第二种情况,也是为了防止第一种情况的出现,即,两个线程不能同时操作一个对象)的。
   为什么MFC的设计者不把这些对象设计成线程安全的?按照微软的原话,原因有二,一是考录到MFC对象的大小(object size),另外,由于执行效率的考虑(performance)。
   微软为了保证MFC对象只有一个线程来调用(为保证对象线程安全,通常的做法是设置访问对象的开关,此开关有用户态的CriticalSection,更有核心态中一些列的Handle:Event,Mutex等;另外的一种方式就是MFC采用的方式,只能由一个线程来调用此对象。MFC中是只能由创建对象的线程来调用此对象),要查当前的MFC对象是否是由当前线程所创建。为了实现此目的,微软的做法是在线程的本地存储(TLS)中,保存线程本地对象表单,这些表单有如下:
HWND (CWnd and CWnd-derived classes)
HDC (CDC and CDC-derived classes)
HMENU (CMenu)
HPEN (CGdiObject)
HBRUSH (CGdiObject)
HFONT (CGdiObject)
HBITMAP (CGdiObject)
HPALETTE (CGdiObject)
HRGN (CGdiObject)
HIMAGELIST (CImageList)
SOCKET (CSocket)
MFC使用AFX_MODULE_THREAD_STATE对象,把上述的对照表单都包装了起来。
     这时,可能你要说,当前的调用pParentWnd和gpDlgPrintCollect是同一个线程进行创建的啊。可你要在看AFX_MODULE_THREAD_STATE,从名字上就能猜出,这个量是和线程有关系,同时和当前模块也是有关系的。
    你可以google这边文章<<How to Provide Your Own DllMain in an MFC Regular DLL>>,看文章的最后段落,MFC CWnd objects, CDC objects, CMenu objects, GDI objects, and CImageList objects are restricted to a per-thread, per-module basis. In other words, MFC objects created in one module or thread cannot be passed to and/or used in a different module or thread.也就是说不要在MFC的规则动态库中传递诸如,CWnd CDC CMenu GDI CImageList对象。
   所以,解决办法应当修改接口为:

CDlgTest* gpDlgPrintCollect = NULL;
RPRT_HIGHLEVEL_DLLAPI HWND GetCollectionDialog(HWND hWnd,const CRect rc)
{

	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	if (gpDlgPrintCollect == NULL)
	{
		if (hWnd == NULL)
		{
			return NULL;
		}
		CWnd* pTemporayWnd = CWnd::FromHandle(hWnd);
		if (pTemporayWnd == NULL)
		{
			return NULL;
		}
		gpDlgPrintCollect = new CDlgTest(pTemporayWnd,rc);
		gpDlgPrintCollect->ShowWindow(SW_SHOW);

	}
	return gpDlgPrintCollect->GetSafeHwnd();
}

但是,这个接口已经定义好了,改动起来涉众较广,如果你是Dll的编写者,你可以修改为

CDlgTest* gpDlgPrintCollect = NULL;
RPRT_HIGHLEVEL_DLLAPI HWND GetChildDialog(CWnd* pParentWnd, CRect rc)
{

	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	if (gpDlgPrintCollect == NULL)
	{
		if (pParentWnd == NULL)
		{
			return NULL;
		}
		CWnd* pTempWnd = CWnd::FromHandle(pParentWnd->GetSafeHwnd());
		gpDlgPrintCollect = new CDlgTest(pParentWnd,rc);
		gpDlgPrintCollect->ShowWindow(SW_SHOW);
	}
	return gpDlgPrintCollect->GetSafeHwnd();
};  
最后,解释一下AFX_MANAGE_STATE

AFX_MANAGE_STATE::AFX_MANAGE_STATE(AFX_MODULE_STATE* pNewState)
{
    m_pThreadState = _afxThreadState;//获取当前的线程状态
    m_pPrevModuleState = m_pThreadState->m_pModuleState;//保存当前的模块状态,当前的模块状态又从线程状态中获取
    m_pThreadState->m_pModuleState = pNewState;//采用当前的新的模块状态
}






















































































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值