一刷 ???? 本(2002/??/??)  

《多型與虛擬》2e

侯捷 著

pic2.jpg (62106 bytes)

 


□適合對象:略具 C++ 語法基礎,欲一步定位到物件導向精髓者。
                        初/中/高   階讀者均可從不同的章節中得益。
□內容特色:涵蓋●語法語意●物件模型●專家經驗●框架基礎建設●設計樣式
□製作特色:含 index,網片輸出,雙色,精裝白書籤絲條。


開放檔案如下:(請注意,預覽品與正式出版品之間可能會有微小差異)

檔名 內容 大小 bytes
pic2-chap06-020222.pdf

第6章:
application framework — OO技術極致展現
程式範例為 mfclite3,如下。

不需密碼即可開啟。
檔案未含書籤(目錄連結)

chap6
(application
framework)
2,634,222
mfclite3-020222.zip

MFCLite 3,一個具體而微(仿 MFC)的 application framework

簡體版讀者請注意:目前所有源碼檔案內之中文註釋皆為繁體,使用臺灣術語(if any)。簡體版出版時,會請轉譯者轉為大陸術語。屆時會提供另一份檔案供下載。

HTML 版開放,請點選 top 視窗之人頭 :)

mfclite 3 1,079,964

如欲下載,請將滑鼠移至上述 hyperlink,按右鍵,再選【另存目標...】即可。
Right-click on the link, and choose "Save Link As...".

mfclite3.zip 解壓縮後,內含一套 MFCLite 3.0 源碼,以及 VC, CB, GCC 三個編譯器的makefile,
以及VC, CB, GCC 三個版本的可執行檔 mfclappm.exe, mfclappb.exe, mfclappg.exe,以及一個VC6 IDE編譯出來的可執行檔 mfcl30.exe。
console 編譯環境設定請參考《1999/04/08 Console 模式中使用C/C++ 編譯器》,
VC6 整合(集成)環境之設定請參考 6.1.2 節
模組切割與檔案組織請參考 6.1.2 節
執行結果請參考 6.10 節


汗如雨下

寄件者: xioax
收件者: 侯 捷
傳送日期: 2002年1月22日 AM 09:36
主旨: 關於MFCLite的一些問題﹗

候老師﹕
您好﹐我是一名剛從浙大畢業的學生﹐也是您的忠實讀者。最近我在閱讀您的MFCLite﹐發現了些問題﹐列舉如下(如有不對的地方﹐還請老師指點)﹕

1、對象持久性。由於兩年前我曾仔細研究過Turbo Vision的持久性﹐對這個問題比較熟悉。所以一開始我就發現了您的代碼中所存在的問題﹐下面是我的測試程序(部份)﹐還有輸出文件的內容﹕
...
CSquare *psqr1 = new CSquare () ;
CSquare *psqr2 = psqr1 ; // psqr1 and psqr2 point to same object
assert (psqr1 == psqr2) ;
{
  CFile write ("test.tmp", CFile::modeWrite) ;
  CArchive store (&write, CArchive::store) ;
  store << psqr1 << psqr2 ;
}
delete psqr1 ;
psqr1 = psqr2 = NULL ;
{
  CFile read ("test.tmp", CFile::modeRead) ;
  CArchive load (&read, CArchive::load) ;
  load >> psqr1 >> psqr2 ;
}
assert (psqr1 == psqr2) ; // here is an assert failure, not point to same object!
...
00 00 07 00 43 53 71 75 61 72 65 00 00 00 00 00 ....CSquare.....
00 00 00 00 00 00 00 00 00 07 00 43 53 71 75 61 ...........CSqua
72 65 00 00 00 00 00 00 00 00 00 00 00 00 re..............

psqr1和psqr2指向同一對象﹐寫入文件時應該只有一份﹐但是在您的實現中卻寫了兩次﹗導致了讀出時﹐psqr1和psqr2指向了不同的對象。顯然這是不正確的。我覺得對於C++ 對象持久性而言﹐最重要的問題﹕一個是如何保存相關的類信息﹐另一個就是如何解決上述問題﹗在您的兩本著作《多形與虛擬》、《深入淺出MFC》中對前者都有很精闢的論述﹐唯獨後者一點也沒有提及﹐不能不說是一個很大的瑕疵。對於如何解決這個問題也不是很困難﹐只要先實現CMapPtrtoPtr和CPtrArray﹐在寫入時先查map如果已寫過﹐就只把輸出序號寫入文件﹐如果沒有就把對象的地址和輸出序號插入map﹐再把數據寫入文件。讀出時﹐遇到第二種情況(即文件中有實際數據,而非只是序號)﹐就先創建一個對象把數據讀出﹐接著再把新建對象的
地址加到數組array尾端﹐遇到第一種情況﹐就以輸出序號為索引直接從數組中得到對象(由於寫入和讀出的順序一樣﹐僅用輸出序號就可以完全解決問題)。

2、CPtrList的實現有內存泄漏﹐下面是相關代碼和我的修改(紅色)﹕
...
我覺得就MFCLite的目的而言﹐完全沒有必要象MFC那樣實現一些比較高級的內存管理技巧。因為一來這些內存管理與MFC的應用程序架構完全沒有關係﹐二來加大了讀者的閱讀難度﹐三來加大了MFCLite的出錯機會。如果CPtrList使用CObList的實現方法就沒有這麼多問題﹗
另外﹐您說在下面的代碼中﹐刪除CPtrList中的元素程序會崩潰﹐不知是不是上面的原因﹖

void CMultiDocTemplate::RemoveDocument(CDocument* pDoc)
{
CDocTemplate::RemoveDocument(pDoc);
// m_docList.RemoveAt(m_docList.Find(pDoc)); // 把 doc 節點移走 <- 有問題. crash!
}

3、CDocument::OnOpenDocument和CDocument::OnSaveDocument有內存泄漏﹕

BOOL CDocument::OnOpenDocument(const string& strFileName)
{
  CFile* pFile = new CFile(strFileName.c_str(), CFile::modeRead);
  CArchive loadArchive(pFile, CArchive::load);
  Serialize(loadArchive);
  loadArchive.Close();
  pFile->Close();
  // should delete pFile
  delete pFile ;
  return TRUE;
}
BOOL CDocument::OnSaveDocument(const string& strFileName)
{
  CFile* pFile = new CFile(strFileName.c_str(), CFile::modeWrite);
  CArchive saveArchive(pFile, CArchive::store);
  Serialize(saveArchive);
  saveArchive.Close();
  pFile->Close();
  // should delete pFile
  delete pFile ;
  return TRUE;
}

4、我發現CObject的析構函數不是虛擬的。我想一定是老師的一時疏忽。:-)面對幾千行的
代碼﹐誰敢保證沒錯呢﹖﹗

5、MFCLite對新建文件、打開文件和保存文件模擬得很好﹔但是對關閉窗口﹐進而造成文檔的摧毀卻模擬得不夠。其實在C++中由於析構錯誤造成的內存泄漏﹐往往是最常見﹐也是最難
排除的Bug。希望在MFCLite的新版本能看到相應的模擬﹗


雖然MFCLite存在一些不足﹐但是瑕不掩玉﹐我覺得它對C++和MFC的學習都有非常非常大的幫助。老師的每一本書(無論是譯作還是著作)都是相關領域的精品﹐凡是在市面上見得到的我都買了下來﹐給我帶來的極大的幫助。現在我正在急切地等待老師的《STL原碼剖析》、《泛型設計與STL》、《標準C++程式庫》等書籍。希望老師注意身體﹐給我們寫出更好的作品﹗



Hi, xioax, 你好:

以下回答你的一些疑問與建議。

> 1、對象持久性。由於兩年前我曾仔細研究過Turbo Vision的持久性﹐對這個問題比較熟悉。所以一開始我就發現了您的代碼中所存在的問題﹐下面是我的測試程序(部份)﹐還有輸出文件的內容﹕…

> 我覺得對於C++ 對象持久性而言﹐最重要的問題﹕一個是如何保存相關的類信息﹐另一個就是如何解決上述問題﹗在您的兩本著作《多形與虛擬》、《深入淺出MFC》中對前者都有很精闢的論述﹐唯獨後者一點也沒有提及﹐不能不說是一個很大的瑕疵。對於如何解決這個問題也不是很困難﹐只要先實現CMapPtrtoPtr和CPtrArray﹐在寫入時先查map如果已寫過﹐就把輸出序號寫入﹐如果沒有就把對象的地址和輸出序號插入map﹐再把數據寫入文件。讀出時﹐遇到第二種情況﹐就先創建一個對象把數據讀出﹐接著再把新建對象的地址加到數組尾端﹐遇到第一種情況﹐就以輸出序號為索引直接從數組中得到對象(由於寫入和讀出的順序一樣﹐僅用輸出序號就可以完全解決問題)。


●侯捷回覆:你所指出的,對我是當頭棒喝。的確,模擬Persistence時我很少考慮你所說的(alias)情況。針對你的提議,我已修正 MFCLite3並開放於本網頁

MFC(Lite) 不但考慮了你所說的那種(alias)情況,還對「隸屬相同class」的不同objects的文件讀寫做了優化考量。對於已儲存 class information(例如class name和 schema no.)的 class而言,再儲存一次是一種浪費 — 浪費空間也浪費時間。因此,MFC的 CArchive利用CMapPtrToPtr做為cache,不但用來安置CObject*,也用來安置CRuntimeClass*。讀取文件時,道理相同,使用CPtrArray,不但安置CObject*也安置CRuntimeClass*。我不打算在MFCLite中再多加上CMapPtrToPtr和CPtrArray的實作(那恐怕又多出1000行代碼,而且CMapPtrToPtr是以 hash table完成,那就使閱讀的門檻又高了些),我已在新版的 MFCLite 3.0 中以C++ 標準程式庫的 map和 vector 取而代之。

MFCLite的閱讀門檻愈壘愈高,樂了諸位功底深厚的人,可難為了其他功力尚淺的讀者呀。對我而言,取捨成了難事。

我保留並擴大了你的測試,放在 MFCLite Application (mfclapp.cpp) 的 CMyWinApp::InitInstance()中。程式一執行就會在螢幕上顯示這段測試結果並顯示 StoreMap 和  LoadArray,然後才開始一般正常的執行流程。你對persistence 的這段 hint 帶給我莫大樂趣,迫使我完成《深入淺出MFC》第 8 章關於 document format 中的 tags(圖8-10a, 圖8-10b內的 FFFF, 8001, 8003...以及未出現於該圖的 tags 如 0002, 0005...)的深刻體會。


> 2、CPtrList的實現有內存泄漏﹐下面是相關代碼和我的修改(紅色)﹕…

●侯捷回覆:修改完畢。我沒有採用你的方式,而是採用MFC的方式。當初實作MFCLite時,為求簡化略去了一些東西,不想造成了memory leak 而不自覺。現補上。我的額頭開始冒汗了。

> 我覺得就MFCLite的目的而言﹐完全沒有必要象MFC那樣實現一些比較高級的內存管理技巧。因為一來這些內存管理與MFC的應用程序架構完全沒有關係﹐二來加大了讀者的閱讀難度﹐三來加大了MFCLite的出錯機會。如果CPtrList使用CObList的實現方法就沒有這麼多問題﹗

●侯捷回覆:CPtrList的精巧設計,與application framework之間非常獨立。雖然它比較複雜,應該不至於混亂讀者對application framework的學習。保留這麼複雜的list實作,有兩個用意,(1) CPtrList是MFC內部自我維護管理時,大量運用的一個data structure,因此對於效率(空間和時間)都很要求 (2)既然其十分獨立,再複雜也不至於混亂讀者對application framework的學習,那麼展示一下這種不錯的設計,滿好的。

> 另外﹐您說在下面的代碼中﹐刪除CPtrList中的元素程序會崩潰﹐不知是不是上面的原因﹖

●侯捷回覆:不是。原因已查出,乃因我把所有的documents的 delete動作放錯位置。這些動作在 MFC 之中應該由 CFrameWnd::OnClose()觸發(目前MFCLite尚未實作之),我卻把它們放在 CMultiDocTemplate::~CMultiDocTemplate()。下面是 MFCLite 的 CallStack:

CMultiDocTemplate::RemoveDocument() // (B)
CDocument::~CDocument()
CMultiDocTemplate::~CMultiDocTemplate() // (A)
CDocManager::~CDocManager()
CWinApp::~CWinApp()

(A) 已針對 pDoc 呼叫 m_docList.RemoveAt(),
(B) 又針對 pDoc 呼叫 m_docList.RemoveAt(),但 pDoc 當時已經不在 m_docList 中了,Find() 傳回 NULL,RemoveAt(NULL) 當然就掛了。

治本之道是模擬 MFC,開發 window close system(也就是你的第5個問題),治標之道則是在 CPtrList::RemoveAt() 一開始增加一行,判斷刪除位置是否為 NULL,如是則立刻回返。

> 3、CDocument::OnOpenDocument和CDocument::OnSaveDocument有內存泄漏﹕

●侯捷回覆:應該說是資源洩漏(resource leak)。已全部補妥。我的身上開始冒汗了。修補這個問題的同時,一併修補了 CFile::~CFile() 和 CFile::Close() 內的安全檢驗工作。

> 4、我發現CObject的析構函數不是虛擬的。我想一定是老師的一時疏忽。:-)面對幾千行的代碼﹐誰敢保證沒錯呢﹖﹗

●侯捷回覆:全部補妥。我汗流夾背了。

> 5、MFCLite對新建文件、打開文件和保存文件模擬得很好﹔但是對關閉窗口﹐進而造成文檔的摧毀卻模擬得不夠。其實在C++中由於析構錯誤造成的內存泄漏﹐往往是最常見﹐也是最難排除的Bug。希望在MFCLite的新版本能看到相應的模擬﹗

●侯捷回覆:汗如雨下。對於你所提的問題,我原本模擬了一些,後來覺得有點煩,不在我最關心的主軸範圍內,就放下了。針對你的提議,我可能會在MFCLite3.0中實作出來,也可能在書中描述這些問題,留給讀者去實現。

> 雖然MFCLite存在一些不足﹐但是瑕不掩玉﹐我覺得它對C++和MFC的學習都有非常非常大的幫助。老師的每一本書(無論是譯作還是著作)都是相關領域的精品﹐凡是在市面上見得到的我都買了下來﹐給我帶來的極大的幫助。現在我正在急切地等待老師的《STL原碼剖析》、《泛型設計與STL》、《標準C++程式庫》等書籍。希望老師注意身體﹐給我們寫出更好的作品﹗

●侯捷回覆:你非常優秀,基本功非常好。浙大電子工程系的學生真是令我刮目相看呀。《STL源碼剖析》和《C++標準程式庫》簡體版出版後各贈你一本,表示我的感謝,以及對你的期許。《泛型程式設計與STL》侯捷譯本只有繁體版,連同《C++ Primer 3e》侯捷譯本(只有繁體版)近日一起寄送給你。請告訴我你的郵寄地址。我只能寄海運(大約需時25天),空運實在太貴了 :)  

你有非常好的代碼追蹤能力,像獵犬一樣的鼻子 :)。我想你肯定用心追蹤過 MFC 源碼。賞析名家手法,是將自己拉抬到制高點的一個重要法門,形同大師灌頂。我自己離開編程第一線後,以此法修練自己,開拓自己。對於你,一個剛畢業的小夥子,我感到十分好奇。(你怎麼知道你的第一個問題的解法?你自行閱讀MFC源碼而參悟的嗎?真是不簡單)

感謝你給我如此豐富的訊息。MFCLite 十分複雜,能夠看懂它又指出問題,切中要害,甚至提出解法,實在很不容易。目前尚未有任何一位讀者給我關於MFCLite 的訊息。我聽說浙大的計算機水準很高,從你的來信得到了一些證明 :)


傳送日期: 2002年1月23日 PM 01:35
候老師﹕

您好﹐沒想到能這麼快收您的回信﹗從這點小事就足以看出老師待人是多麼熱忱﹐多麼平易近人﹗無論是在治學、還是在待人上﹐都是我學習的榜樣。

其實我的經歷很平凡﹐剛過23歲的生日(按照傳統習俗今年是我的本命年)﹐出生在貴州都勻(一個風景很美的小城市)一個普通的工人家庭﹐97年考上了浙江大學﹐專業是和計算機比較相近的電子工程﹔記得在上大學前﹐我最喜歡的課程就是數學(儘管很多人都覺得它很枯燥)﹐因為一套非常複雜的理論﹐往往僅僅建立在幾條顯而易見的公理之上﹐而其它的東西只不過是一些推論﹐那時我最喜歡做的事就是盡力把各種定理推導出來﹐從中我不僅學到了很多在書本中沒有的數學知識﹐更重要的是讓我養成了刨根問底的習慣﹐凡事都想弄清楚為什麼﹐而不僅僅是知道﹔

學習程序設計是在上大學後(在此以前我還沒有接觸過計算機)﹐剛開始學C語言﹐我最感興趣的就是那些標準程序庫是如何設計出來﹐當時我還用Taylor定理實現了標準數學函數(雖然比原來的慢了許多﹐但還是學到了不少東西)﹐接ぴ我開始看標準庫的源代碼﹐很快我就發現光會C還不夠﹐因為裡面充滿了太多的彙編代碼﹐於是我開始學習x86彙編和計算機硬件結構﹐前後用了半年多的時間﹐在學習過程中進一步加深了對C語言的理解(象參數調用規則﹐變長參數的實現﹐局部變量和全局變量的區別﹐程序的內存佈局...都是在這段時間才有了質的認識)﹐直到99年才開始學C++﹐目的就是為了學MFC﹐在學完C++語法後﹐我開始試ぴ看MFC的源代碼﹐結果自然是以失敗告終(一個剛學了一點C++語法﹐沒有一點Windows程序設計經驗的人﹐想看懂MFC源代碼﹐當然不可能)﹐

但是我沒有放棄﹐我開始制定了一個長期的學習計劃(三年左右﹐現在應該快要實現了)﹐第一步閱讀在Dos下一個較大的C++類庫源代碼﹐當時我選擇了Turbo Vision﹐目的主要是學習用C++設計大型系統的知識﹐當然最後的收穫不僅至於此﹐我還掌握了消息驅動的精髓﹐管理視窗系統的數據結構﹐內存管理的技巧﹐彙編語言與C++的結合...一直以來我都覺得TV是一個很好的學習範例﹐一來是它要求的起點比較低﹐只要會C++、彙編和熟悉Dos環境﹐二來大小比較合適總共只有1MB源代碼﹐最後它也足夠複雜基本上實現了一個小型的Windows系統(主要是消息驅動機製和視窗管理)並展現了許多重要的C++課題(象持久性)﹐為Windows和MFC的學習打下了很好的基礎﹐

第二步學習用C語言寫Windows程序設計﹐這一步是我在看了您的《深入淺出MFC 1st》後臨時加上去的﹐很感謝您你在附錄裡的書評﹐基本上我就是按照您的建議先後閱讀了《Programming Widnows 5th》、《Advanced Windows 3rd》、《Windows 95 : A Developer's Guide》、《Windows 95 System Programming SECRETS》(後兩本書是從老師的網站上下載的﹐在這我還要再次感謝老師無償地提供這兩本非常經典的書籍﹗)﹐為了理解Windows虛擬內存管理﹐我還專門閱讀了386保護模式的書籍﹐同時還看了老師的《Inside C++ Object Model》和《Effective C++ 2nd》(很可惜沒能在99年學習Turbo Vision的時候買到這兩本書﹐否則我對C++和TV的理解肯定會更進一個檔次)﹐

第三步就是閱讀MFC源代碼﹐同樣老師的書評和書籍再次給了學生很大的幫助﹐現在這一步正在進行中﹐有了上兩步的準備﹐我想一定能完成。

上面基本上是我大學四年主要學習過程。下面是我今明兩年的學習計劃﹕
1、加強數學基礎﹐重新深入學習離散數學﹐組合理論﹐圖論等知識
2、學習常用的算法﹐如圖形、編譯、數值計算
3、加強對C++的理解﹐像老師的泛型設計系列
4、UNIX平臺程序設計
5、網絡編程和TCP/IP協議
6、COM和CORBA

光有計划不行﹐還得落實到行動中﹐我會儘量爭取業余時間﹐努力提高自己的水平﹗

記得去年三月份第一次上老師的網站﹐立即就被裡面的內容所深深吸引(我把整個網站download下來﹐刻成了一張光盤)﹗無論是書評、散文還是書籍﹐都充滿了真知灼見﹗現在粗製濫造的"作家"實在是太多了﹐像老師這樣對讀者負責的技術作家﹐怎能不讓人耳目一新呢﹗對老師關於書籍和書價的觀點﹐我非常讚同﹗對於一本真正的好書﹐貴上一兩倍也是應該﹐只有這樣才能在作(譯)者、出版社、讀者之間產生良性循環﹐產生更多更好的書﹗

現在﹐我在北京□□工作﹐一家主要從事集成電路設計和中間件開發的合資企業。目前﹐我所在的項目組主要從事...。按照安排﹐再過一個月就到我們小組給其它組做技術講座了﹐我們選的題目就是泛型設計和STL﹐真希望能在此前買到老師關於泛型設計的簡體中文版書籍。

寄件者: xioax
傳送日期: 2002年1月28日 PM 07:23

就像老師說的最好的辦法就是在退出之前對每個文檔框架窗口發出WM_CLOSE命令﹐一來可以提示用戶存盤﹐二來可以將其刪除。不過也可以把下面這句給註釋起來﹐就可以避免兩次刪除的問題(只由CDocument的析構函數刪除一次)。
CMultiDocTemplate::~CMultiDocTemplate()
{
  POSITION pos = m_docList.GetHeadPosition();
  while (pos != NULL)
  {
    POSITION posDoc = pos;
    CDocument* pDoc = (CDocument*)m_docList.GetNext(pos);
    // m_docList.RemoveAt(posDoc); //
    delete (CDocument*)pDoc; //
  }
}

●侯捷回覆:在這個地方修補,有點東補西湊的味道,恐怕不是很好。anyway,我已經將整個 window-close 系統放上去,見稍後說明。真是大工程呀。累死我了。

對了﹐在AfxWinInit中打開resource.res用的是絕對路徑﹐如果目錄換了﹐就打不開﹐所以最好改成相對路徑。
void AfxWinInit(void) { // ref. appinit.cpp
...
// ifstream ifs("d:\\pic2\\mfclite\\resource.res");
ifstream ifs("resource.res"); // change to relative directory

●侯捷回覆:同意,並修改為「萬一找不到檔案,提示使用者」。使用相對路徑,那麼程式發展過程中每次修改 .rc 時,必須將新製造的 .res 複製到:
\pic2\mfclite\vc6ide 目錄和
\pic2\mfclite\vc5ide\debug 目錄和
\pic2\mfclite\vc5ide\release 目錄中,
第一個給IDE環境中執行程式時使用,第二個給IDE環境編譯出來的DEBUG版本但於console環境中執行時使用,第三個給IDE環境編譯出來的RELEASE版本但於console環境中執行時使用。已於 makefile 中撰寫這些拷貝動作。



寄件者: xioax
傳送日期: 2002年1月29日 AM 10:58

候老師﹕
好﹗今天意外地發現一個問題﹐是關於text mode和binary mode的。由於在打開文件時您用的是"wt"和"rt"﹐所以會影響文件的讀寫﹕
1、在寫入時﹐'\n'(0x0a)轉化成'\r''\n'(0x0d 0x0a)﹐在讀入時﹐'\r''\n'轉化成'\n'﹐這個影響不算太大﹐畢竟寫入讀出的轉化是對稱的。
2、在text mode下﹐CTRL+Z(0x1a)被認為是文件結束的標誌﹐這就會產生大麻煩。下面是測試代碼﹕
...
{
  char c = 0x1a ;
  CFile write ("test.tmp", CFile::modeWrite) ;
  CArchive store (&write, CArchive::store) ;
  store << c ;
}
{
  char c ;
  CFile read ("test.tmp", CFile::modeRead) ;
  CArchive load (&read, CArchive::load) ;
  load >> c ; // here, an assert failure
}
...
由於寫入的是0x1a﹐導致在讀出時會被認為到了文件尾﹐fread失敗﹗

所以應該修改CFile::Open﹐用binary mode打開﹕
BOOL CFile::Open(const char* lpszFileName, UINT nOpenFlags)
{
  // TRACE1("filename=%s %s\n", lpszFileName,
  // (nOpenFlags == modeRead ? "modeRead" : "modeWrite"));
  switch (nOpenFlags) {
    case modeRead: //
        m_hFile = ::fopen(lpszFileName, "
rb"); //
        break;
    case modeWrite: //
        m_hFile = ::fopen(lpszFileName, "
wb"); //
        break;
    default:
        assert(FALSE); //
  }
  assert(m_hFile != NULL);
  return TRUE;
}

下面是對text mdoe和binary mode的一段描述(摘自MSDN)﹕

Open in text (translated) mode. In this mode, CTRL+Z is interpreted as an end-of-file character on input. In files opened for reading/writing with "a+", fopen checks for a CTRL+Z at the end of the file and removes it, if possible. This is done because using fseek and ftell to move within a file that ends with a CTRL+Z, may cause fseek to behave improperly near the end of the file.

Also, in text mode, carriage return-linefeed combinations are translated into single linefeeds on input, and linefeed characters are translated to carriage return-linefeed combinations on output. When a Unicode stream-I/O function operates in text mode (the default), the source or destination stream is assumed to be a sequence of multibyte characters. Therefore, the Unicode stream-input functions convert multibyte characters to wide characters (as if by a call to the mbtowc function). For the same reason, the Unicode stream-output functions convert wide characters to multibyte characters (as if by a call to the wctomb function).

Open in binary (untranslated) mode; translations involving carriage-return and linefeed characters are suppressed.

●侯捷回覆:同意,已修改。這個錯誤你也找得出來,佩服。
你的提問方式,以及對 MFCLite 的深刻理解,對我的寫作很有幫助。感謝你的用心。

 

傳送日期: 2002年1月28日 AM 08:10
侯老師﹐您好﹐我發現在沒有安裝Cygwin的機器上無法運行mfclite3中的mfclappg.exe﹐建議您在下載文件中提供cygwin1.dll(一般在"/cygwin/bin/"可以找到)﹐並使之與mfclappg.exe位於同一目錄。
Solstice

●侯捷回覆:好的。

 

各位讀者請注意

本網頁起始處提供的是最新版 MFCLite3 和《多型與虛擬》2e,ch6,已涵蓋上述各項修正,包括resource leak 問題、object persistence 臭蟲修補及功能加強、window-close system。開放之第6章亦為最新內容,所有補強動作的相關說明,集中於 6.12 節

為解決 xioax 提出的幾個大哉問,我累壞了,但是很開心。在解決 window-close system 的過程中,我必須關注 WM_CLOSE, WM_DESTROY, WM_NCDESTROY, WM_QUIT 等訊息的發生和傳遞,注意 MFC 源碼之中哪些可以刪除,哪些可以保留,哪些必須改寫。我得注意視窗的摧毀和 C++ objects 的刪除,我得有一個小型的視窗管理系統。這些統合起來,實在是困難度比較高的。完成了它,很令我高興。

 

 


英雄帖

任何讀者若能指出本網頁開放之MFCLite 3 中的重大技術錯誤,我將贈以《多型與虛擬》簡體版《STL源碼剖析》簡體版一冊,以示感謝。如一併提出堪用解法,兩書合贈。至於何謂重大何謂堪用,無從獲得客觀標準,侯捷心證之 :)

任何合法行為如果造成 MFCLite crash,當然算是MFCLite的重大技術錯誤 :)。唯「在MFCLite 掌控之下」的程式強迫結束 不能算技術錯誤,因為MFCLite為求簡化,略去了 exception handling 和許多平和手法,往往在安全檢查失敗(例如assertion failed)後強迫程式結束並輸出提示訊息。

您舉出之錯誤必須可重現(再製,reproduce),並有清楚的描述。同一個問題,先提出者得分。

若是單純只由觀察MFCLite源碼來發掘問題,難上加難矣,畢竟 application framework的方方面面實在太複雜了。可能的方式之一是撰寫各類測試(MFCLite application 極類似 MFC application,寫法可參考MFCLite 提供之 mfclapp.cpp),從執行結果觀察MFCLite是否有正確、如你想像的行為。可能的方式之二是使用各種輔助工具,觀察諸如 memory leak 或 resource leak 之類的問題。

歡迎所有 MFC programmers 施出渾身解數來挑戰 MFCLite 3。

所有回帖都開放於此。

-- jjhou

 

傳送日期: 2002年1月25日 PM 03:37
我看到網站上"汗如雨下"一文對MFCLite的研討激發了我原來就有的一個想法﹐組織讀書會或者讀書心得研討﹐特別是大陸現在在出版外版書和引進原版書﹐很多書內容不太容易懂或者比較複雜﹐需要去講解﹐類似教材的講義,需要去做補丁。比如Design Patterns,所以要有 Design Patterns Explained這樣的書作補充﹐通過實例或應用來提高。我們現在正開始嘗試 "技術沙龍"﹐我想組織讀書沙龍可能也很受歡迎的。
jiangtao

●侯捷回覆:good idea.

 

傳送日期: 2002年2月7日 PM 05:51
侯老師﹕
像“對您的仰慕之情如滔滔之江水”之類的話向您也聽膩了﹐學生在此就不復述了。
最近一段時間抽空看了以下MFCLite﹐大的框架上暫沒發現什麼問題﹐只順便捉的了一些小蟲子﹐不成敬意﹕
1。
Bug展現﹕測試環境﹕MFCLite3.0 測試程序﹕MFCLite3.0中自帶mfcl30.exe。
.hoo
.hou
.dat
please input doc-type such as '.xxx' like above:
129487
...
->0
---------------------
DocTemplates count : 3
Documents count : 0
mainWnd hwnd: 1
active frame hwnd: 65535
active view hwnd: 65535
abort

原因﹕使用了NULL﹐應為下一步將要顯示active windows:
cout << "active windows: " << endl;
cout << g_state[JJGetActiveFrame()]->m_hWnd << " ";
cout << g_state[JJGetActiveView()]->m_hWnd << " ";
此時g_state[JJGetActiveFrame()]==NULL,豈有不宕之理﹖

2。
Bug展現﹕測試環境﹕MFCLite3.0 測試程序﹕MFCLite3.0中自帶mfcl30.exe。
.hoo
.hou
.dat
please input doc-type such as '.xxx' like above:
129487
...
->N
.hoo
.hou
.dat
please input doc-type such as '.xxx' like above: .hoo
->S
filename (with ext-name such as 'file.hoo'): lx.hoo
CMyDocument2::Serialize()
->1
abort

原因﹕
map<HWND, CWnd*>::iterator posG = g_state.find(JJGetActiveFrame());
map<HWND, CWnd*>::iterator pos = posG;
for (++pos; pos != g_state.end(); ++pos) {
if ((pos->second)->IsKindOf(RUNTIME_CLASS(CFrameWnd))) { // doc-frame, not view.
...
pos != g_state.end();這裡忽略了對pos->second是否為NULL的檢驗﹐所以出錯。

●侯捷回覆:首先謝謝你的來信。

我在書上第6章(已開放)說過,這個MFCLite 主要展現application framework 主軸技術,並非一個 UI 完善的產品,所以對於使用者的輸入,並未做太多檢驗。程式給的提示,使用者必須按提示完成。上述錯誤都是因為刻意輸入不正確資料而造成。

不過,為了讓使用者更方便,錯誤情況更減少,最新開放的 MFCLite3中,我已將部份的(我注意到了的)顯示介面和輸入介面做更多處理,例如熱鍵 0 或熱鍵 1 所產生的畫面中,對 active view 做了星號提示,視覺效果好多了;又如輸入 document type 時要求以 1,2,3方式做選擇題,而非讓使用者填寫文字(那比較容易出錯),因此不再可能有你所提的情況發生。

新版若有任何其他錯誤或類似錯誤,還請指正,謝謝。