WTL7.5
在
VC8
中的使用简单剖析
目录
我看了很多关于MFC/ATL/COM方面的书,我发现国外的技术作者大多喜好挖掘代码内部最晦涩难懂的精华部分,试图解释给读者,当然我非常感激,我也因此受益匪浅。但是常常发现当我在钻研技术底层的时候,容易迷失了方向,搞不清这些东西做什么用。我常常想,写书的人,在每每挖掘内核的时候,总是能在一开始提纲携领的描述一下我们要做什么,可能效果好得多。但是也许那些人实在站在比我高得太多的山巅上,那些在他们已经非常熟悉以至于不需要多言的事情,对我而言很多却是陌生的领域。
所以我写文章还是按照我的能够接受的思路,首先从应用角度看待WTL,了解WTL和ATL提供的诸多窗口类的功能,如何搭配使用,实现各种不同风格的窗口或者控件。至于内部消息流动机制是如何的优秀,还是放在另外的专门文章里面讨论巴。
从Microsoft网站上下载并解压到WTL7.5目录下面,执行WTL75/AppWiz目录下面的setup80.js脚本程序,这样我们就可以在VC8中使用WTL Wizard了。
使用WTL Wizard创建一个WTL项目,不管是什么,创建后要在工程属性中添加WTL75/include文件所在的绝对路径到C/C++和ReSources的Additional Include Directories属性中。
使用WTL Wizard向导,我们很容易产生一个对话框程序。具体操作不用多说,我们现在来看向导生成的模式对话框的代码:
class
CMainDlg : public CAxDialogImpl<CMainDlg>
{
public
:
enum { IDD = IDD_MAINDLG };
BEGIN_MSG_MAP(CMainDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
END_MSG_MAP()
LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
public
:
};
从上面,我们可以看到很多ATL窗口类的痕迹,CAxDialogImpl类是ATL的一个对话框类,使用该类可以创建模式或者非模式对话框,该类的第一个模板参数我们应该传递我们的派生类,这里是CMainDlg类。第二个参数则是整个派生层次中的父类,默认是CWindow,这同样是一个ATL窗口类,CWindow类提供了很多成员函数,大多是使用窗口句柄的sdk函数的封装。
如果我们要对话框能够容纳ActiveX控件,就应该使用CAxDialogImpl类,否则可以使用
ATL提供的另一个类CDialogImpl类。
BEGIN_MSG_MAP/END_MSG_MAP宏以及它们之间使用的HANDLER宏都属于ATL的窗口消息映射机制。看上去模板对话框类在WTL中很简单。
让我们来看看非模板对话框类。
class
CMainDlg : public CAxDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>,
public CMessageFilter, public CIdleHandler
{
public
:
enum { IDD = IDD_MAINDLG };
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual BOOL OnIdle();
BEGIN_UPDATE_UI_MAP(CMainDlg)
END_UPDATE_UI_MAP()
BEGIN_MSG_MAP(CMainDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
END_MSG_MAP()
LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
void CloseDialog(int nVal);
};
这里CMainDlg多了三个父类:CUpdateUI,CMessageFilter,CIdleHandler类。CUpdateUI类用来动态刷新各种UI元素,因为非模式对话框常常不被关闭,而是经常被其它窗口遮挡,因此这个特性很有用。常常需要配合下列宏使用:
BEGIN_UPDATE_UI_MAP(CMainFrame)
UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP)
UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP)
END_UPDATE_UI_MAP()
在CUpdateUI的父类中定义了下面的一些需要刷新的子元素的类型:
enum
{
// UI element type
UPDUI_MENUPOPUP = 0x0001,
UPDUI_MENUBAR = 0x0002,
UPDUI_CHILDWINDOW = 0x0004,
UPDUI_TOOLBAR = 0x0008,
UPDUI_STATUSBAR = 0x0010,
// state
UPDUI_ENABLED = 0x0000,
UPDUI_DISABLED = 0x0100,
UPDUI_CHECKED = 0x0200,
UPDUI_CHECKED2 = 0x0400,
UPDUI_RADIO = 0x0800,
UPDUI_DEFAULT = 0x1000,
UPDUI_TEXT = 0x2000,
// internal state
UPDUI_CLEARDEFAULT = 0x4000,
};
class
CMessageFilter
{
public
:
virtual BOOL PreTranslateMessage(MSG* pMsg) = 0;
};
CMessageFilter
类只是一个Abstract Class,继承她的目的是实现PreTranslateMessage,这样CMainDlg类可以拦截一些特定的消息。如下代码过滤掉非对话框消息。
BOOL CMainDlg::PreTranslateMessage(MSG* pMsg)
{
return CWindow::IsDialogMessage(pMsg);
}
这个特性也适合于模式对话框,我们可以手动添加。
class
CIdleHandler
{
public
:
virtual BOOL OnIdle() = 0;
};
CIdleHandler
同样也是提供了一个接口而已,CMainDlg类实现OnIdle函数,该函数将在空闲时(即消息队列为空)被调用。但是返回的BOOL值代表什么呢? 通过跟踪Call Stack我发现,实际上是CMessageLoop类的OnIdle函数内部调用了该Idle函数:
virtual
BOOL OnIdle(int /*nIdleCount*/)
{
for(int i = 0; i < m_aIdleHandler.GetSize(); i++)
{
CIdleHandler* pIdleHandler = m_aIdleHandler[i];
if(pIdleHandler != NULL)
pIdleHandler->OnIdle();
}
return FALSE; // don't continue
}
由于并不检查返回值,所以我暂时认为返回值没有什么用。
通过上面的分析,我们已经清楚了向导是如何生成模式和非模式对话框类的,现在我们看看这些类如何使用。
模式对话框的创建方式:
CMainDlg dlgMain;
nRet = dlgMain.DoModal();
非模式对话框的创建方式:
CMainDlg dlgMain;
if
(dlgMain.Create(NULL) == NULL)
{
ATLTRACE(_T("Main dialog creation failed!/n"));
return 0;
}
dlgMain.ShowWindow(nCmdShow);
如果我们没有向导的帮助,通过上面的分析,依样画葫芦,我们仍然可以随时随地创建我们想要的对话框。
不如MFC方便的是没有向导帮助我们将对话框上的控件变成对应的变量,因此我们在WTL中总是要这样:(假设IDC_EDIT1是我们的一个控件id)
CEdit m_edit1
;
m_Edit1.Attach(GetDlgItem(IDC_EDIT1));
m_edit1.SetWindowsText(_T("Hello"));
MFC的Document/View结构非常的复杂,相比较之下,WTL的窗口程序就简化很多,舍弃了Document的概念,又大大利用了模板的灵活性。
传统的SDI窗口总是有一个Frame Window类和一个View类构成。WTL也是如此。我们先来看看向导生成的View类:
class
CSDIView : public CWindowImpl<CSDIView>
{
public
:
DECLARE_WND_CLASS(NULL)
BOOL PreTranslateMessage(MSG* pMsg);
BEGIN_MSG_MAP(CSDIView)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
END_MSG_MAP()
LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
};
CWindowImpl类是ATL的窗口类,它接受三个模板参数,前两个和类CAxDialogImpl类相同,第三个类代表了窗口特征,默认是CControlWinTraits类,我们通过传递不同的窗口特征类可以改变窗口的特征,比如是View还是Frame风格,比如CFrameWinTraits类。
现在我们看看Frame Window类:
class
CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>,
public CMessageFilter, public CIdleHandler
{
public
:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
CSDIView m_view;
CCommandBarCtrl m_CmdBar;
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual BOOL OnIdle();
BEGIN_UPDATE_UI_MAP(CMainFrame)
UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP)
UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP)
END_UPDATE_UI_MAP()
BEGIN_MSG_MAP(CMainFrame)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)
COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
END_MSG_MAP()
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
LRESULT OnFileExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnViewToolBar(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnViewStatusBar(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
};
就像前面所述,如果我们创建自己的框架窗口,可以使用CFrameWinTraits类作为模板参数传递给
CWindowImpl类,这是ATL的实现方法,WTL利用此机理提供了一个更加方便的类CFrameWindowImpl。
DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME),该宏制定了框架窗口所使用的菜单资源。
同时,框架窗口类包含了一个我们的
CSDIView对象作为成员变量。一切是如此的简洁灵活,令我不愿意再回到MFC的编程模型中。:)
传统上MDI窗口包含Frame Window、ChildFrame Window和View。让我们来看看向导如何实现一个ChldFram类。
class
CChildFrame : public CMDIChildWindowImpl<CChildFrame>
{
public
:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MDICHILD)
CMDIView m_view;
virtual void OnFinalMessage(HWND /*hWnd*/);
BEGIN_MSG_MAP(CChildFrame)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_FORWARDMSG, OnForwardMsg)
CHAIN_MSG_MAP(CMDIChildWindowImpl<CChildFrame>)
END_MSG_MAP()
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled);
LRESULT OnForwardMsg(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/);
};
WTL提供了CMDIChildWindowImpl类,除此之外,我们无须再做什么。
class
CMainFrame : public CMDIFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>,
public CMessageFilter, public CIdleHandler
{
public
:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
CMDICommandBarCtrl m_CmdBar;
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual BOOL OnIdle();
BEGIN_UPDATE_UI_MAP(CMainFrame)
UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP)
UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP)
END_UPDATE_UI_MAP()
BEGIN_MSG_MAP(CMainFrame)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)
COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
COMMAND_ID_HANDLER(ID_WINDOW_CASCADE, OnWindowCascade)
COMMAND_ID_HANDLER(ID_WINDOW_TILE_HORZ, OnWindowTile)
COMMAND_ID_HANDLER(ID_WINDOW_ARRANGE, OnWindowArrangeIcons)
CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
CHAIN_MSG_MAP(CMDIFrameWindowImpl<CMainFrame>)
END_MSG_MAP()
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
LRESULT OnFileExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnViewToolBar(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnViewStatusBar(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnWindowCascade(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnWindowTile(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnWindowArrangeIcons(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
};
我们的主框架窗口继承自WTL提供的类CMDIFrameWindowImpl。除此之外,我们也不需要再做什么。
几乎所有向导生成的WTL工程都使用了该全局变量:
CAppModule _Module;
CAppModule派生自CComModule类,这是一个ATL提供的旧式类,说它旧式,是因为ATL8已经提供了新的类。CComModule类内部创建了一个静态表,表中保存了com服务器中提供的各个类的对象以及相关信息,比如提供了创建com实现类的类工厂,进行生命周期管理等。
CAppModule类派生自CComModule类,显然也就获得了同样的能力,这就意味着我们可以在我们的WTL工程里面创建com对象,并提供给客户端调用。如果我们建立一个WTL窗口程序,并在某些时候需要暴露一个com类,以供外部客户以DCOM的方式远程调用,就需要这样的功能。
CAppModule自身还增加了一些功能,以适应普通窗口应用程序的要求。比如对CMessageLoop对象的管理,该类主要提供消息检测功能,负责处理消息。
总之,我认为CAppModule主要代表了一个应用程序模块,并且提供了管理com服务器的功能。
现在让我们来看看在模式对话框应用程序,向导为我们生成的_tWinMain函数。
CAppModule _Module;
int
WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
HRESULT hRes = ::CoInitialize(NULL);
// If you are running on NT 4.0 or higher you can use the following call instead to
// make the EXE free threaded. This means that calls come in on a random RPC thread.
// HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
ATLASSERT(SUCCEEDED(hRes));
// this resolves ATL window thunking problem when Microsoft Layer for Unicode (MSLU) is used
::DefWindowProc(NULL, 0, 0, 0L);
AtlInitCommonControls(ICC_BAR_CLASSES); // add flags to support other controls
hRes = _Module.Init(NULL, hInstance);
ATLASSERT(SUCCEEDED(hRes));
AtlAxWinInit();
int nRet = 0;
// BLOCK: Run application
{
CMainDlg dlgMain;
nRet = dlgMain.DoModal();
}
_Module.Term();
::CoUninitialize();
return nRet;
}
看到这里,我忍不住指出向导生成的代码不够完美,考虑到函数运行的过程中,可能会出现异常,CoInitialize/CoUninitialize函数应该包装到一个类里面。同样的问题出现在CAppModule类的Init/Term函数。由于这是模式对话框应用程序,所有的消息循环都在对话框类内部处理,所以_tWinMain函数就显得非常的简单。
现在我们再来看看复杂一点的非模式对话框应用程序:
CAppModule _Module;
int
Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT)
{
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);
CMainDlg dlgMain;
if(dlgMain.Create(NULL) == NULL)
{
ATLTRACE(_T("Main dialog creation failed!/n"));
return 0;
}
dlgMain.ShowWindow(nCmdShow);
int nRet = theLoop.Run();
_Module.RemoveMessageLoop();
return nRet;
}
int
WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
HRESULT hRes = ::CoInitialize(NULL);
// If you are running on NT 4.0 or higher you can use the following call instead to
// make the EXE free threaded. This means that calls come in on a random RPC thread.
// HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
ATLASSERT(SUCCEEDED(hRes));
// this resolves ATL window thunking problem when Microsoft Layer for Unicode (MSLU) is used
::DefWindowProc(NULL, 0, 0, 0L);
AtlInitCommonControls(ICC_BAR_CLASSES); // add flags to support other controls
hRes = _Module.Init(NULL, hInstance);
ATLASSERT(SUCCEEDED(hRes));
AtlAxWinInit();
int nRet = Run(lpstrCmdLine, nCmdShow);
_Module.Term();
::CoUninitialize();
return nRet;
}
由于这里使用了非模式对话框,也就是没有模式对话框的DoModal,该函数直到用户关闭了对话框才能够返回。没有这个函数的帮助,为了防止_tWinMain函数过早结束,这里提供了Run函数。
Run函数内部创建了CMessageLoop对象,该对象代表一个消息循环,CMessageLoop::Run函数内部调用了我们非常熟悉的GetMessage/DispatchMessageAPI处理消息。
SDI/MDI应用程序的_tWinMain也都使用了大致相同的Run函数,不同之处在于创建窗口的方式不同。
WTL向导创建工程时,我么可以选择创建一个多线程版的SDI应用程序。和前面我介绍的SDI应用程序想比,在窗口类代码上完全相同,不同的就是_tWinMain函数。
int
WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
HRESULT hRes = ::CoInitialize(NULL);
// If you are running on NT 4.0 or higher you can use the following call instead to
// make the EXE free threaded. This means that calls come in on a random RPC thread.
// HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
ATLASSERT(SUCCEEDED(hRes));
// this resolves ATL window thunking problem when Microsoft Layer for Unicode (MSLU) is used
::DefWindowProc(NULL, 0, 0, 0L);
AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES); // add flags to support other controls
hRes = _Module.Init(NULL, hInstance);
ATLASSERT(SUCCEEDED(hRes));
AtlAxWinInit();
int nRet = 0;
// BLOCK: Run application
{
CSDIMulThreadThreadManager mgr;
nRet = mgr.Run(lpstrCmdLine, nCmdShow);
}
_Module.Term();
::CoUninitialize();
return nRet;
}
该函数内部使用了CXXThreadManage类(XX代表我的工程名)。让我们来看看这个多线程管理类的内部实现:
class
CSDIMulThreadThreadManager
{
public
:
// thread init param
struct _RunData
{
LPTSTR lpstrCmdLine;
int nCmdShow;
};
// thread proc
static DWORD WINAPI RunThread(LPVOID lpData)
{
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);
_RunData* pData = (_RunData*)lpData;
CMainFrame wndFrame;
if(wndFrame.CreateEx() == NULL)
{
ATLTRACE(_T("Frame window creation failed!/n"));
return 0;
}
wndFrame.ShowWindow(pData->nCmdShow);
::SetForegroundWindow(wndFrame); // Win95 needs this
delete pData;
int nRet = theLoop.Run();
_Module.RemoveMessageLoop();
return nRet;
}
DWORD m_dwCount;
HANDLE m_arrThreadHandles[MAXIMUM_WAIT_OBJECTS - 1];
CSDIMulThreadThreadManager() : m_dwCount(0)
{ }
// Operations
DWORD AddThread(LPTSTR lpstrCmdLine, int nCmdShow)
{
if(m_dwCount == (MAXIMUM_WAIT_OBJECTS - 1))
{
::MessageBox(NULL, _T("ERROR: Cannot create ANY MORE threads!!!"), _T("SDIMulThread"), MB_OK);
return 0;
}
_RunData* pData = new _RunData;
pData->lpstrCmdLine = lpstrCmdLine;
pData->nCmdShow = nCmdShow;
DWORD dwThreadID;
HANDLE hThread = ::CreateThread(NULL, 0, RunThread, pData, 0, &dwThreadID);
if(hThread == NULL)
{
::MessageBox(NULL, _T("ERROR: Cannot create thread!!!"), _T("SDIMulThread"), MB_OK);
return 0;
}
m_arrThreadHandles[m_dwCount] = hThread;
m_dwCount++;
return dwThreadID;
}
void RemoveThread(DWORD dwIndex)
{
::CloseHandle(m_arrThreadHandles[dwIndex]);
if(dwIndex != (m_dwCount - 1))
m_arrThreadHandles[dwIndex] = m_arrThreadHandles[m_dwCount - 1];
m_dwCount--;
}
int Run(LPTSTR lpstrCmdLine, int nCmdShow)
{
MSG msg;
// force message queue to be created
::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
AddThread(lpstrCmdLine, nCmdShow);
int nRet = m_dwCount;
DWORD dwRet;
while(m_dwCount > 0)
{
dwRet = ::MsgWaitForMultipleObjects(m_dwCount, m_arrThreadHandles, FALSE, INFINITE, QS_ALLINPUT);
if(dwRet == 0xFFFFFFFF)
{
::MessageBox(NULL, _T("ERROR: Wait for multiple objects failed!!!"), _T("SDIMulThread"), MB_OK);
}
else if(dwRet >= WAIT_OBJECT_0 && dwRet <= (WAIT_OBJECT_0 + m_dwCount - 1))
{
RemoveThread(dwRet - WAIT_OBJECT_0);
}
else if(dwRet == (WAIT_OBJECT_0 + m_dwCount))
{
if(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_USER)
AddThread(_T(""), SW_SHOWNORMAL);
}
}
else
{
::MessageBeep((UINT)-1);
}
}
return nRet;
}
};
实际上,这里做的就是创建一个辅线程,在辅线程内创建并显示一个窗口,如果接收到一个用户自定义消息WM_USER,就立刻再创建一个辅线程,并再次创建显示一个窗口,直到所有窗口被关闭,程序才结束。