很早就想寫這篇日志了,2008年了嗎,一個新的開始,總要寫點什么做點留念吧!可以一拖再拖,現在都1月5號了!感覺自己再不寫的話,就要到2009年才能寫這篇日志了。
2008年的第一個天,也就是1月1號,不知道各位是怎么過的,我是基本上就躺在床上什么都不想,傻乎乎的看完了一部《雙妻時代》,因為這樣,那一天的時間我才能打發掉,那樣才能讓我覺得不失落。前段時間突然覺得自己畢業后的很多時間都用來去追尋所謂的愛情,結果卻TMD什么都沒有,無非讓自己更曖昧,更知道怎么才不會受傷,更知道怎么才能收放自如。。。。。。等等一切,我發現我開始“變得有點壞”了,不是我想變,是別人把我變成這樣的,不要說“你可以不變啊”,我是沒有想變,可是TMD我也不知道怎么就這樣了。我被人說成了“太曖昧了、嘴巴太甜了。。。”,這兩天還被一些人定義成“一個騙子”,可我什么都沒有做啊。。。。。。難道讓我去接受一個我不喜歡的人,我就不是騙子了? 呵呵!只是有的時候覺得很好笑,以前都TMD人家不要我,原來我也可以“不要”別人的。太搞笑了!
不想那么多了,我知道我自己想要什么,無論別人怎么說我,只是希望以后的她對我好就足夠了。這里再扯淡一下:有個人幫我看手上的愛情線,說我要招惹7個女人,暈死!!我現在滿打滿算,還有好幾個要我招惹啊!!
前段時間去參加一個培訓后,突然發現自己原來這一年多,都把時間花在前任女朋友身上了,太多的東西要學了,要不然真的要“掛了”,發現自己對技術的了解太少了,自己不知道最近一年都在干些什么,再不學習很快就要完蛋了,追,要追,最近2個月來的學習后覺的自己還是比較喜歡現在這樣生活,有學習才有樂趣。不是嗎? 希望到了2009年1月1號的時候,自己通過1年多的學習,可以有些進步。
“To Do,To Become,To Own”----用這句話勉勵自己!
深入探討MFC消息循環和消息泵
作者:周焱
首 先,應該清楚MFC的消息循環(::GetMessage,::PeekMessage),消息泵(CWinThread::PumpMessage)和 MFC的消息在窗口之間的路由是兩件不同的事情。在MFC的應用程序中(應用程序類基于CWinThread繼承),必須要有一個消息循環,他的作用是從 應用程序的消息隊列中讀取消息,并把它派送出去(::DispatchMessage)。而消息路由是指消息派送出去之后,系統(USER32.DLL) 把消息投遞到哪個窗口,以及以后消息在窗口之間的傳遞是怎樣的。
消息分為隊列消息(進入線程的消息隊列) 和非隊列消息(不進入線程的消息隊列)。對于隊列消息,最常見的是鼠標和鍵盤觸發的消息,例如WM_MOUSERMOVE,WM_CHAR等消息;還有例 如:WM_PAINT、WM_TIMER和WM_QUIT。當鼠標、鍵盤事件被觸發后,相應的鼠標或鍵盤驅動程序就會把這些事件轉換成相應的消息,然后輸 送到系統消息隊列,由Windows系統負責把消息加入到相應線程的消息隊列中,于是就有了消息循環(從消息隊列中讀取并派送消息)。還有一種是非隊列消 息,他繞過系統隊列和消息隊列,直接將消息發送到窗口過程。例如,當用戶激活一個窗口系統發送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。創建窗口時發送WM_CREATE消息。在后面你將看到,MS這么設計是很有道理的,以及他的整套實現機制。
這里講述MFC的消息循環,消息泵。先看看程序啟動時,怎么進入消息循環的:
_tWinMain ->AfxWinMain ->AfxWinInit ->CWinThread::InitApplication ->CWinThread::InitInstance ->CWinThread::Run
非對話框程序的消息循環的事情都從這CWinThread的一Run開始...
第一部分:非對話框程序的消息循環機制。
//thrdcore.cpp
// main running routine until thread exits
int CWinThread::Run()
{
ASSERT_VALID(this);
// for tracking the idle time state
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
// acquire and dispatch messages until a WM_QUIT message is received.
for (;;)
{
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
{
// call OnIdle while in bIdle state
if (!OnIdle(lIdleCount++))
bIdle = FALSE; // assume "no idle" state
}
// phase2: pump messages while available
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();
// reset "no idle" state after pumping "normal" message
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
} //無限循環,退出條件是收到WM_QUIT消息。
ASSERT(FALSE); // not reachable
}
這是一個無限循環,他的退出條件是收到WM_QUIT消息:
if (!PumpMessage())
return ExitInstance();
在PumpMessage中,如果收到WM_QUIT消息,那么返回FALSE,所以ExitInstance()函數執行,跳出循環,返回程序的退出代碼。所以,一個程序要退出,只用在代碼中調用函數
VOID PostQuitMessage( int nExitCode )。指定退出代碼nExitCode就可以退出程序。
下面討論一下這個函數Run的流程,分兩步:
1, 第一個內循環phase1。bIdle代表程序是否空閑。他的意思就是,如果程序是空閑并且消息隊列中沒有要處理的消息,那么調用虛函數OnIdle進行 空閑處理。在這個處理中將更新UI界面(比如工具欄按鈕的enable和disable狀態),刪除臨時對象(比如用FromHandle得到的對象指 針。由于這個原因,在函數之間傳遞由FromHandle得到的對象指針是不安全的,因為他沒有持久性)。OnIdle是可以重載的,你可以重載他并返回 TRUE使消息循環繼續處于空閑狀態。
NOTE:MS用臨時對象是出于效率上的考慮,使內存 有效利用,并能夠在空閑時自動撤銷資源。關于由句柄轉換成對象,可以有若干種方法。一般是先申明一個對象obj,然后使用obj.Attatch來和一個 句柄綁定。這樣產生的對象是永久的,你必須用obj.Detach來釋放對象。
2,第二個內循環phase2。在這個循環內先啟動消息泵(PumpMessage),如果不是WM_QUIT消息,消息泵將消息發送出去(::DispatchMessage)。消息的目的地是消息結構中的hwnd字段所對應的窗口。
//thrdcore.cpp
BOOL CWinThread::PumpMessage()
{
ASSERT_VALID(this);
//如果是WM_QUIT就退出函數(return FALSE),這將導致程序結束.
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) {
#ifdef _DEBUG
if (afxTraceFlags & traceAppMsg)
TRACE0("CWinThread::PumpMessage - Received WM_QUIT.\n");
m_nDisablePumpCount++; // application must die
// Note: prevents calling message loop things in 'ExitInstance'
// will never be decremented
#endif
return FALSE;
}
#ifdef _DEBUG
if (m_nDisablePumpCount != 0)
{
TRACE0("Error: CWinThread::PumpMessage called when not permitted.\n");
ASSERT(FALSE);
}
#endif
#ifdef _DEBUG
if (afxTraceFlags & traceAppMsg)
_AfxTraceMsg(_T("PumpMessage"), &m_msgCur);
#endif
// process this message
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
::TranslateMessage(&m_msgCur); //鍵轉換
::DispatchMessage(&m_msgCur); //派送消息
}
return TRUE;
}
在 這一步有一個特別重要的函數大家一定認識:PreTranslateMessage。這個函數在::DispatchMessage發送消息到窗口之前, 進行對消息的預處理。PreTranslateMessage函數是CWinThread的成員函數,大家重載的時候都是在View類或者主窗口類中,那 么,它是怎么進入別的類的呢?代碼如下:
//thrdcore.cpp
BOOL CWinThread::PreTranslateMessage(MSG* pMsg)
{
ASSERT_VALID(this);
// 如果是線程消息,那么將會調用線程消息的處理函數
if (pMsg->hwnd == NULL && DispatchThreadMessageEx(pMsg))
return TRUE;
// walk from target to main window
CWnd* pMainWnd = AfxGetMainWnd();
if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg))
return TRUE;
// in case of modeless dialogs, last chance route through main
// window's accelerator table
if (pMainWnd != NULL)
{
CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
if (pWnd->GetTopLevelParent() != pMainWnd)
return pMainWnd->PreTranslateMessage(pMsg);
}
return FALSE; // no special processing
}
由上面這個函數可以看出:
第一,如果(pMsg->hwnd == NULL),說明這是一個線程消息。調用CWinThread::DispatchThreadMessageEx到消息映射表找到消息入口,然后調用消息處理函數。
NOTE: 一般用PostThreadMessage函數發送線程之間的消息,他和窗口消息不同,需要指定線程id,消息激被系統放入到目標線程的消息隊列中;用 ON_THREAD_MESSAGE( message, memberFxn )宏可以映射線程消息和他的處理函數。這個宏必須在應用程序類(從CWinThread繼承)中,因為只有應用程序類才處理線程消息。如果你在別的類(比 如視圖類)中用這個宏,線程消息的消息處理函數將得不到線程消息。
第二,消息的目標窗口的 PreTranslateMessage函數首先得到消息處理權,如果函數返回FALSE,那么他的父窗口將得到消息的處理權,直到主窗口;如果函數返回 TRUE(表示消息已經被處理了),那么就不需要調用父類的PreTranslateMessage函數。這樣,保證了消息的目標窗口以及他的父窗口都可 以有機會調用PreTranslateMessage--在消息發送到窗口之前進行預處理(如果自己處理完然后返回FALSE的話 -_-b),如果你想要消息不傳遞給父類進行處理的話,返回TRUE就行了。
第三,如果消息的目標窗口和主窗口沒有父子關系,那么再調用主 窗口的PreTranslateMessage函數。為什么這樣?由第二步知道,一個窗口的父窗口不是主窗口的話,盡管它的 PreTranslateMessage返回FALSE,主窗口也沒有機會調用PreTranslateMessage函數。我們知道,加速鍵的轉換一般 在框架窗口的PreTranslateMessage函數中。我找遍了MFC中關于加速鍵轉換的處理,只有CFrameWnd, CMDIFrameWnd,CMDIChildWnd等窗口類有。所以,第三步的意思是,如果消息的目標窗口(他的父窗口不是主窗口,比如一個這樣的非模 式對話框)使消息的預處理繼續漫游的話(他的PreTranslateMessage返回FALSE),那么給一次機會給主窗口調用 PreTranslateMessage(萬一他是某個加速鍵消息呢?),這樣能夠保證在有非模式對話框的情況下還能保證主窗口的加速鍵好使。
我做了一個小例子,在對話框類的PreTranslateMessage中,返回FALSE。在主窗口顯示這個非模式對話框,在對話框擁有焦點的時候,仍然能夠激活主窗口的快捷鍵。
總之,整個框架就是讓每個消息的目標窗口(包括他的父窗口)都有機會參與消息到來之前的處理。呵呵~
至 此,非對話框的消息循環和消息泵的機制就差不多了。這個機制在一個無限循環中,不斷地從消息隊列中獲取消息,并且保證了程序的線程消息能夠得到機會處理, 窗口消息在預處理之后被發送到相應的窗口處理過程。那么,還有一點疑問,為什么要一會兒調用::PeekMessage,一會兒調用:: GetMessage呢,他們有什么區別?
NOTE:一般來說,GetMessage被設計用來高效地從消息隊列獲取消息。如果隊列中沒有消息,那么函數GetMessage將導致線程休眠(讓出CPU時間)。而PeekMessage是判斷消息隊列中如果沒有消息,它馬上返回0,不會導致線程處于睡眠狀態。
在 上面的phase1第一個內循環中用到了PeekMessage,它的參數PM_NOREMOVE表示并不從消息隊列中移走消息,而是一個檢測查詢,如果 消息隊列中沒有消息他立刻返回0,如果這時線程空閑的話將會引起消息循環調用OnIdle處理過程(上面講到了這個函數的重要性)。如果將:: PeekMessage改成::GetMessage(***),那么如果消息隊列中沒有消息,線程將休眠,直到線程下一次獲得CPU時間并且有消息出現 才可能繼續執行,這樣,消息循環的空閑時間沒有得到應用,OnIdle也將得不到執行。這就是為什么既要用::PeekMessage(查詢),又要 用::GetMessage(做實際的工作)的緣故。
第二部分: 對話框程序的消息循環機制
基于對話框的MFC工程和上面的消息循環機制不一樣。實際上MFC的對話框工程程序就是模式對話框。他和上面講到的非對話框程序的不同之處,主要在于應用程序對象的InitInstance()不一樣。
//dlg_5Dlg.cpp
BOOL CDlg_5App::InitInstance()
{
AfxEnableControlContainer();
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
CDlg_5Dlg dlg; //定義一個對話框對象
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal(); //對話框的消息循環在這里面開始
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}
NOTE: InitInstance函數返回FALSE,由最上面程序啟動流程可以看出,CWinThread::Run是不會得到執行的。也就是說,上面第一部分 說的消息循環在對話框中是不能執行的。實際上,對話框也有消息循環,她的消息循環在CDialog::DoModal()虛函數中的一個 RunModalLoop函數中。
這個函數的實現體在CWnd類中:
int CWnd::RunModalLoop(DWORD dwFlags)
{
ASSERT(::IsWindow(m_hWnd)); // window must be created
ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in modal state
// for tracking the idle time state
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() & WS_VISIBLE);
HWND hWndParent = ::GetParent(m_hWnd);
m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);
MSG* pMsg = &AfxGetThread()->m_msgCur;
// acquire and dispatch messages until the modal state is done
for (;;)
{
ASSERT(ContinueModal());
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))
{
ASSERT(ContinueModal());
// show the dialog when the message queue goes idle
if (bShowIdle)
{
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
bShowIdle = FALSE;
}
// call OnIdle while in bIdle state
if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0)
{
// send WM_ENTERIDLE to the parent
::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd);
}
if ((dwFlags & MLF_NOKICKIDLE) ||
!SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))
{
// stop idle processing next time
bIdle = FALSE;
}
}
// phase2: pump messages while available
do
{
ASSERT(ContinueModal());
// pump message, but quit on WM_QUIT
//PumpMessage(消息泵)的實現和上面講的差不多。都是派送消息到窗口。
if (!AfxGetThread()->PumpMessage())
{
AfxPostQuitMessage(0);
return -1;
}
// show the window when certain special messages rec'd
if (bShowIdle &&
(pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))
{
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
bShowIdle = FALSE;
}
if (!ContinueModal())
goto ExitModal;
// reset "no idle" state after pumping "normal" message
if (AfxGetThread()->IsIdleMessage(pMsg))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));
} //無限循環
ExitModal:
m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL);
return m_nModalResult;
}
先說說怎么退出這個無限循環,在代碼中:
if (!ContinueModal())
goto ExitModal;
決定是否退出循環,消息循環函數返回也就是快要結束結束程序了。
BOOL CWnd::ContinueModal()
{
return m_nFlags & WF_CONTINUEMODAL;
}
NOTE: CWnd::ContinueModal()函數檢查對話框是否繼續模式。返回TRUE,表示現在是模式的;返回FALSE,表示對話框已經不是模式(將要結束)。
如 果要結束對話框,在內部最終會調用函數CWnd::EndModalLoop,它取消m_nFlags的模式標志(消息循環中的 ContinueModal函數將返回FALSE,消息循環將結束,程序將退出);然后激發消息循環讀取消息。也就是說,結束模式對話框是一個標志,改變 這個標志就可以了。他的代碼是:
//wincore.cpp
void CWnd::EndModalLoop(int nResult)
{
ASSERT(::IsWindow(m_hWnd));
// this result will be returned from CWnd::RunModalLoop
m_nModalResult = nResult;
// make sure a message goes through to exit the modal loop
if (m_nFlags & WF_CONTINUEMODAL)
{
m_nFlags &= ~WF_CONTINUEMODAL;
PostMessage(WM_NULL);
}
}
NOTE: PostMessage(NULL)是有用的。如果消息隊列中沒有消息的話,可能消息循環中的ContinueModal()不會馬上執行,發送一個空消息是激發消息循環馬上工作。
下面說一下CWnd::RunModalLoop函數中的消息循環究竟干了些什么事情:
1, 第一個內循環。首先從消息隊列中查詢消息,如果對話框空閑,而且消息隊列中沒有消息,他做三件事情,大家應到都能從字面上明白什么意思。最重要的是發送 WM_KICKIDLE消息。為什么呢?第一部分講到了,非對話框程序用OnIdle來更新用戶界面(UI),比如工具欄,狀態欄。那么,如果對話框中也 有工具欄和狀態欄呢,在哪里更新(網上有很多這樣的程序)?可以處理WM_KICKIDLE消息:
LRESULT CDlg_5Dlg::OnKickIdle(WPARAM w,LPARAM l)
{
//調用CWnd::UpdateDialogControls更新用戶界面
UpdateDialogControls(this, TRUE);
return 0;
}
NOTE: CWnd::UpdateDialog函數發送CN_UPDATE_COMMAND_UI消息給所有的用戶界面對話框控件。
2, 第二個內循環。最重要的還是PumpMessage派送消息到目標窗口。其他的,像第二個if語句,0x118消息好像是WM_SYSTIMER消息(系 統用來通知光標跳動的一個消息)。也就是說,如果消息為WM_SYSTIMER或者WM_SYSKEYDOWN,并且空閑顯示標志為真的話,就顯示窗口并 通知窗口立刻重繪。
總之,對話框的消息循環機制和非對話框(比如SDI,MDI)還是類似 的,僅僅側重點不同。模式對話框是模式顯示,自然有他的特點。下面部分討論一下模式對話框和非模式對話框的區別。因為模式對話框有自己的特殊消息循環;而 非模式對話框,共用程序的消息循環,和普通的窗口已經沒有什么大的區別了。
第三部分:模式對話框和非模式對話框的區別
這個話題已經有很多人討論,我說說我所理解的意思。
在MFC 框架中,一個對話框對象DoModal一下就能產生一個模式對話框,Create一下就能產生一個非模式對話框。實際上,無論是模式對話框還是非模式對話 框,在MFC內部都是調用::CreateDialogIndirect(***)函數來創建非模式對話框。只是模式對話框作了更多的工作,包括使父窗口 無效,然后進入自己的消息循環等等。::CreateDialogIndirect(***)函數最終調用CreateWindowEx函數通知系統創建 窗體并返回句柄,他內部沒有實現自己的消息循環。
非模式對話框創建之后立即返回,并且和主程序共用一個消息循環。非模式對話框要等對話框結束之后才返回,自己有消息循環。比如下面的代碼:
CMyDlg* pdlg = new CMyDlg;
pdlg ->Create(IDD_DIALOG1);
pdlg->ShowWindow(SW_SHOW);
MessageBox("abc");
非模式對話框和消息框MessageBox幾乎是同時彈出來。而如果將Create改成DoModal,那么,只能彈出模式對話框,在關閉了對話框之后(模式對話框自己的消息循環結束),消息框才彈出來。
NOTE: 可以在模式對話框中調用GetParent()->EnableWindow(true);這樣,主窗口的菜單,工具欄又激活了,能用了。MFC使 用非模式對話框來模擬模式對話框,而在win32 SDK程序中,模式對話框激發他的父窗口Enable操作是沒有效果的。
關于消息循環總結:
1, 我們站在一個什么高度看消息循環?消息循環其實沒有什么深奧的道理。如果一個郵遞員要不斷在一個城市中送信,我們要求他做什么?要求他來回跑,但他一次只 能在一個地方出現。如果我們的應用程序只有一個線程的話,我們要他不斷地為窗口傳遞消息,我們怎么做?在一個循環中不斷的檢測消息,并將他發送到適當的窗 口。窗口可以有很多個,但消息循環只有一個,而且每時每刻最多只有一個地方在執行代碼。為什么? 看第二點。
2,因為是單線程的(程序進程 啟動的時候,只有而且有一個線程,我們稱他為主線程),所以就像郵遞員一樣,每次只能在某一個地方干活。什么意思呢?舉個例子,用:: DiapatchMessage派送消息,在窗口處理過程(WinProc,窗口函數)返回之前,他是阻塞的,不會立即返回,也就是消息循環此時不能再從 消息隊列中讀取消息,直到::DispatchMessage返回。如果你在窗口函數中執行一個死循環操作,就算你用PostQuitMessage函數 退出,程序也會down掉。
while(1)
{
PostQuitMessage(0); //程序照樣down.
}
所 以,當窗口函數處理沒有返回的時候,消息循環是不會從消息隊列中讀取消息的。這也是為什么在模式對話框中要自己用無限循環來繼續消息循環,因為這個無限循 環阻塞了原來的消息循環,所以,在這個無限循環中要用GetMessage,PeekMessage,DispatchMessage來從消息隊列中讀取 消息并派送消息了。要不然程序就不會響應了,這不是我們所希望的。
所以說,消息循環放在程序的什么的地方都基本上是過的去的,比如放在DLL里 面。但是,最好在任何時候,只有一個消息循環在工作(其他的都被阻塞了)。然后,我們要作好的一件事情,就是怎么從消息循環中退出!當然用WM_QUIT 是可以拉~(PostThreadMessage也是個好主意),這個消息循環退出后,可能程序退出,也可能會激活另外一個被阻塞的消息循環,程序繼續運 行。這要看你怎么想,怎么去做。最后一個消息循環結束的時候,也許就是程序快結束的時候,因為主線程的執行代碼也快要完了(除非BT的再作個死循環)。
NOTE: 讓windows系統知道創建一個線程的唯一方法是調用API CreatThread函數(__beginthreadex之類的都要在內部調用他創建新線程)。好像windows核心編程說,在win2000下, 系統用CreateRemoteThread函數來創建線程,CreateThread在內部調用CreateRemoteThread。不過這不是爭論 的焦點,至少win98下CreateRemoteThread并不能正常工作,還是CreateThread主持大局。
3,在整個消息循環的機制中,還必須談到窗口函數的可重入性。什么意思?就是窗口函數(他是個回調函數)的代碼什么時候都可以被系統(調用者一般是user32模塊)調用。比如在窗口過程中,向自己的窗口SendMessage(***);那么執行過程是怎樣的?
我們知道,SendMessage是要等到消息發送并被目標窗口執行完之后才返回的。那么窗口在處理消息,然后又等待剛才發送到本窗口的消息被處理后之后(SendMessage返回)才繼續往下執行,程序不就互相死鎖了嗎?
其 實是不會的。windows設計一套適合SendMessage的算法,他判斷如果發送的消息是屬于本線程創建的窗口的,那么直接由user32模塊調用 窗口函數(可能就有窗口重入),并將消息的處理結果結果返回。這樣做體現了窗口重入。上面的例子,我們調用SendMessage(***)發送消息到本 窗口,那么窗口過程再次被調用,處理完消息之后將結果返回,然后SendMessage之后的程序接著執行。對于非隊列消息,如果沒有窗口重入,不知道會 是什么樣子。
NOTE: 由于窗口的可重入性。在win32 SDK程序中應盡量少用全局變量和靜態變量,因為在窗口函數執行過程中可能窗口重入,如果重入后將這些變量改了,但你的程序在窗口重入返回之后繼續執行, 可能就是使用已經改變的全局或靜態變量。在MFC中(所有窗口的窗口函數基本上都是AfxWndProc),按照類的思想進行了組織,一般變量都是類中 的,好管理的多。
4,MFC中窗口類(比如C**View,CFrameWnd等)中的MessageBox函數,以及 AfxMessageBox函數都是阻塞原有的消息循環的。由消息框內部的一個消息循環來從消息隊列中讀取消息,并派送消息(和模式對話框類似)。實際 上,這些消息函數最終調用的是::MessageBox,它在消息框內部實現了一個消息循環(原有的主程序消息循環被阻塞了)。論壇中碰到過幾次關于計時 器和消息框的問題,看下面的代碼:
void CTest_recalclayoutView::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
MessageBox("abc");
while(1); //設計一個死循環
CView::OnTimer(nIDEvent);
}
咱 讓OnTimer大約5秒鐘彈出一個消息框。那么,消息框不斷的被彈出來,只要消息框不被關閉,那么程序就不會進入死循環。實際上,每次彈出對話框,都是 最上層的那個消息框掌握著消息循環,其他的消息循環被阻塞了。只要不關閉最上面的消息框,while(1);就得不到執行。如果點了關閉,程序就進入了死 循環,只能用ctrl+alt+del來解決問題了。
5,消息循環在很多地方都有應用。比如應用在線程池中。一個線程的執行周期一般在線程 函數返回之后結束,那么怎么延長線程的生命周期呢?一種方法就是按照消息循環的思想,在線程中加入消息循環,不斷地從線程隊列讀取消息,并處理消息,線程 的生命周期就保持著直到這個消息循環的退出。
NOTE:只要線程有界面元素或者調用GetMessage,或者有線程消息發送過來,系統就會為線程創建一個消息隊列。
6, 在單線程程序中,如果要執行一個長時間的復雜操作而且界面要有相應的話,可以考慮用自己的消息泵。比如,可以將一個阻塞等待操作放在一個循環中,并將超時 值設置得比較小,然后每個等待的片段中用消息泵繼續消息循環,使界面能夠響應用戶操作。等等之類,都可以應用消息泵(調用一個類似這樣的函數):
BOOL CChildView::PeekAndPump()
{
MSG msg;
while(::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
{
if(!AfxGetApp()->PumpMessage())
{
::PostQuitMessage(0);
return false;
}
}
return true;
}
其實,用多線程也能解決復雜運算時的界面問題,但是沒有這么方便,而且一般要加入線程通信和同步,考慮的事情更多一點。
綜上所述,MFC消息循環就那么回事,主要思想還是和SDK中差不多。這種思想主要的特點表現在迎合MFC整個框架上,為整個框架服務,為應用和功能服務。這是我的理解。呵呵~
1. 你們的項目組使用源代碼管理工具了么?
MVM:應該用。VSS、CVS、PVCS、ClearCase、CCC/Harvest、FireFly都可以。我的選擇是VSS。
2. 你們的項目組使用缺陷管理系統了么?
MVM:應該用。ClearQuest太復雜,我的推薦是BugZilla。
3. 你們的測試組還在用Word寫測試用例么?
MVM:不要用Word寫測試用例(Test Case)。應該用一個專門的系統,可以是Test Manager,也可以是自己開發一個ASP.NET的小網站。主要目的是Track和Browse。
4. 你們的項目組有沒有建立一個門戶網站?
MVM:要有一個門戶網站,用來放Contact Info、Baselined Schedule、News等等。推薦Sharepoint Portal Server 2003來實現,15分鐘就搞定。買不起SPS 2003可以用WSS (Windows Sharepoint Service)。
5. 你們的項目組用了你能買到最好的工具么?
MVM:應該用盡量好的工具來工作。比如,應該用VS.NET而不是Notepad來寫C#。用Notepad寫程序多半只是一種炫耀。但也要考慮到經費,所以說是“你能買到最好的”。
6. 你們的程序員工作在安靜的環境里么?
MVM:需要安靜環境。這點極端重要,而且要保證每個人的空間大于一定面積。
7. 你們的員工每個人都有一部電話么?
MVM:需要每人一部電話。而且電話最好是帶留言功能的。當然,上這么一套帶留言電話系統開銷不小。不過至少每人一部電話要有,千萬別搞得經常有人站起來喊:“某某某電話”。《人件》里面就強烈譴責這種做法。
8. 你們每個人都知道出了問題應該找誰么?
MVM:應該知道。任何一個Feature至少都應該有一個Owner,當然,Owner可以繼續Dispatch給其他人。
9. 你遇到過有人說“我以為…”么?
MVM:要消滅“我以為”。Never assume anything。
10. 你們的項目組中所有的人都坐在一起么?
MVM:需要。我反對Virtual Team,也反對Dev在美國、Test在中國這種開發方式。能坐在一起就最好坐在一起,好處多得不得了。
11. 你們的進度表是否反映最新開發進展情況?
MVM:應該反映。但是,應該用Baseline的方法來管理進度表:維護一份穩定的Schedule,再維護一份最新更改。Baseline的方法也應該用于其它的Spec。Baseline是變更管理里面的一個重要手段。
12. 你們的工作量是先由每個人自己估算的么?
MVM:應該讓每個人自己估算。要從下而上估算工作量,而不是從上往下分派。除非有其他原因,比如政治任務工期固定等。
13. 你們的開發人員從項目一開始就加班么?
MVM:不要這樣。不要一開始就搞疲勞戰。從項目一開始就加班,只能說明項目進度不合理。當然,一些對日軟件外包必須天天加班,那屬于剝削的范疇。
14. 你們的項目計劃中Buffer Time是加在每個小任務后面的么?
MVM:不要。Buffer Time加在每個小任務后面,很容易輕易的就被消耗掉。Buffer Time要整段的加在一個Milestone或者checkpoint前面。
15. 值得再多花一些時間,從95%做到100%好
MVM:值得,非常值得。尤其當項目后期人困馬乏的時候,要堅持。這會給產品帶來質的區別。
16. 登記新缺陷時,是否寫清了重現步驟?
MVM:要。這屬于Dev和Test之間的溝通手段。面對面溝通需要,詳細填寫Repro Steps也需要。
17. 寫新代碼前會把已知缺陷解決么?
MVM:要。每個人的缺陷不能超過10個或15個,否則必須先解決老的bug才能繼續寫新代碼。
18. 你們對缺陷的輕重緩急有事先的約定么?
MVM:必須有定義。Severity要分1、2、3,約定好:藍屏和Data Lost算Sev 1,Function Error算Sev 2,界面上的算Sev 3。但這種約定可以根據產品質量現狀適當進行調整。
19. 你們對意見不一的缺陷有三國會議么?
MVM:必須要有。要有一個明確的決策過程。這類似于CCB (Change Control Board)的概念。
20. 所有的缺陷都是由登記的人最后關閉的么?
MVM:Bug應該由Opener關閉。Dev不能私自關閉Bug。
21. 你們的程序員厭惡修改老的代碼么?
MVM:厭惡是正常的。解決方法是組織Code Review,單獨留出時間來。XP也是一個方法。
22. 你們項目組有Team Morale Activity么?
MVM:每個月都要搞一次,吃飯、唱歌、Outing、打球、開卡丁車等等,一定要有。不要剩這些錢。
23. 你們項目組有自己的Logo么?
MVM:要有自己的Logo。至少應該有自己的Codename。
24. 你們的員工有印有公司Logo的T-Shirt么?
MVM:要有。能增強歸屬感。當然,T-Shirt要做的好看一些,最好用80支的棉來做。別沒穿幾次就破破爛爛的。
25. 總經理至少每月參加次項目組會議
MVM:要的。要讓team member覺得高層關注這個項目。
26. 你們是給每個Dev開一個分支么?
MVM:反對。Branch的管理以及Merge的工作量太大,而且容易出錯。
27. 有人長期不Check-In代碼么?
MVM:不可以。對大部分項目來說,最多兩三天就應該Check-In。
28. 在Check-In代碼時都填寫注釋了么?
MVM:要寫的,至少一兩句話,比如“解決了Bug No.225”。如果往高處拔,這也算做“配置審計”的一部分。
29. 有沒有設定每天Check-In的最后期限?
MVM:要的,要明確Check-In Deadline。否則會Build Break。
30. 你們能把所有源碼一下子編譯成安裝文件嗎?
MVM:要的。這是每日編譯(Daily Build)的基礎。而且必須要能夠做成自動的。
31. 你們的項目組做每日編譯么?
MVM:當然要做。有三樣東西是軟件項目/產品開發必備的:1. bug management; 2. source control; 3. daily build。
32. 你們公司有沒有積累一個項目風險列表?
MVM:要。Risk Inventory。否則,下個項目開始的時候,又只能拍腦袋分析Risk了。
33. 設計越簡單越好
MVM:越簡單越好。設計時候多一句話,將來可能就帶來無窮無盡的煩惱。應該從一開始就勇敢的砍。這叫scope management。
34. 盡量利用現有的產品、技術、代碼
MVM:千萬別什么東西都自己Coding。BizTalk和Sharepoint就是最好的例子,有這兩個作為基礎,可以把起點提高很多。或者可以盡量多用現成的Control之類的。或者盡量用XML,而不是自己去Parse一個文本文件;盡量用RegExp,而不是自己從頭操作字符串,等等等等。這就是“軟件復用”的體現。
35. 你們會隔一段時間就停下來夯實代碼么?
MVM:要。最好一個月左右一次。傳言去年年初Windows組在Stevb的命令下停過一個月增強安全。Btw,“夯”這個字念“hang”,第一聲。
36. 你們的項目組每個人都寫Daily Report么?
MVM:要寫。五分鐘就夠了,寫10句話左右,告訴自己小組的人今天我干了什么。一則為了溝通,二則鞭策自己(要是游手好閑一天,自己都會不好意思寫的)。
37. 你們的項目經理會發出Weekly Report么?
MVM:要。也是為了溝通。內容包括目前進度,可能的風險,質量狀況,各種工作的進展等。
38. 你們項目組是否至少每周全體開會一次?
MVM:要。一定要開會。程序員討厭開會,但每個禮拜開會時間加起來至少應該有4小時。包括team meeting, spec review meeting, bug triage meeting。千萬別大家悶頭寫code。
39. 你們項目組的會議、討論都有記錄么?
MVM:會前發meeting request和agenda,會中有人負責主持和記錄,會后有人負責發meeting minutes,這都是effective meeting的要點。而且,每個會議都要形成agreements和action items。
40. 其他部門知道你們項目組在干什么么?
MVM:要發一些Newsflash給整個大組織。Show your team’s value。否則,當你坐在電梯里面,其他部門的人問:“你們在干嘛”,你回答“ABC項目”的時候,別人全然不知,那種感覺不太好。
41. 通過Email進行所有正式溝通
MVM:Email的好處是免得抵賴。但也要避免矯枉過正,最好的方法是先用電話和當面說,然后Email來確認。
42. 為項目組建立多個Mailing Group
MVM:如果在AD+Exchange里面,就建Distribution List。比如,我會建ABC Project Core Team,ABC Project Dev Team,ABC Project All Testers,ABC Project Extended Team等等。這樣發起Email來方便,而且能讓該收到email的人都收到、不該收到不被騷擾。
43. 每個人都知道哪里可以找到全部的文檔么?
MVM:應該每個人都知道。這叫做知識管理(Knowledge Management)。最方便的就是把文檔放在一個集中的File Share,更好的方法是用Sharepoint。
44. 你做決定、做變化時,告訴大家原因了么?
MVM:要告訴大家原因。Empower team member的手段之一是提供足夠的information,這是MSF一開篇的幾個原則之一。的確如此,tell me why是人之常情,tell me why了才能有understanding。中國人做事喜歡搞限制,限制信息,似乎能夠看到某一份文件的人就是有身份的人。大錯特錯。權威、權力,不在于是不是能access information/data,而在于是不是掌握資源。
45. Stay agile and expect change
MVM:要這樣。需求一定會變的,已經寫好的代碼一定會被要求修改的。做好心理準備,對change不要抗拒,而是expect change。
46. 你們有沒有專職的軟件測試人員?
MVM:要有專職測試。如果人手不夠,可以peer test,交換了測試。千萬別自己測試自己的。
47. 你們的測試有一份總的計劃來規定做什么和怎么做么?
MVM:這就是Test Plan。要不要做性能測試?要不要做Usability測試?什么時候開始測試性能?測試通過的標準是什么?用什么手段,自動的還是手動的?這些問題需要用Test Plan來回答。
48. 你是先寫Test Case然后再測試的么?
MVM:應該如此。應該先設計再編程、先test case再測試。當然,事情是靈活的。我有時候在做第一遍測試的同時補上test case。至于先test case再開發,我不喜歡,因為不習慣,太麻煩,至于別人推薦,那試試看也無妨。
49. 你是否會為各種輸入組合創建測試用例?
MVM:不要,不要搞邊界條件組合。當心組合爆炸。有很多test case工具能夠自動生成各種邊界條件的組合——但要想清楚,你是否有時間去運行那么多test case。
50. 你們的程序員能看到測試用例么?
MVM:要。讓Dev看到Test Case吧。我們都是為了同一個目的走到一起來的:提高質量。
51. 你們是否隨便抓一些人來做易用性測試?
MVM:要這么做。自己看自己寫的程序界面,怎么看都是順眼的。這叫做審美疲勞——臭的看久了也就不臭了,不方便的永久了也就習慣了。
52. 你對自動測試的期望正確么?
MVM:別期望太高。依我看,除了性能測試以外,還是暫時先忘掉“自動測試”吧,忘掉WinRunner和LoadRunner吧。對于國內的軟件測試的現狀來說,只能“矯枉必須過正”了。
53. 你們的性能測試是等所有功能都開發完才做的么?
MVM:不能這樣。性能測試不能被歸到所謂的“系統測試”階段。早測早改正,早死早升天。
54. 你注意到測試中的殺蟲劑效應了么?
MVM:蟲子有抗藥性,Bug也有。發現的新Bug越來越少是正常的。這時候,最好大家交換一下測試的area,或者用用看其他工具和手法,就又會發現一些新bug了。
55. 你們項目組中有人能說出產品的當前整體質量情況么?
MVM:要有。當老板問起這個產品目前質量如何,Test Lead/Manager應該負責回答。
56. 你們有單元測試么?
MVM:單元測試要有的。不過沒有單元測試也不是不可以,我做過沒有單元測試的項目,也做成功了——可能是僥幸,可能是大家都是熟手的關系。還是那句話,軟件工程是非常實踐、非常工程、非常靈活的一套方法,某些方法在某些情況下會比另一些方法好,反之亦然。
57. 你們的程序員是寫完代碼就扔過墻的么?
MVM:大忌。寫好一塊程序以后,即便不做單元測試,也應該自己先跑一跑。雖然有了專門的測試人員,做開發的人也不可以一點測試都不做。微軟還有Test Release Document的說法,程序太爛的話,測試有權踢回去。
58. 你們的程序中所有的函數都有輸入檢查么?
MVM:不要。雖然說做輸入檢查是write secure code的要點,但不要做太多的輸入檢查,有些內部函數之間的參數傳遞就不必檢查輸入了,省點功夫。同樣的道理,未必要給所有的函數都寫注釋。寫一部分主要的就夠了。
59. 產品有統一的錯誤處理機制和報錯界面么?
MVM:要有。最好能有統一的error message,然后每個error message都帶一個error number。這樣,用戶可以自己根據error number到user manual里面去看看錯誤的具體描述和可能原因,就像SQL Server的錯誤那樣。同樣,ASP.NET也要有統一的Exception處理。可以參考有關的Application Block。
60. 你們有統一的代碼書寫規范么?
MVM:要有。Code Convention很多,搞一份來發給大家就可以了。當然,要是有FxCop這種工具來檢查代碼就更好了。
61. 你們的每個人都了解項目的商業意義么?
MVM:要。這是Vision的意思。別把項目只當成工作。有時候要想著自己是在為中國某某行業的信息化作先驅者,或者時不時的告訴team member,這個項目能夠為某某某國家部門每年節省多少多少百萬的納稅人的錢,這樣就有動力了。平凡的事情也是可以有個崇高的目標的。
62. 產品各部分的界面和操作習慣一致么?
MVM:要這樣。要讓用戶覺得整個程序好像是一個人寫出來的那樣。
63. 有可以作為宣傳亮點的Cool Feature么?
MVM:要。這是增強團隊凝聚力、信心的。而且,“一俊遮百丑”,有亮點就可以掩蓋一些問題。這樣,對于客戶來說,會感覺產品從質量角度來說還是acceptable的。或者說,cool feature或者說亮點可以作為質量問題的一個事后彌補措施。
64. 盡可能縮短產品的啟動時間
MVM:要這樣。軟件啟動時間(Start-Up time)是客戶對性能好壞的第一印象。
65. 不要過于注重內在品質而忽視了第一眼的外在印象
MVM:程序員容易犯這個錯誤:太看重性能、穩定性、存儲效率,但忽視了外在感受。而高層經理、客戶正相反。這兩方面要兼顧,協調這些是PM的工作。
66. 你們根據詳細產品功能說明書做開發么?
MVM:要這樣。要有設計才能開發,這是必須的。設計文檔,應該說清楚這個產品會怎么運行,應該采取一些講故事的方法。設計的時候千萬別鉆細節,別鉆到數據庫、代碼等具體實現里面去,那些是后面的事情,一步步來不能著急。
67. 開始開發和測試之前每個人都仔細審閱功能設計么?
MVM:要做。Function Spec review是用來統一思想的。而且,review過以后形成了一致意見,將來再也沒有人可以說“你看,當初我就是反對這么設計的,現在吃苦頭了吧”
68. 所有人都始終想著The Whole Image么?
MVM:要這樣。項目里面每個人雖然都只是在制造一片葉子,但每個人都應該知道自己在制造的那片葉子所在的樹是怎么樣子的。我反對軟件藍領,反對過分的把軟件制造看成流水線、車間。參見第61條。
69. Dev工作的劃分是單純縱向或橫向的么?
MVM:不能單純的根據功能模塊分,或者單純根據表現層、中間層、數據庫層分。我推薦這么做:首先根據功能模塊分,然后每個“層”都有一個Owner來Review所有人的設計和代碼,保證consistency。
70. 你們的程序員寫程序設計說明文檔么?
MVM:要。不過我聽說微軟的程序員1999年以前也不寫。所以說,寫不寫也不是絕對的,偷懶有時候也是可以的。參見第56條。
71. 你在招人面試時讓他寫一段程序么?
MVM:要的。我最喜歡讓人做字符串和鏈表一類的題目。這種題目有很多循環、判斷、指針、遞歸等,既不偏向過于考算法,也不偏向過于考特定的API。
72. 你們有沒有技術交流講座?
MVM:要的。每一兩個禮拜搞一次內部的Tech Talk或者Chalk Talk吧。讓組員之間分享技術心得,這筆花錢送到外面去培訓劃算。
73. 你們的程序員都能專注于一件事情么?
MVM:要讓程序員專注一件事。例如說,一個部門有兩個項目和10個人,一種方法是讓10個人同時參加兩個項目,每個項目上每個人都花50%時間;另一種方法是5個人去項目A,5個人去項目B,每個人都100%在某一個項目上。我一定選后面一種。這個道理很多人都懂,但很多領導實踐起來就把屬下當成可以任意拆分的資源了。
74. 你們的程序員會夸大完成某項工作所需要的時間么?
MVM:會的,這是常見的,尤其會在項目后期夸大做某個change所需要的時間,以次來抵制change。解決的方法是坐下來慢慢磨,磨掉程序員的逆反心理,一起分析,并把估算時間的顆粒度變小。
75. 盡量不要用Virtual Heads
MVM:最好不要用Virtual Heads。Virtual heads意味著resource is not secure,shared resource會降低resource的工作效率,容易增加出錯的機會,會讓一心二用的人沒有太多時間去review spec、review design。一個dedicated的人,要強過兩個只能投入50%時間和精力的人。我是吃過虧的:7個part time的tester,發現的Bug和干的活,加起來還不如兩個full-time的。參見第73條。73條是針對程序員的,75條是針對Resource Manager的。
創建型模式
1、FACTORY —追MM少不了請吃飯了,麥當勞的雞翅和肯德基的雞翅都是MM愛吃的東西,雖然口味有所不同,但不管你帶MM去麥當勞或肯德基,只管向服務員說“來四個雞翅”就行了。麥當勞和肯德基就是生產雞翅的Factory
工廠模式:客戶類和工廠類分開。消費者任何時候需要某種產品,只需向工廠請求即可。消費者無須修改就可以接納新產品。缺點是當產品修改時,工廠類也要做相應的修改。如:如何創建及如何向客戶端提供。
2、BUILDER —MM最愛聽的就是“我愛你”這句話了,見到不同地方的MM,要能夠用她們的方言跟她說這句話哦,我有一個多種語言翻譯機,上面每種語言都有一個按鍵,見到MM我只要按對應的鍵,它就能夠用相應的語言說出“我愛你”這句話了,國外的MM也可以輕松搞掂,這就是我的“我愛你”builder。(這一定比美軍在伊拉克用的翻譯機好賣)
建造模式:將產品的內部表象和產品的生成過程分割開來,從而使一個建造過程生成具有不同的內部表象的產品對象。建造模式使得產品內部表象可以獨立的變化,客戶不必知道產品內部組成的細節。建造模式可以強制實行一種分步驟進行的建造過程。
3、FACTORY METHOD —請MM去麥當勞吃漢堡,不同的MM有不同的口味,要每個都記住是一件煩人的事情,我一般采用Factory Method模式,帶著MM到服務員那兒,說“要一個漢堡”,具體要什么樣的漢堡呢,讓MM直接跟服務員說就行了。
工廠方法模式:核心工廠類不再負責所有產品的創建,而是將具體創建的工作交給子類去做,成為一個抽象工廠角色,僅負責給出具體工廠類必須實現的接口,而不接觸哪一個產品類應當被實例化這種細節。
4、PROTOTYPE —跟MM用QQ聊天,一定要說些深情的話語了,我搜集了好多肉麻的情話,需要時只要copy出來放到QQ里面就行了,這就是我的情話prototype了。(100塊錢一份,你要不要)
原始模型模式:通過給出一個原型對象來指明所要創建的對象的類型,然后用復制這個原型對象的方法創建出更多同類型的對象。原始模型模式允許動態的增加或減少產品類,產品類不需要非得有任何事先確定的等級結構,原始模型模式適用于任何的等級結構。缺點是每一個類都必須配備一個克隆方法。
5、SINGLETON —俺有6個漂亮的老婆,她們的老公都是我,我就是我們家里的老公Sigleton,她們只要說道“老公”,都是指的同一個人,那就是我(剛才做了個夢啦,哪有這么好的事)
單例模式:單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例單例模式。單例模式只應在有真正的“單一實例”的需求時才可使用。
結構型模式
6、ADAPTER —在朋友聚會上碰到了一個美女Sarah,從香港來的,可我不會說粵語,她不會說普通話,只好求助于我的朋友kent了,他作為我和Sarah之間的Adapter,讓我和Sarah可以相互交談了(也不知道他會不會耍我)
適配器模式:把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口原因不匹配而無法一起工作的兩個類能夠一起工作。適配類可以根據參數返還一個合適的實例給客戶端。
7、BRIDGE —早上碰到MM,要說早上好,晚上碰到MM,要說晚上好;碰到MM穿了件新衣服,要說你的衣服好漂亮哦,碰到MM新做的發型,要說你的頭發好漂亮哦。不要問我“早上碰到MM新做了個發型怎么說”這種問題,自己用BRIDGE組合一下不就行了
橋梁模式:將抽象化與實現化脫耦,使得二者可以獨立的變化,也就是說將他們之間的強關聯變成弱關聯,也就是指在一個軟件系統的抽象化和實現化之間使用組合/聚合關系而不是繼承關系,從而使兩者可以獨立的變化。
8、COMPOSITE —Mary今天過生日。“我過生日,你要送我一件禮物。”“嗯,好吧,去商店,你自己挑。”“這件T恤挺漂亮,買,這條裙子好看,買,這個包也不錯,買。”“喂,買了三件了呀,我只答應送一件禮物的哦。”“什么呀,T恤加裙子加包包,正好配成一套呀,小姐,麻煩你包起來。”“……”,MM都會用Composite模式了,你會了沒有?
合成模式:合成模式將對象組織到樹結構中,可以用來描述整體與部分的關系。合成模式就是一個處理對象的樹結構的模式。合成模式把部分與整體的關系用樹結構表示出來。合成模式使得客戶端把一個個單獨的成分對象和由他們復合而成的合成對象同等看待。
9、DECORATOR —Mary過完輪到Sarly過生日,還是不要叫她自己挑了,不然這個月伙食費肯定玩完,拿出我去年在華山頂上照的照片,在背面寫上“最好的的禮物,就是愛你的Fita”,再到街上禮品店買了個像框(賣禮品的MM也很漂亮哦),再找隔壁搞美術設計的Mike設計了一個漂亮的盒子裝起來……,我們都是Decorator,最終都在修飾我這個人呀,怎么樣,看懂了嗎?
裝飾模式:裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關系的一個替代方案,提供比繼承更多的靈活性。動態給一個對象增加功能,這些功能可以再動態的撤消。增加由一些基本功能的排列組合而產生的非常大量的功能。
10、FAÇADE —我有一個專業的Nikon相機,我就喜歡自己手動調光圈、快門,這樣照出來的照片才專業,但MM可不懂這些,教了半天也不會。幸好相機有Facade設計模式,把相機調整到自動檔,只要對準目標按快門就行了,一切由相機自動調整,這樣MM也可以用這個相機給我拍張照片了。
門面模式:外部與一個子系統的通信必須通過一個統一的門面對象進行。門面模式提供一個高層次的接口,使得子系統更易于使用。每一個子系統只有一個門面類,而且此門面類只有一個實例,也就是說它是一個單例模式。但整個系統可以有多個門面類。
11、FLYWEIGHT —每天跟MM發短信,手指都累死了,最近買了個新手機,可以把一些常用的句子存在手機里,要用的時候,直接拿出來,在前面加上MM的名字就可以發送了,再不用一個字一個字敲了。共享的句子就是Flyweight,MM的名字就是提取出來的外部特征,根據上下文情況使用。
享元模式:FLYWEIGHT在拳擊比賽中指最輕量級。享元模式以共享的方式高效的支持大量的細粒度對象。享元模式能做到共享的關鍵是區分內蘊狀態和外蘊狀態。內蘊狀態存儲在享元內部,不會隨環境的改變而有所不同。外蘊狀態是隨環境的改變而改變的。外蘊狀態不能影響內蘊狀態,它們是相互獨立的。將可以共享的狀態和不可以共享的狀態從常規類中區分開來,將不可以共享的狀態從類里剔除出去。客戶端不可以直接創建被共享的對象,而應當使用一個工廠對象負責創建被共享的對象。享元模式大幅度的降低內存中對象的數量。
12、PROXY —跟MM在網上聊天,一開頭總是“hi,你好”,“你從哪兒來呀?”“你多大了?”“身高多少呀?”這些話,真煩人,寫個程序做為我的Proxy吧,凡是接收到這些話都設置好了自動的回答,接收到其他的話時再通知我回答,怎么樣,酷吧。
代理模式:代理模式給某一個對象提供一個代理對象,并由代理對象控制對源對象的引用。代理就是一個人或一個機構代表另一個人或者一個機構采取行動。某些情況下,客戶不想或者不能夠直接引用一個對象,代理對象可以在客戶和目標對象直接起到中介的作用。客戶端分辨不出代理主題對象與真實主題對象。代理模式可以并不知道真正的被代理對象,而僅僅持有一個被代理對象的接口,這時候代理對象不能夠創建被代理對象,被代理對象必須有系統的其他角色代為創建并傳入。
行為模式
13、CHAIN OF RESPONSIBLEITY —晚上去上英語課,為了好開溜坐到了最后一排,哇,前面坐了好幾個漂亮的MM哎,找張紙條,寫上“Hi,可以做我的女朋友嗎?如果不愿意請向前傳”,紙條就一個接一個的傳上去了,糟糕,傳到第一排的MM把紙條傳給老師了,聽說是個老處女呀,快跑!
責任鏈模式:在責任鏈模式中,很多對象由每一個對象對其下家的引用而接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。客戶并不知道鏈上的哪一個對象最終處理這個請求,系統可以在不影響客戶端的情況下動態的重新組織鏈和分配責任。處理者有兩個選擇:承擔責任或者把責任推給下家。一個請求可以最終不被任何接收端對象所接受。
14、COMMAND —俺有一個MM家里管得特別嚴,沒法見面,只好借助于她弟弟在我們倆之間傳送信息,她對我有什么指示,就寫一張紙條讓她弟弟帶給我。這不,她弟弟又傳送過來一個COMMAND,為了感謝他,我請他吃了碗雜醬面,哪知道他說:“我同時給我姐姐三個男朋友送COMMAND,就數你最小氣,才請我吃面。”,
命令模式:命令模式把一個請求或者操作封裝到一個對象中。命令模式把發出命令的責任和執行命令的責任分割開,委派給不同的對象。命令模式允許請求的一方和發送的一方獨立開來,使得請求的一方不必知道接收請求的一方的接口,更不必知道請求是怎么被接收,以及操作是否執行,何時被執行以及是怎么被執行的。系統支持命令的撤消。
15、INTERPRETER —俺有一個《泡MM真經》,上面有各種泡MM的攻略,比如說去吃西餐的步驟、去看電影的方法等等,跟MM約會時,只要做一個Interpreter,照著上面的腳本執行就可以了。
解釋器模式:給定一個語言后,解釋器模式可以定義出其文法的一種表示,并同時提供一個解釋器。客戶端可以使用這個解釋器來解釋這個語言中的句子。解釋器模式將描述怎樣在有了一個簡單的文法后,使用模式設計解釋這些語句。在解釋器模式里面提到的語言是指任何解釋器對象能夠解釋的任何組合。在解釋器模式中需要定義一個代表文法的命令類的等級結構,也就是一系列的組合規則。每一個命令對象都有一個解釋方法,代表對命令對象的解釋。命令對象的等級結構中的對象的任何排列組合都是一個語言。
16、ITERATOR —我愛上了Mary,不顧一切的向她求婚。
Mary:“想要我跟你結婚,得答應我的條件”
我:“什么條件我都答應,你說吧”
Mary:“我看上了那個一克拉的鉆石”
我:“我買,我買,還有嗎?”
Mary:“我看上了湖邊的那棟別墅”
我:“我買,我買,還有嗎?”
Mary:“你的小弟弟必須要有50cm長”
我腦袋嗡的一聲,坐在椅子上,一咬牙:“我剪,我剪,還有嗎?”
……
迭代子模式:迭代子模式可以順序訪問一個聚集中的元素而不必暴露聚集的內部表象。多個對象聚在一起形成的總體稱之為聚集,聚集對象是能夠包容一組對象的容器對象。迭代子模式將迭代邏輯封裝到一個獨立的子對象中,從而與聚集本身隔開。迭代子模式簡化了聚集的界面。每一個聚集對象都可以有一個或一個以上的迭代子對象,每一個迭代子的迭代狀態可以是彼此獨立的。迭代算法可以獨立于聚集角色變化。
17、MEDIATOR —四個MM打麻將,相互之間誰應該給誰多少錢算不清楚了,幸虧當時我在旁邊,按照各自的籌碼數算錢,賺了錢的從我這里拿,賠了錢的也付給我,一切就OK啦,俺得到了四個MM的電話。
調停者模式:調停者模式包裝了一系列對象相互作用的方式,使得這些對象不必相互明顯作用。從而使他們可以松散偶合。當某些對象之間的作用發生改變時,不會立即影響其他的一些對象之間的作用。保證這些作用可以彼此獨立的變化。調停者模式將多對多的相互作用轉化為一對多的相互作用。調停者模式將對象的行為和協作抽象化,把對象在小尺度的行為上與其他對象的相互作用分開處理。
18、MEMENTO —同時跟幾個MM聊天時,一定要記清楚剛才跟MM說了些什么話,不然MM發現了會不高興的哦,幸虧我有個備忘錄,剛才與哪個MM說了什么話我都拷貝一份放到備忘錄里面保存,這樣可以隨時察看以前的記錄啦。
備忘錄模式:備忘錄對象是一個用來存儲另外一個對象內部狀態的快照的對象。備忘錄模式的用意是在不破壞封裝的條件下,將一個對象的狀態捉住,并外部化,存儲起來,從而可以在將來合適的時候把這個對象還原到存儲起來的狀態。
19、OBSERVER —想知道咱們公司最新MM情報嗎?加入公司的MM情報郵件組就行了,tom負責搜集情報,他發現的新情報不用一個一個通知我們,直接發布給郵件組,我們作為訂閱者(觀察者)就可以及時收到情報啦
觀察者模式:觀察者模式定義了一種一隊多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使他們能夠自動更新自己。
20、STATE —跟MM交往時,一定要注意她的狀態哦,在不同的狀態時她的行為會有不同,比如你約她今天晚上去看電影,對你沒興趣的MM就會說“有事情啦”,對你不討厭但還沒喜歡上的MM就會說“好啊,不過可以帶上我同事么?”,已經喜歡上你的MM就會說“幾點鐘?看完電影再去泡吧怎么樣?”,當然你看電影過程中表現良好的話,也可以把MM的狀態從不討厭不喜歡變成喜歡哦。
狀態模式:狀態模式允許一個對象在其內部狀態改變的時候改變行為。這個對象看上去象是改變了它的類一樣。狀態模式把所研究的對象的行為包裝在不同的狀態對象里,每一個狀態對象都屬于一個抽象狀態類的一個子類。狀態模式的意圖是讓一個對象在其內部狀態改變的時候,其行為也隨之改變。狀態模式需要對每一個系統可能取得的狀態創立一個狀態類的子類。當系統的狀態變化時,系統便改變所選的子類。
21、STRATEGY —跟不同類型的MM約會,要用不同的策略,有的請電影比較好,有的則去吃小吃效果不錯,有的去海邊浪漫最合適,單目的都是為了得到MM的芳心,我的追MM錦囊中有好多Strategy哦。
策略模式:策略模式針對一組算法,將每一個算法封裝到具有共同接口的獨立的類中,從而使得它們可以相互替換。策略模式使得算法可以在不影響到客戶端的情況下發生變化。策略模式把行為和環境分開。環境類負責維持和查詢行為類,各種算法在具體的策略類中提供。由于算法和環境獨立開來,算法的增減,修改都不會影響到環境和客戶端。
22、TEMPLATE METHOD ——看過《如何說服女生上床》這部經典文章嗎?女生從認識到上床的不變的步驟分為巧遇、打破僵局、展開追求、接吻、前戲、動手、愛撫、進去八大步驟(Template method),但每個步驟針對不同的情況,都有不一樣的做法,這就要看你隨機應變啦(具體實現);
模板方法模式:模板方法模式準備一個抽象類,將部分邏輯以具體方法以及具體構造子的形式實現,然后聲明一些抽象方法來迫使子類實現剩余的邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩余的邏輯有不同的實現。先制定一個頂級邏輯框架,而將邏輯的細節留給具體的子類去實現。
23、VISITOR —情人節到了,要給每個MM送一束鮮花和一張卡片,可是每個MM送的花都要針對她個人的特點,每張卡片也要根據個人的特點來挑,我一個人哪搞得清楚,還是找花店老板和禮品店老板做一下Visitor,讓花店老板根據MM的特點選一束花,讓禮品店老板也根據每個人特點選一張卡,這樣就輕松多了;
訪問者模式:訪問者模式的目的是封裝一些施加于某種數據結構元素之上的操作。一旦這些操作需要修改的話,接受這個操作的數據結構可以保持不變。訪問者模式適用于數據結構相對未定的系統,它把數據結構和作用于結構上的操作之間的耦合解脫開,使得操作集合可以相對自由的演化。訪問者模式使得增加新的操作變的很容易,就是增加一個新的訪問者類。訪問者模式將有關的行為集中到一個訪問者對象中,而不是分散到一個個的節點類中。當使用訪問者模式時,要將盡可能多的對象瀏覽邏輯放在訪問者類中,而不是放到它的子類中。訪問者模式可以跨過幾個類的等級結構訪問屬于不同的等級結構的成員類。
正則表達式是使用一套特殊符號模式做為表達格式的字符串,主要用處是描述和解析文本。許多程序員(甚至一些不錯的高手)都無視(也不用)正則表達式,我認為這是一個恥辱,因為在解決很多問題的時候,正則表達式常常讓我們有得心應手的感覺。一旦你掌握了,就會發現它能解決無數真實世界的問題。
正則表達式的工作方式就象Windows或者*nix系統里面的文件名替代符 - 你可以使用特定的*或者?來指定一系列文件。但是使用正則表達式的特殊字符或者metacharacters(元字符)來表示這類事情會更準確。
正則表達式把大多數字符當作直接字符,就好像正則表達式 mike,將只會匹配按順序的字符序列m - i - k - e。與此同時正則表達式使用一個采用元字符的擴展集合,可以表示非常復雜的文字匹配。
認識元字符: ^[](){}.*?\|+$ 以及在某些時候出現的 -
我知道它們看上去很恐怖,但是一旦你了解它們就會知道它們是很可愛的符號。
行定位點: ‘^’ 和 ‘$’
‘^’ (讀成:caret) 和 ‘$’ (讀成:dollar) 這兩個元字符分別代表一行文字的開始和結束。就象我前面舉的例子,正則表達式mike會匹配字符序列m - i - k – e,可是它會匹配一行中的所有位置 (比如,它會匹配 “I’m mike”或者 “carmike”)。 ‘^’字符被用來限定匹配行的開始,因此^mike 將只會尋找以mike開始的行。同樣,表達式mike$將只會尋找m - i - k - e在一行末尾的(當然還是會匹配 ‘carmike’)。
如果我們聯合使用這兩個行定位點字符,我們可以搜索在多行文字中尋找包含的特殊字符串序列。比如:表達式 ^mike$ 將只會匹配占有單獨一行的單詞mike,一個字不多一個字不少。同樣,表達式 ^$ 對于發現空行(一行開始就是本行結束的那種)很有用。
字符分類: ‘[]’
一對方括號被稱為一個字符分類, 你可以用來匹配任何一個或多個字符。假設你想匹配單詞 ‘gray’,同時也想找一下被拼寫成 ‘grey’的單詞。 使用一個字符分類將允許你匹配這兩者 -- 正則表達式 gr[ea]y 被解讀成 “匹配這樣的字符串 - 一個g, 跟著是r, 跟著或者是一個e或者是一個a, 跟著一個y”。
如果你用 [^ ... ] 代替 [ ... ], 這個分類將匹配后面列出來字符以外的任何字符。首字符 ^ 表示“否定"列表 - 不同于你列出所有希望包含的字符,你是去列出所有不想包含的字符。 注意在這里使用的^ (caret) 字符,它在字符分類方式之外使用表示另外的意思 - 用來匹配文字行的開始(見文章前面部分)。
字符分類中的元字符: ‘-’
在一個字符分類中,字符分類中的元字符 ‘-’ (dash) 用來指出一個字符范圍。考慮字符分類 [01234567890abcdefABCDEF],采用’-’的話我們可以這樣寫[0-9a-fA-F],方便了不少吧。有一點大家要注意的,這個’-’符號只有用一個字符分類中才被認為是元字符,在其他位置,它只是簡單的匹配普通的’-’字符,沒有任何其他意義。
但是且慢,我看到有人舉手質疑。假如在一個字符分類里面,’-’字符做為第一個字符出現的時候,會把它認為成什么呢?比如[-A-F],問題很好,注意:這是一個例外,如果在字符分類中,’-’字符是第一個出現的字符,那我們把它當作普通字符而不是元字符處理(因為實際上它不可能表示一個字符范圍,范圍需要有開始和結束字符),這個時候它只會匹配一個普通的’-’字符。引申開來,我們再說一個例外:S’?’和’.’在大多數情況下都是正則表達式的元字符,但是有個例外是在字符分類中,當它們在字符分類中的時候(比如在:[-0-9.?],它們只是代表一個普通字符,唯一的特殊字符(元字符)是0和9中間的’-’)。
用一個句點: ‘.’匹配任何字符
‘.’ 元字符(一般讀成a dot 或者point)是一種匹配任何字符的寫法。在你想在一個字符串的指定位置匹配一個任意字符的時候,它顯得非常可愛。再強調一遍,在字符分類中,’.’就不是一個元字符了。到現在為止,你開始看出一些門道來了吧?哪些是元字符哪些不是元字符在字符分類里面和外面是不一樣的。
選擇性元字符: ‘|’
‘|’ 元字符(讀成pipe)的意思是“or”。它允許你把多個表達式合成到一個表達式,然后匹配里面任何單個表達式的結果。這些子表達式被稱為備選項。
例如:Mike 和 Michael 是兩個獨立的正則表達式,但是Mike|Michael 這樣來寫的話,這個正則表達式匹配任意一個單詞。
圓括號在這里可以被用來限制備選的范圍。我們可以使用圓括號來達到和上面這個正則表達式同樣的目的,同時縮短它長度,正則表達式Mi(ke|chael) 同樣匹配Mike或者Michael。當然,在實際程序中我還是會用第一種寫法,雖然長了一點,可是更容易理解,因此也更容易維護。
匹配可選項: ‘?’
‘?’ 元字符(讀成:question mark)意味著可選。它放在正則表達式的某個位置的一個字符后面,這個字符允許在匹配結果中出現,也可以不出現。當然,我們可以肯定的是:這個’?’字符只能跟在一個普通字符而不是元字符后面。
如果我想匹配英式或者美式拼法的單詞‘flavor’ ,我會用正則表達式flavou?r,它被解讀成:“匹配一個字符串:f,跟著一個l,跟著一個a,跟著一個v,跟著一個o,跟著一個可選的u,跟著一個r”。
數量符號: ‘+’ and ‘*’
象’?’字符一樣,‘+’ (讀成plus)和‘*’(讀成star)元字符影響前導字符(就是在這個符號前面的字符)可以在匹配字符串中出現的數量 (使用前面說的‘?’的話,相當于前導字符可以出現0次或一次)。元字符‘+’ 匹配前面出現的項目一次或更多次,而‘*’ 則表示匹配任何次,包括0次。
如果我想通過在一場足球比賽中解說員說’goal’的聲音次數來統計比分的話,我應該用正則表達式go+al, 它可以匹配‘goal’,也可以匹配一些激情主播的‘gooooooooooooooooal’ (但肯定不會是 ‘gal’)。
前面的三個元字符:’?’、’+’、’*’一般又叫做計量符。因為它們影響前面項目的數量。
數量范圍: ‘{}’
‘{最小, 最大}’ 這個元字符序列允許你指定特定項目可以被匹配的最少和最大次數。例如go{1,5}al 可以用來限制我們上面的例子,只匹配1到5次o。同樣的{0,1} 其實就等同于一個’?’元字符。
轉義字符: ‘\’
‘\’ 元字符(讀成:backslash)被用來轉換指定的元字符的含義,以便于你可以把它們當成普通字符來匹配。例如,你打算匹配字符’?’或者’\’,你就可以在它們前面加上一個’\’字符,這樣它們就被轉換成普通字符的含義,就好像這樣寫:‘\?’ or ‘\\’.
如果在一個非元字符前面使用’\’的話,那么根據你使用正則表達式的語言不同,會有不同的含義,必須參閱相應的手冊。比較普遍采用的是perl兼容的正則表達式(PCREs),你可以在這里查看the perldoc page for perl regular expressions. PCREs用得非常普遍,在PHP、 Ruby和ECMAScript/Javascript還有很多語言中都可以使用。
用圓括號匹配: ‘()’
大部分正則表達式工具允許你用圓括號設定一個特定的表達式子集。比如,我們可以用一個正則表達式http://([^/]+)去匹配一個URL的域名部分。下面讓我們把這個正則表達式分解開,看看它是如何工作的。
這個表達式的起始部分非常直白:它必須匹配“h - t - t - p - : - / - /”這樣的字符序列。這個初始序列之后就是圓括號了,它被用來捕捉符合它們包圍的子表達式的字符。在現在的例子中,子表達式是‘[^/]+’,用上面學到的知識,我們知道它實際上是匹配除了‘/’字符以外的任何字符一次到多次。對于一個像是 http://immike.net/blog/Some-blog-post的URL,‘immike.net’ 將會被這個圓括號里面的表達式所匹配。
首先要在在文件首定義菜單項:
#define ID_MENU_EDIT 5001
#define ID_MENU_DELETE 5002
然后添加對話框的WM_CONTEXTMENU消息函數,函數內容為:
CMenu menuPopup;
if(menuPopup.CreatePopupMenu())
{
menuPopup.AppendMenu(MF_STRING,ID_MENU_EDIT,"修改(&E)");
menuPopup.AppendMenu(MF_STRING,ID_MENU_DELETE,"刪除(&D)");
menuPopup.TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,this);
}
然后定義菜單相應函數,
1,在頭文件中添加函數定義語句:
// Generated message map functions
//{{AFX_MSG(CAdo2Dlg)
virtual BOOL OnInitDialog();
afx_msg void onInfoEdit(); // 這個是編輯菜單的響應函數
afx_msg void onInfoDelete(); //這個是刪除菜單的響應函數
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnButton1();
afx_msg void OnButton2();
afx_msg void OnRdblclkList1(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnDblclkList1(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
2,在cpp文件中添加函數體:
void CAdo2Dlg::OnInfoEdit()
{
AfxMessageBox("edit");
}

void CAdo2Dlg::OnInfoDelete()
{
AfxMessageBox("delete");
}
3,然后在cpp文件中添加影射:
BEGIN_MESSAGE_MAP(CAdo2Dlg, CDialog)
//{{AFX_MSG_MAP(CAdo2Dlg)
ON_COMMAND(ID_MENU_EDIT, OnInfoEdit)
ON_COMMAND(ID_MENU_DELETE, OnInfoDelete)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
所有的工作完成了!
一,MFC擴展DLL
創建:
1,新建一個MFC擴展DLL ,名字為dll5,添加頭文件,名為dll5
2,頭文件中加入:
extern __declspec(dllexport) CString concatA(CString x,CString y);
3,在cpp文件中加入:
extern __declspec(dllexport) CString concatA(CString x,CString y)
{
return x + y;
}
4,在cpp文件中加入:
#include "dll5.h"
5,編譯,生成dll
使用:
1,新建一個單文檔應用程序,名為Usedll5
2,將剛才生成的dll5.lib文件和dll5.h文件拷貝到當前應用程序路徑下,
將dll5.dll 文件拷貝到 當前應用程序下的debug下
3,在當前應用程序中用到該dll5的導出方法(concatA)的文件(或類)上添加如下語句:
#include "dll5.h"
假設將其加到 Usedll5View.cpp中。
4,在Usedll5View類中建立消息映射入口,在消息函數中添加如下語句:
CString a=concatA("中國北車集團","長春軌道客車股份有限公司");
MessageBox(a);
5,在 工程/設置/連接/對象庫/模塊 中加入:dll5.lib
6,編譯執行該應用程序,并觸發該消息,則輸出:
中國北車集團長春軌道客車股份有限公司
之后只要定義不更改,函數體無論怎么更改。我們只要將編譯好的dll拷貝過來即可。如果定義有了修改,則需要將h文件和lib 文件拷貝過來,并需要重新編譯。
二,動態鏈接庫使用共享MFC DLL
創建:
1,新建一個 DLL(選 動態鏈接庫使用共享MFC DLL)
2,頭文件中加入:
_declspec(dllexport) CString WINAPI concatA(CString x,CString y);
3,在cpp文件末尾加入:
_declspec(dllexport) CString WINAPI concatA(CString x,CString y)
{
return x + y;
}
4,編譯,生成dll
使用:
1,新建一個單文檔應用程序,名為Usedll8
2,將剛才生成的dll8.lib文件拷貝到當前應用程序路徑下,
將dll8.dll 文件拷貝到 c:\winnt\system32下
3,在當前應用程序中用到該dll5的導出方法(concatA)的 類的頭文件上添加如下語句:
extern CString WINAPI concatA(CString x,CString y);
假設將其加到 Usedll8View.h中。
4,在Usedll8View類中建立消息映射入口,在消息函數中添加如下語句:
CString a=concatA("中國北車集團","長春軌道客車股份有限公司");
MessageBox(a);
5,在 工程/設置/連接/對象庫/模塊 中加入:dll8.lib
6,編譯執行該應用程序,并觸發該消息,則輸出:
中國北車集團長春軌道客車股份有限公司
現在有三種比較常用微軟Source Control 工具:VSS 6.0d,VSS 2005,TFS(Team Foundation Server),做個簡單的比較。
整體功能上,前兩者屬于小型的管理工具,一般在15個人以下的開發團隊中使用比較合適,適合小型的公司或者團隊使用。Team Foundation Server 是 Visual Studio Team System 產品線發布的最后一個組件。它為團隊項目開發提供了高度集成化的工作平臺,可以在大規模的團隊開發中使用,不僅可以提供源代碼管理功能,還可以提供其他的功能.
1. VSS 6.0d是比較早期的工具,最大支持2GB的數據空間,但是不能支持遠程訪問。
2. VSS 2005是可以支持最大4GB的數據空間,可以提供遠程訪問,支持多人簽出。
3.TFS是企業級的,支持50個人以上的團隊,但是要求必須在Windows 2003 sp1,Sql Server 2005的基礎之上,強烈建議干凈的安裝環境。
聲明: 1.本人男 2.下面的事情是真實的事情,前天下午四點多發生 -------------------------------------------------------------------- 前天下午, 我接到移動的10086打電話回訪, 我都"喂"好幾聲了,移動的那位MM竟然說“你是李小姐嗎。。。”, 當時我差點沒暈倒,崩潰掉... 估計那位MM 也超汗! |