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,就立刻再創建一個輔線程,並再次創建顯示一個窗口,直到所有窗口被關閉,程序才結束。
陳抒 1/6/2007
ALL RIGHTS RESERVED.