MFC六大關(guān)鍵技術(shù)_第1頁
MFC六大關(guān)鍵技術(shù)_第2頁
MFC六大關(guān)鍵技術(shù)_第3頁
MFC六大關(guān)鍵技術(shù)_第4頁
MFC六大關(guān)鍵技術(shù)_第5頁
已閱讀5頁,還剩16頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

MFC六大關(guān)鍵技術(shù)MFC六大關(guān)鍵技術(shù)之初始化過程我并不認(rèn)為MFC減輕了程序員們的負(fù)擔(dān),MFC出現(xiàn)的目的雖然似乎是為了讓程序員不用懂得太多就可以進(jìn)行視窗編程,但本人在MFC里徘徊了很久很久〔因?yàn)槟菚r(shí)沒有書本詳細(xì)介紹MFC的原理〕,毫無收獲。可能朋友們會(huì)說,怎么一定要了解MFC的具體呢,“黑箱〞作業(yè)不行嗎?這不是微軟的初衷嗎?不行?。?!如果這樣,我寧愿永遠(yuǎn)不選擇MFC!在學(xué)電腦之前,本人學(xué)習(xí)的東西大都與藝術(shù)不無關(guān)系,小學(xué)時(shí)參加過全國書畫比賽獲銀獎(jiǎng)。兒時(shí)的愛好就是在一張紙上隨心所欲地畫畫!MFC“黑箱〞就象一幅碩大的抽象畫〔抽象到你不能理解〕,它用鉛筆勾畫好線條,然后請你填顏色。我們怎么能忍受“黑箱〞作業(yè)?我們選擇C++,就是因?yàn)樗鼔蜃杂?,夠藝術(shù),我們可以在此放飛夢想。所以,我們要攻克MFC。偉大孫老師在剖析MFC的時(shí)候雖然盡心盡力,但可能由于篇幅所限,說得并不大清楚〔我相信許多學(xué)員都有這方面的感受〕。在此,我突發(fā)奇想,想與大家一同分享一下著名的MFC六大關(guān)鍵技術(shù)。從什么地方開始講起好呢?我覺得回到最初摸索MFC的時(shí)候,從根本談起最好。因?yàn)槲抑?,一個(gè)走過來程序員,總是忘記了當(dāng)初自己是怎么走過來的,忘記了一個(gè)學(xué)員最想知道的是什么。一個(gè)小小的問題〔一兩句話就可以解釋的〕,足學(xué)以令手無寸鐵的學(xué)員頭大半個(gè)月,所以,我努力回憶當(dāng)初是怎么讓自己豁然開朗的。轉(zhuǎn)入正題,MFC的六大關(guān)鍵技術(shù)包括:·MFC程序的初始化過程·運(yùn)行時(shí)類型識(shí)別〔RTTI〕·動(dòng)態(tài)創(chuàng)立·永久保存·消息映射·消息傳遞MFC程序的初始化過程1、設(shè)計(jì)一個(gè)簡單完整MFC程序,產(chǎn)生一個(gè)窗口。當(dāng)然這不能讓AppWizard自動(dòng)為我們生成。我們可以在Win32Application工程下面那樣寫:#include<afxwin.h>classMyApp:publicCWinApp{public:BOOLInitInstance()//②程序入點(diǎn){CFrameWnd*Frame=newCFrameWnd();//構(gòu)造框架m_pMainWnd=Frame;//將m_pMainWnd設(shè)定為Frame;Frame->Create(NULL,"最簡單的窗口");//建立框架Frame->ShowWindow(SW_SHOW);//顯示框架returntrue;//返回}};MyApptheApp;//①建立應(yīng)用程序。設(shè)定鏈接MFC庫,運(yùn)行,即可看見一個(gè)窗口。從上面,大家可以看到建立一個(gè)MFC窗口很容易,只用兩步:一是從CWinApp派生一個(gè)應(yīng)用程序類〔這里是MyApp〕,,然后建立應(yīng)用程序?qū)ο蟆瞭heApp〕,就可以產(chǎn)生一個(gè)自己需要的窗口(即需要什么樣就在InitInstance()里創(chuàng)立就行了)。整個(gè)程序,就改寫一個(gè)InitInstance()函數(shù),創(chuàng)立那么一個(gè)對象〔theApp〕,就是一個(gè)完整的窗口程序。這就是“黑箱〞作業(yè)的魅力?。。。≡谖覀冋霝槲④浌恼频臅r(shí)候,我們突然覺得心里空蕩蕩的,我們想知道微軟幫我們做了什么事情,而我們想編自己的程序時(shí)又需要做什么事情,那怕在上面幾行的程序里面,我們還有不清楚的地方,比方,干嘛有一個(gè)m_pMainWnd指針變量,它從哪里來,又要到哪里去呢?想一想在DOS下編程是多么美妙的一件事呵,我們需要什么變量,就聲明什么變量,需要什么樣的函數(shù),就編寫什么樣的函數(shù),或者引用函數(shù)庫……但是現(xiàn)在我們怎么辦?。?!我們可以逆向思維一下,MFC要到達(dá)這種效果,它是怎么做的呢?首先我們要弄明白,VC不是一種語言,它就象我們學(xué)c語言的時(shí)候的一個(gè)類似記事本的編輯器〔請?jiān)徫业牟毁N切的比喻〕,所以,在VC里面我們用的是C++語言編程,C++才是根本〔初學(xué)者總是以為VC是一門什么新的什么語言,一門比C++先進(jìn)很多的復(fù)雜語言,汗〕。說了那么多,我想用一句簡單的話概括“MFC‘黑箱’就是幫助我們插入了‘C++代碼’的東西〞。既然MFC黑箱幫我們插入了代碼,那么大家想想它會(huì)幫我們插入什么樣的代碼呢?他會(huì)幫我們插入求解一元二次方程的代碼嗎?當(dāng)然不會(huì),所以它插入的實(shí)際上是每次編寫窗口程序必須的,通用的代碼。再往下想,什么才是通用的呢?我們每次視窗編程都要寫WinMain()函數(shù),都要有注冊窗口,產(chǎn)生窗口,消息循環(huán),回調(diào)函數(shù)……即然每次都要的東西,就讓它們從我們眼前消失,讓MFC幫助寫入!要知道MFC初始化過程,大家當(dāng)然可以跟蹤執(zhí)行程序。孫老師的第三課跟蹤了很長一段時(shí)間,我相信大家都有點(diǎn)暈頭轉(zhuǎn)向。本人覺得那怕你理解了MFC代碼,也很容易讓人找不著北,我們完全不懂的時(shí)候,在成千上萬行程序的迷宮中如何能找到出口?我們要換一種方法,不如就來重新編寫個(gè)MFC庫吧,嘩!大家不要笑,小心你的大牙,我不是瘋子〔雖然瘋子也說自己不瘋〕。我們要寫的就是最簡單的MFC類庫,就是把MFC宏觀上的,理論上的東西寫出來。我們要用最簡化的代碼,簡化到剛好能運(yùn)行。既然,我們這一節(jié)寫的是MFC程序的初始化過程,上面我們還有了一個(gè)可執(zhí)行的MFC程序。程序中只是用了兩個(gè)MFC類,一個(gè)是CWinApp,另一個(gè)是CFrameWnd。當(dāng)然,還有很多同樣重要MFC類如視圖類,文檔類等等。但在上面的程序可以不用到,所以暫時(shí)省去了它〔總之是為了簡單〕。好,現(xiàn)在開始寫MFC類庫吧……唉,面前又有一個(gè)大難題,就是讓大家背一下MFC層次結(jié)構(gòu)圖。天,那張魚網(wǎng)怎么記得住,但既然我們要理解他,總得知道它是從那里派生出來的吧??紤]到大家都很辛苦,那我們看一下上面兩個(gè)類的父子關(guān)系(箭頭代表派生):CObject->CCmdTarget->CWinThread->CWinApp->自己的重寫了InitInstance()的應(yīng)用程序類。CObject〔同上〕->CCmdTarget〔同上〕->CWnd->CFrameWnd看到層次關(guān)系圖之后,終于可以開始寫MFC類庫了。按照上面層次結(jié)構(gòu),我們可以寫以下六個(gè)類〔為了直觀,省去了構(gòu)造函數(shù)和析構(gòu)函數(shù)〕。/////////////////////////////////////////////////////////classCObiect{};//MFC類的基類。classCCmdTarget:publicCObject{};------------------------------------------------classCWinThread:publicCCmdTarget{};classCWinApp:publicCWinThread{};------------------------------------------------classCWnd:publicCCmdTarget{};classCFrameWnd:publicCWnd{};/////////////////////////////////////////////////////////大家再想一下,在上面的類里面,應(yīng)該有什么?大家馬上會(huì)想到,CWinApp類或者它的基類CCmdTarget里面應(yīng)該有一個(gè)虛函數(shù)virtualBOOLInitInstance(),是的,因?yàn)槟抢锸浅绦虻娜肟邳c(diǎn),初始化程序的地方,那自然少不了的。可能有些朋友會(huì)說,反正InitInstance()在派生類中一定要重載,我不在CCmdTarget或CWinApp類里定義,留待CWinApp的派生類去增加這個(gè)函數(shù)可不可以。扯到這個(gè)問題可能有點(diǎn)越說越遠(yuǎn),但我想信C++的朋友對虛函數(shù)應(yīng)該是沒有太多的問題的。總的來說,作為程序員如果清楚知道基類的某個(gè)函數(shù)要被派生類用到,那定義為虛函數(shù)要方便很多。也有很多朋友問,C++為什么不自動(dòng)把基類的所有函數(shù)定義為虛函數(shù)呢,這樣可以省了很多麻煩,這樣所有函數(shù)都遵照派生類有定義的函數(shù)就調(diào)用派生類的,沒定義的就調(diào)用基類的,不用寫virtual的麻煩多好!其實(shí),很多面向?qū)ο蟮恼Z言都這樣做了。但定義一個(gè)虛函數(shù)要生成一個(gè)虛函數(shù)表,要占用系統(tǒng)空間,虛函數(shù)越多,表就越大,有時(shí)得不償失!這里哆嗦幾句,是因?yàn)橥笠f明的消息映射中大家更加會(huì)體驗(yàn)到這一點(diǎn),好了,就此打往。上面我們自己解決了一個(gè)問題,就是在CCmdTarge寫一個(gè)virtualBOOLInitInstance()。大家再下想,我們還要我們MFC“隱藏〞更多的東西:WinMain()函數(shù),設(shè)計(jì)窗口類,窗口注冊,消息循環(huán),回調(diào)函數(shù)……我們馬上想到封裝想封裝他們。大家似乎隱約地感覺到封裝WinMain()不容易,覺得WinMain()是一個(gè)特殊的函數(shù),許多時(shí)候它代表了一個(gè)程序的起始和終結(jié)。所以在以前寫程序的時(shí)候,我們寫程序習(xí)慣從WinMain()的左大括寫起,到右大括弧返回、結(jié)束程序。我們換一個(gè)角度去想,有什么東西可以拿到WinMain()外面去做,許多初學(xué)者們,總覺得WinMain()函數(shù)天大的函數(shù),什么函數(shù)都好象要在它里面才能真正運(yùn)行。其實(shí)這樣了解很片面,甚至錯(cuò)誤。我們可以寫一個(gè)這樣的C++程序:////////////////////////////////////////////////////#include<iostream.h>classtest{public:test(){cout<<"請改變你對main()函數(shù)的看法!"<<endl;}};testtest1;/**************************/voidmain(){}////////////////////////////////////////////////////在上面的程序里,入口的main()函數(shù)外表上什么也不做,但程序執(zhí)行了〔注:實(shí)際入口函數(shù)做了一些我們可以不了解的事情〕,并輸出了一句話〔注:全局對象比main()首先運(yùn)行〕?,F(xiàn)在大家可以知道我們的WinMain()函數(shù)可以什么都不做,程序依然可以運(yùn)行,但沒有這個(gè)入口函數(shù)程序會(huì)報(bào)錯(cuò)。那么WinMain()函數(shù)會(huì)放哪個(gè)類上面呢,請看下面程序:#include<afxwin.h>classMyApp:publicCWinApp{public:BOOLInitInstance()//②程序入點(diǎn){AfxMessageBox("程序依然可以運(yùn)行!");returntrue;}};MyApptheApp;//①建立應(yīng)用程序。大家可以看到,我并沒有構(gòu)造框架,而程序卻可以運(yùn)行了——彈出一個(gè)對話框〔如果沒有WinMain()函數(shù)程序會(huì)報(bào)錯(cuò)〕。上面我這樣寫還是為了直觀起見,其實(shí)我們只要寫兩行程序:#include<afxwin.h>CWinApptheApp;//整個(gè)程序只構(gòu)造一個(gè)CWinApp類對象,任可事情,程序就可以運(yùn)行!所以說,只要我們構(gòu)造了CWinApp對象,就可以執(zhí)行WinMain()函數(shù)。我們馬上相信WinMain()函數(shù)是在CWinApp類或它的基類中,而不是在其他類中。其實(shí)這種看法是錯(cuò)誤的,我們知道編寫C++程序的時(shí)候,不可能讓你在一個(gè)類中包含入口函數(shù),WinMain()是由系統(tǒng)調(diào)用,跟我們的平時(shí)程序自身調(diào)用的函數(shù)有著本質(zhì)的區(qū)別。我們可以暫時(shí)簡單想象成,當(dāng)CWinApp對象構(gòu)造完的時(shí)候,WinMain()跟著執(zhí)行?,F(xiàn)在大家明白了,大局部的“通用代碼〔我們想封裝隱藏的東西〕〞都可以放到CWinApp類中,那么它又是怎樣運(yùn)行起來的呢?為什么構(gòu)造了CWinApp類對象就“自動(dòng)〞執(zhí)行那么多東西。大家再仔細(xì)想一下,CWinApp類對象構(gòu)造之后,它會(huì)“自動(dòng)〞執(zhí)行自己的構(gòu)造函數(shù)。那么我們可以把想要“自動(dòng)〞執(zhí)行的代碼放到CWinApp類的構(gòu)造函數(shù)中。那么CWinApp類可能打算這樣設(shè)計(jì)〔先不計(jì)較正確與否〕:classCWinApp:publicCWinThead{public:virtualBOOLInitInstance();//解釋過的程序的入點(diǎn)CWinApp::CWinApp(){//構(gòu)造函數(shù)////////////////////////WinMain();//這個(gè)是大家一眼看出的錯(cuò)誤Create();//設(shè)計(jì)、創(chuàng)立、更新顯示窗口Run();//消息循環(huán)//////////////////////}};寫完后,大家又馬上感覺到似乎不對,WinMain()函數(shù)在這里好象真的一點(diǎn)用處都沒有,并且能這樣被調(diào)用嗎〔請?jiān)试S我把手按在圣經(jīng)上聲明一下:WinMain()不是普通的函數(shù),它要肩負(fù)著初始化應(yīng)用程序,包括全局變量的初始化,是由系統(tǒng)而不是程序本身調(diào)用的,WinMain()返回之后,程序就結(jié)束了,進(jìn)程撤消〕。再看Create()函數(shù),它能確定設(shè)計(jì)什么樣的窗口,創(chuàng)立什么樣的窗口嗎?如果能在CWinApp的構(gòu)造函數(shù)里確定的話,我們以后設(shè)計(jì)MFC程序時(shí)窗口就一個(gè)樣,變得寫程序變有必要。再看Run()函數(shù),它能在WinMain()函數(shù)外面運(yùn)行嗎?回過頭來,我們可以讓W(xué)inMain()函數(shù)一條語句都不包含嗎?不可以,我們看一下WinMain()函數(shù)的四個(gè)參數(shù):WinMain(HINSTANCE,HINSTANCE,LPSTR,int)其中第一個(gè)參數(shù)指向一個(gè)實(shí)例句柄,我們在設(shè)計(jì)WNDCLASS的時(shí)候一定要指定實(shí)例句柄。我們窗口編程,肯定要設(shè)計(jì)窗口類。所以,WinMain()再簡單也要這樣寫:intWinMain(HINSTANCEhinst,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow){hInstance=hinst}既然實(shí)例句柄要等到程序開始執(zhí)行才能知道,那么我們用于創(chuàng)立窗口的Create()函數(shù)也要在WinMain〔〕內(nèi)部才能執(zhí)行[因?yàn)槿绻鹊絎inMain〔〕執(zhí)行完畢后,程序結(jié)束,進(jìn)程撤消,當(dāng)然Create()也不可能創(chuàng)立窗口]那么Run()(消息循環(huán))放在那里執(zhí)行好呢?眾所周知,消息循環(huán)就是相同的那么幾句代碼,但我們也不要企圖把它放在WinMain〔〕函數(shù)之外執(zhí)行。所以我們在WinMain〔〕函數(shù)里面,我們程序要象以下這樣寫WinMain〔……〕{……窗口類對象執(zhí)行創(chuàng)立窗口函數(shù)…………程序類對象執(zhí)行消息循環(huán)函數(shù)……}對于WinMain〔〕的問題,得總結(jié)一下,我們封裝的時(shí)候是不可以把它封裝到CWinApp類里面,但由于WinMain〔〕的不變性〔或者說有規(guī)律可循〕,MFC完全有能力在我們構(gòu)造CWinApp類對象的時(shí)候,幫我們完成那幾行代碼。轉(zhuǎn)了一個(gè)大圈,我們仿佛又回到了SDK編程的開始。但現(xiàn)在我們現(xiàn)在能清楚地知道,外表上MFC與SDK編程截然不同,但實(shí)質(zhì)上MFC只是用類的形式封裝了SDK函數(shù),封裝之后,我們在WinMain〔〕函數(shù)中只需要幾行代碼,就可以完成一個(gè)窗口程序。我們也由此知道了應(yīng)如何去封裝應(yīng)用程序類〔CWinApp〕和主框架窗口類〔CFrameWnd〕。下面把上開始設(shè)計(jì)這兩個(gè)類。為了簡單起見,我們忽略這兩個(gè)類的基類和派生類的編寫,可能大家會(huì)認(rèn)為這是一種很不負(fù)責(zé)任的做法,但本人覺得這既可減輕負(fù)擔(dān),又免了大家在各類之間穿來穿去,更好理解一些〔我們在關(guān)鍵的地方作注明〕。還有,我把全部代碼寫在同一個(gè)文件中,讓大家看起來不用那么吃力,但這是最不提倡的寫代碼方法,大家不要學(xué)哦!#include<windows.h>HINSTANCEhInstance;classCFrameWnd{HWNDhwnd;public:CFrameWnd();//也可以在這里調(diào)用Create()virtual~CFrameWnd();intCreate();//類就留意這一個(gè)函數(shù)就行了!BOOLShowWnd();};classCWinApp1{public:CFrameWnd*m_pMainWnd;//在真正的MFC里面//它是CWnd指針,但這里由于不寫CWnd類//只要把它寫成CFrameWnd指針CWinApp1*m_pCurrentWinApp;//指向應(yīng)用程序?qū)ο蟊旧鞢WinApp1();virtual~CWinApp1();virtualBOOLInitInstance();//MFC原本是必須重載的函數(shù),最重要的函數(shù)?。。。irtualBOOLRun();//消息循環(huán)};CFrameWnd::CFrameWnd(){}CFrameWnd::~CFrameWnd(){}intCFrameWnd::Create()//封裝創(chuàng)立窗口代碼{WNDCLASSwndcls;wndcls.style=0;wndcls.cbClsExtra=0;wndcls.cbWndExtra=0;wndcls.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);wndcls.hIcon=LoadIcon(NULL,IDC_ARROW);wndcls.hInstance=hInstance;wndcls.lpfnWndProc=DefWindowProc;//默認(rèn)窗口過程函數(shù)。//大家可以想象成MFC通用的窗口過程。wndcls.lpszClassName="窗口類名";wndcls.lpszMenuName=NULL;RegisterClass(&wndcls);hwnd=CreateWindow("窗口類名","窗口實(shí)例標(biāo)題名",WS_OVERLAPPEDWINDOW,0,0,600,400,NULL,NULL,hInstance,NULL);return0;}BOOLCFrameWnd::ShowWnd()//顯示更新窗口{ShowWindow(hwnd,SW_SHOWNORMAL);UpdateWindow(hwnd);return0;}/////////////CWinApp1::CWinApp1(){m_pCurrentWinApp=this;}CWinApp1::~CWinApp1(){}//以下為InitInstance()函數(shù),MFC中要為CWinApp的派生類改寫,//這里為了方便理解,把它放在CWinApp類里面完成!//你只要記住真正的MFC在派生類改寫此函數(shù)就行了。BOOLCWinApp1::InitInstance(){m_pMainWnd=newCFrameWnd;m_pMainWnd->Create();m_pMainWnd->ShowWnd();return0;}BOOLCWinApp1::Run()//////////////////////封裝消息循環(huán){MSGmsg;while(GetMessage(&msg,NULL,0,0)){TranslateMessage(&msg);DispatchMessage(&msg);}return0;}//////////////////////////////////////////////////////封裝消息循環(huán)CWinApp1theApp;//應(yīng)用程序?qū)ο蟆踩帧砳ntWINAPIWinMain(HINSTANCEhinst,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow){hInstance=hinst;CWinApp1*pApp=theApp.m_pCurrentWinApp;//真正的MFC要寫一個(gè)全局函數(shù)AfxGetApp,以獲取CWinApp指針。pApp->InitInstance();pApp->Run();return0;}代碼那么長,實(shí)際上只是寫了三個(gè)函數(shù),一是CFrameWnd類的Create(),第二個(gè)是CWinApp類的InitInstance()和Run()。在此特別要說明的是InitInstance(),真正的MFC中,那是我們跟據(jù)自己構(gòu)造窗口的需要,自己改寫這個(gè)函數(shù)。大家可以看到,封裝了上面兩個(gè)類以后,在入口函數(shù)WinMain中就寫幾行代碼,就可以產(chǎn)生一個(gè)窗口程序。在MFC中,因?yàn)閃inMain函數(shù)就是固定的那么幾行代碼,所以MFC絕對可以幫我們自動(dòng)完成〔MFC的特長就是幫我們完成有規(guī)律的代碼〕,所以我們創(chuàng)造MFC應(yīng)用程序的時(shí)候,看不到WinMain函數(shù)。寫到這里,MFC六大關(guān)鍵技術(shù)之一:MFC程序的初始化過程〔模擬〕,就差不多寫完了?;仡^看一下,居然寫了八千多字,原本以為寫完六大關(guān)鍵技術(shù)也不用寫那么多字,現(xiàn)在還覺得慶幸的是不把文檔、視類牽連進(jìn)去,否那么更不知寫到何時(shí)。還有五大關(guān)鍵技術(shù)沒有寫,我還應(yīng)該寫下去嗎?上面寫了八千多字,都是我一個(gè)字一個(gè)字地敲進(jìn)去,每個(gè)例子都是自己生硬地想出來。用了十多個(gè)小時(shí),換來的可能更多是論壇中朋友們的漫罵,譏諷!但我覺得還是值得的,我一向認(rèn)為VC沒有敵人,只有朋友,放眼周圍,覺察學(xué)VC的朋友越來越少,也沒有發(fā)現(xiàn)多少招收VC程序員的地方。記得讀大學(xué)的時(shí)候,我遇到一位搞美術(shù)的師兄,本來同行如敵國〔我曾經(jīng)搞過美術(shù)〕。師兄美術(shù)功底很好,但他從來沒有在學(xué)校獲過美術(shù)一等獎(jiǎng),原因評獎(jiǎng)的不懂得欣賞他的作品。我的出現(xiàn),他深刻地體會(huì)到了:多一個(gè)朋友,會(huì)少一分孤獨(dú)!有時(shí)覺得學(xué)習(xí)VC的朋友是英雄〔但我不是英雄,因?yàn)槲覍W(xué)VC多年來無甚突破〕,是值得尊敬的人物,大家交流一下,糾正一下自己的錯(cuò)誤,真是一種福份……MFC六大關(guān)鍵技術(shù)之運(yùn)行時(shí)類型識(shí)別(RTTI)運(yùn)行時(shí)類型識(shí)別〔RTTI〕即是程序執(zhí)行過程中知道某個(gè)對象屬于某個(gè)類,我們平時(shí)用C++編程接觸的RTTI一般是編譯器的RTTI,即是在新版本的VC++編譯器里面選用“使能RTTI〞,然后載入typeinfo.h文件,就可以使用一個(gè)叫typeid〔〕的運(yùn)算子,它的地位與在C++編程中的sizeof()運(yùn)算子類似的地方〔包含一個(gè)頭文件,然后就有一個(gè)熟悉好用的函數(shù)〕。typdid()關(guān)鍵的地方是可以接受兩個(gè)類型的參數(shù):一個(gè)是類名稱,一個(gè)是對象指針。所以我們判別一個(gè)對象是否屬于某個(gè)類就可以象下面那樣:if〔typeid(ClassName)==typeid〔*ObjectName〕〕{((ClassName*)ObjectName)->Fun();}象上面所說的那樣,一個(gè)typeid〔〕運(yùn)算子就可以輕松地識(shí)別一個(gè)對象是否屬于某一個(gè)類,但MFC并不是用typeid〔〕的運(yùn)算子來進(jìn)行動(dòng)態(tài)類型識(shí)別,而是用一大堆令人費(fèi)解的宏。很多學(xué)員在這里很疑惑,好象MFC在大局部地方都是故作神秘。使們大家編程時(shí)很迷惘,只知道在這里參加一組宏,又在那兒參加一個(gè)映射,而不知道我們?yōu)槭裁匆獏⒓舆@些東東。其實(shí),早期的MFC并沒有typeid〔〕運(yùn)算子,所以只能沿用一個(gè)老方法。我們甚至可以想象一下,如果MFC早期就有template〔模板〕的概念,可能更容易解決RTTI問題。所以,我們要回到“古老〞的年代,想象一下,要完成RTTI要做些什么事情。就好似我們在一個(gè)新型〔新型到我們還不認(rèn)識(shí)〕電器公司里面,我們要識(shí)別哪個(gè)是電飯鍋,哪個(gè)是電磁爐等等,我們要查看登記的各電器一系列的信息,我們才可以比擬、鑒別,那個(gè)東西是什么!要登記一系列的消息并不是一件簡單的事情,大家可能首先想到用數(shù)組登記對象。但如果用數(shù)組,我們要定義多大的數(shù)組才好呢,大了浪費(fèi)空間,小了更加不行。所以我們要用另一種數(shù)據(jù)結(jié)構(gòu)——鏈表。因?yàn)殒湵砝碚撋峡纱罂尚?,可以無限擴(kuò)展。鏈表是一種常用的數(shù)據(jù)結(jié)構(gòu),簡單地說,它是在一個(gè)對象里面保存了指向下一個(gè)同類型對象的指針。我們大體可以這樣設(shè)計(jì)我們的類:structCRuntimeClass{……類的名稱等一切信息……CRuntimeClass*m_pNextClass;//指向鏈表中下一CRuntimeClass對象的指針};鏈表還應(yīng)該有一個(gè)表頭和一個(gè)表尾,這樣我們在查鏈表中各對象元素的信息的時(shí)候才知道從哪里查起,到哪兒結(jié)束。我們還要注明本身是由哪能個(gè)類派生。所以我們的鏈表類要這樣設(shè)計(jì):structCRuntimeClass{……類的名稱等一切信息……CRuntimeClass*m_pBaseClass;//指向所屬的基類。CRuntimeClass*m_pNextClass;//定義表尾的時(shí)候只要定義此指針為空就可以了。staticCRuntimeClass*pFirstClass;//這里表頭指針屬于靜態(tài)變量,因?yàn)槲覀冎纒tatic變量在內(nèi)存中只初始化一次,就可以為各對象所用!保證了各對象只有一個(gè)表頭。};有了CRuntimeClass結(jié)構(gòu)后,我們就可以定義鏈表了:staticCRuntimeClassclassCObject={NULL,NULL};//這里定義了一個(gè)CRuntimeClass對象,因?yàn)閏lassCObject無基類,所以m_pBaseClass為NULL。因?yàn)槟壳爸挥幸粋€(gè)元素〔即目前沒有下一元素〕,所以m_pNextClass為NULL〔表尾〕。至于pFirstClass〔表頭〕,大家可能有點(diǎn)想不通,它到什么地方去了。因?yàn)槲覀冞@里并不想把classCObject作為鏈表表頭,我們還要在前面插入很多的CRuntimeClass對象,并且因?yàn)閜FirstClass為static指針,即是說它不是屬于某個(gè)對象,所以我們在用它之前要先初始化:CRuntimeClass*CRuntimeClass::pFirstClass=NULL;現(xiàn)在我們可以在前面插入一個(gè)CRuntimeClass對象,插入之前我得重要申明一下:如果單純?yōu)榱诉\(yùn)行時(shí)類型識(shí)別,我們未必用到m_pNextClass指針〔更多是在運(yùn)行時(shí)創(chuàng)立時(shí)用〕,我們關(guān)心的是類本身和它的基類。這樣,查找一個(gè)對象是否屬于一個(gè)類時(shí),主要關(guān)心的是類本身及它的基類,CRuntimeClassclassCCmdTarget={&classCObject,NULL};CRuntimeClassclassCWnd={&classCCmdTarget,NULL};CRuntimeClassclassCView={&classCWnd,NULL};好了,上面只是僅僅為一個(gè)指針m_pBaseClass賦值〔MFC中真正CRuntimeClass有多個(gè)成員變量和方法〕,就連接成了鏈表。假設(shè)我們現(xiàn)在已全部構(gòu)造完成自己需要的CRuntimeClass對象,那么,這時(shí)候應(yīng)該定義表頭。即要用pFirstClass指針指向我們最后構(gòu)造的CRuntimeClass對象——classCView。CRuntimeClass::pFirstClass=&classCView;現(xiàn)在鏈表有了,表頭表尾都完善了,問題又出現(xiàn)了,我們應(yīng)該怎樣訪問每一個(gè)CRuntimeClass對象?要判斷一個(gè)對象屬于某類,我們要從表頭開始,一直向表尾查找到表尾,然后才能比擬得出結(jié)果嗎??隙ú皇沁@樣!大家可以這樣想一下,我們構(gòu)造這個(gè)鏈表的目的,就是構(gòu)造完之后,能夠按主觀地拿一個(gè)CRuntimeClass對象和鏈表中的元素作比擬,看看其中一個(gè)對象中否屬于你指定的類。這樣,我們需要有一個(gè)函數(shù),一個(gè)能返回自身類型名的函數(shù)GetRuntimeClass()。上面簡單地說一下鏈表的過程,但單純有這個(gè)鏈表是沒有任何意義?;氐組FC中來,我們要實(shí)現(xiàn)的是在每個(gè)需要有RTTI能力的類中構(gòu)造一個(gè)CRuntimeClass對象,比擬一個(gè)類是否屬于某個(gè)對象的時(shí)候,實(shí)際上只是比擬CRuntimeClass對象。如何在各個(gè)類之中插入CRuntimeClass對象,并且指定CRuntimeClass對象的內(nèi)容及CRuntimeClass對象的鏈接,這里起碼有十行的代碼才能完成。在每個(gè)需要有RTTI能力的類設(shè)計(jì)中都要重復(fù)那十多行代碼是一件乏味的事情,也容易出錯(cuò),所以MFC用了兩個(gè)宏代替這些工作,即DECLARE_DYNAMIC(類名)和IMPLEMENT_DYNAMIC(類名,基類名)。從這兩個(gè)宏我們可以看出在MFC名類中的CRuntimeClass對象構(gòu)造連接只有類名及基類名的不同!到此,可能會(huì)有朋友問:為什么要用兩個(gè)宏,用一個(gè)宏不可以代換CRuntimeClass對象構(gòu)造連接嗎?個(gè)人認(rèn)為肯定可以,因?yàn)楹曛皇俏淖执鷵Q的游戲而已。但我們在編程之中,頭文件與源文件是分開的,我們要在頭文件頭聲明變量及方法,在源文件里實(shí)具體實(shí)現(xiàn)。即是說我們要在頭文件中聲明:public:staticCRuntimeClassclassXXX//XXX為類名virtualCRuntime*GetRuntimeClass()const;然后在源文件里實(shí)現(xiàn):CRuntimeClass*XXX::classXXX={……};CRuntime*GetRuntimeClass()const;{return&XXX::classXXX;}//這里不能直接返回&classXXX,因?yàn)閟tatic變量是類擁有而不是對象擁有。我們一眼可以看出MFC中的DECLARE_DYNAMIC(類名)宏應(yīng)該這樣定義:#defineDECLARE_DYNAMIC(class_name)public:staticCRuntimeClassclass##class_name;virtualCRuntimeClass*GetRuntimeClass()const;其中##為連接符,可以讓我們傳入的類名前面加上class,否那么跟原類同名,大家會(huì)知道產(chǎn)生什么后果。有了上面的DECLARE_DYNAMIC(類名)宏之后,我們在頭文件里寫上一句DECLARE_DYNAMIC(XXX)宏展開后就有了我們想要的:public:staticCRuntimeClassclassXXX//XXX為類名virtualCRuntime*GetRuntimeClass()const;對于IMPLEMENT_DYNAMIC(類名,基類名),看來也不值得在這里代換文字了,大家知道它是知道回事,宏展開后為我們做了什么,再深究真是一點(diǎn)意義都沒有!有了此鏈表之后,就像有了一張存放各類型的網(wǎng),我們可以輕而易舉地RTTI。CObject有一個(gè)函數(shù)BOOLIsKindOf(constCRuntimeClass*pClass)const;,被它以下所有派生員繼承。此函數(shù)實(shí)現(xiàn)如下:BOOLCObject::IsKindOf(constCRuntimeClass*pClass)const{CRuntimeClass*pClassThis=GetRuntimeClass();//獲得自己的CRuntimeClass對象指針。while(pClassThis!=NULL){if(pClassThis==pClass)returnTRUE;pClassThis=pClassThis->m_pBaseClass;//這句最關(guān)鍵,指向自己基類,再回頭比擬,一直到盡頭m_pBaseClass為NULL結(jié)束。}returnFALSE;}說到這里,運(yùn)行時(shí)類型識(shí)別〔RTTI〕算是完成了。寫這篇文章的時(shí)候,我一直重感冒。我曾一度在想,究竟寫這東西是為了什么。因?yàn)槿绻野堰@些時(shí)間用在別的地方〔甚至幫別人打打字〕,應(yīng)該有數(shù)百元的報(bào)酬。是什么讓“嗜財(cái)如命〞的我繼續(xù)寫下去?我想,無非是想交幾個(gè)計(jì)算機(jī)的朋友而已。計(jì)算機(jī)是大家公認(rèn)高科技的東西,但學(xué)習(xí)它的朋友大多只能默默無聞,外界的朋友也不知道怎么去認(rèn)識(shí)你。程序員更不是“潮流〞的東西,更加得不到別人的認(rèn)可。有一件個(gè)人認(rèn)為很典型的事情,有一天,我跟一個(gè)朋友到一個(gè)單位里面。里面有一個(gè)女打字員。朋友看著她熟練的指法,心悅誠服地說:“她的電腦水平比你的又高了一個(gè)很高的層次!〞,那個(gè)女的打字高手亦自豪地說:“我靠電腦為生,電腦水平肯定比你〔指筆者〕的好一點(diǎn)!換著是你,如果以電腦為生,我也不敢說好過你!〞。雖然我想聲明我是計(jì)算機(jī)專業(yè)的,但我知道沒有理解,所以我只得客氣地點(diǎn)頭。選擇電腦“潮流〞的東西實(shí)際是選擇了平凡,而選擇做程序員就是選擇了孤獨(dú)!幸好我不是一個(gè)專門的程序員,但即使如此,我愿意做你們的朋友,因?yàn)槲覑勰銈?!MFC六大關(guān)鍵技術(shù)之動(dòng)態(tài)創(chuàng)立動(dòng)態(tài)創(chuàng)立就是運(yùn)行時(shí)創(chuàng)立指定類的對象,在MFC中大量使用。如框架窗口對象、視對象,還有文檔對象都需要由文檔模板類對象來動(dòng)態(tài)的創(chuàng)立。我覺得這是每個(gè)MFC的學(xué)習(xí)者很希望理解的問題。初次接觸MFC的時(shí)候,很容易有這樣的迷惘。MFC的幾大類不用我們設(shè)計(jì)也就罷了,但最疑惑的是不用我們實(shí)例化對象。本來最直觀的理解就是,我們需要框架的時(shí)候,親手寫上CFrameWndmyFrame;需要視的時(shí)候,親自打上CViewmyView;……但MFC不給我們這個(gè)時(shí)機(jī),致使我們錯(cuò)覺窗口沒有實(shí)例化就彈出來了!就象畫了張電視機(jī)的電路圖就可以看電視一樣令人難以置信。但大伙想了一下,可能會(huì)一拍腦門,認(rèn)為簡單不過:MFC自動(dòng)幫我們完成CViewmyView之流的代碼不就行了么?。。∑鋵?shí)不然,寫MFC程序的時(shí)候,我們幾乎要對每個(gè)大類進(jìn)行派生改寫。換句話說,MFC并不知道我們打算怎樣去改寫這些類,當(dāng)然也不打算全部為我們“靜態(tài)〞創(chuàng)立這些類了。即使靜態(tài)了創(chuàng)立這些類也沒有用,因?yàn)槲覀儚膩硪膊粫?huì)直接利用這些類的實(shí)例干什么事情。我們只知道,想做什么事情就往各大類里塞,不管什么變量、方法照塞,塞完之后,我們似乎并未實(shí)例化對象,程序就可以運(yùn)行!要做到把自己的類交給MFC,MFC就用同一樣的方法,把不同的類一一準(zhǔn)確創(chuàng)立,我們要做些什么事情呢?同樣地,我們要建立鏈表,記錄各類的關(guān)鍵信息,在動(dòng)態(tài)創(chuàng)立的時(shí)候找出這些信息,就象上一節(jié)RTTI那樣!我們可以設(shè)計(jì)一個(gè)類:structCRuntimeClass{LPCSTRm_lpszClassName;//類名指針CObject*(PASCAL*m_pfnCreateObject)();//創(chuàng)立對象的函數(shù)的指針CRuntimeClass*m_pBaseClass;//講RTTI時(shí)介紹過CRuntimeClass*m_pNextClass;//指向鏈表的下一個(gè)元素(許多朋友說上一節(jié)講RTTI時(shí)并沒有用到這個(gè)指針,我原本以為這樣更好理解一些,因?yàn)闆]有這個(gè)指針,這個(gè)鏈表是無法連起來,而m_pBaseClass僅僅是向基類走,在MFC的樹型層次結(jié)構(gòu)中m_pBaseClass是不能遍歷的)CObject*CreateObject();//創(chuàng)立對象staticCRuntimeClass*PASCALLoad();//遍歷整個(gè)類型鏈表,返回符合動(dòng)態(tài)創(chuàng)立的對象。staticCRuntimeClass*pFirstClass;//類型鏈表的頭指針};一下子往結(jié)構(gòu)里面塞了那么多的東西,大家可以覺得有點(diǎn)頭暈。至于CObject*(PASCAL*m_pfnCreateObject)();,這定義函數(shù)指針的方法,大家可能有點(diǎn)陌生。函數(shù)指針在C++書籍里一般被定為選學(xué)章節(jié),但MFC還是經(jīng)常用到此類的函數(shù),比方我們所熟悉的回調(diào)函數(shù)。簡單地說m_pfnCreateObject即是保存了一個(gè)函數(shù)的地址,它將會(huì)創(chuàng)立一個(gè)對象。即是說,以后,m_pfnCreateObject指向不同的函數(shù),我們就會(huì)創(chuàng)立不同類型的對象。有函數(shù)指針,我們要實(shí)現(xiàn)一個(gè)與原定義參數(shù)及返回值都相同一個(gè)函數(shù),在MFC中定義為:staticCObject*PASCALCreateObject(){returnnewXXX};//XXX為類名。類名不同,我們就創(chuàng)立不同的對象。由此,我們可以如下構(gòu)造CRuntimeClass到鏈表:CRuntimeClassclassXXX={類名,……,XXX::CreateObject(),//m_pfnCreateObject指向的函數(shù)RUNTIME_CLASS(基類名)//RUNTIME_CLASS宏可以返回CRuntimeClass對象指針。NULL//m_pNextClass暫時(shí)為空,最后會(huì)我們再設(shè)法讓它指向舊鏈表表頭。};這樣,我們用函數(shù)指針m_pfnCreateObject〔指向CreateObject函數(shù)),就隨時(shí)可new新對象了。并且大家留意到,我們在設(shè)計(jì)CRuntimeClass類對時(shí)候,只有類名〔和基類名〕的不同〔我們用XXX代替的地方〕,其它的地方一樣,這正是我們想要的,因?yàn)槲覀儎?dòng)態(tài)創(chuàng)立也象RTTI那樣用到兩個(gè)宏,只要傳入類名和基類作宏參數(shù),就可以滿足條件。即是說,我們類說明中使用DECLARE_DYNCREATE〔CLASSNMAE〕宏和在類的實(shí)現(xiàn)文件中使用IMPLEMENT_DYNCREATE〔CLASSNAME,BASECLASS〕宏來為我們參加鏈表,至于這兩個(gè)宏怎么為我們建立一個(gè)鏈表,我們自己可以玩玩文字代換的游戲,在此不一一累贅。但要說明的一點(diǎn)就是:動(dòng)態(tài)創(chuàng)立宏xxx_DYNCREATE包含了RTTI宏,即是說,xxx_DYNCREATE是xxx_DYNAMIC的“增強(qiáng)版〞。到此,我們有必要了解一下上節(jié)課沒有明講的m_pNextClass指針。因?yàn)镸FC層次結(jié)構(gòu)是樹狀的,并不是直線的。如果我們只有一個(gè)m_pBaseClass指針,它只會(huì)沿著基類上去,會(huì)漏掉其它分支。在動(dòng)態(tài)創(chuàng)立時(shí),必需要檢查整個(gè)鏈表,看有多少個(gè)要?jiǎng)討B(tài)創(chuàng)立的對象,即是說要從表頭〔pFirstClass〕開始一直遍歷到表尾〔m_pNextClass=NULL〕,不能漏掉一個(gè)CRuntimeClass對象。所以每當(dāng)有一個(gè)新的鏈表元素要參加鏈表的時(shí)候,我們要做的就是使新的鏈表元素成為表頭,并且m_pNextClass指向原來鏈表的表頭,即像下面那樣〔當(dāng)然,這些不需要我們操心,是RTTI宏幫助我們完成的〕:pNewClass->m_pNextClass=CRuntimeClass::pFirstClass;//新元素的m_pNextClass指針指向想?yún)⒓拥逆湵淼谋眍^。CRuntimeClass::pFirstClass=pNewClass;//鏈表的頭指針指向剛插入的新元素。好了,有了上面的鏈表,我們就可以分析動(dòng)態(tài)創(chuàng)立了。有一了張有類名,函數(shù)指針,動(dòng)態(tài)創(chuàng)立函數(shù)的鏈表,我們就可以知道應(yīng)該按什么步驟去動(dòng)態(tài)創(chuàng)立了:1、獲得一要?jiǎng)討B(tài)創(chuàng)立的類的類名〔假設(shè)為A〕。2、將A跟鏈表里面每個(gè)元素的m_lpszClassName指向的類名作比擬。3、假設(shè)找到跟A相同的類名就返回A所屬的CRuntimeClass元素的指針。4、判斷m_pfnCreateObject是否有指向創(chuàng)立函數(shù),有那么創(chuàng)立對象,并返回該對象。代碼演示如下〔以下兩個(gè)函數(shù)都是CRuntimeClass類函數(shù)〕:///////////////以下為根據(jù)類名從表頭向表尾查找所屬的CRuntimeClass對象////////////CRuntimeClass*PASCALCRuntimeClass::Load(){charszClassXXX[64];CRuntimeClass*pClass;cin>>szClassXXX;//假定這是我們希望動(dòng)態(tài)創(chuàng)立的類名for(pClass=pFirstClass;pClass!=NULL;pClass=pClass->m_pNextClass){if(strcmp(szClassXXX,pClass->m_lpszClassName)==0)returnpClass;}returnNULL}///////////根據(jù)CRuntimeClass創(chuàng)立對象///////////CObject*CRuntimeClass::CreateObject(){if(m_pfnCreateObject==NULL)returnNULL;CObject*pObject;pObject=(*m_pfnCreateObject)();//函數(shù)指針調(diào)用returnpObject;}有了上面兩個(gè)函數(shù),我們在程序執(zhí)行的時(shí)候調(diào)用,就可以動(dòng)態(tài)創(chuàng)立對象了。我們還可以更簡單地實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)立,大家注意到,就是在我們的程序類里面有一個(gè)RUNTIME_CLASS(class_name)宏,這個(gè)宏在MFC里定義為:RUNTIME_CLASS(class_name)((CRuntimeClass*)(&class_name::class##class_name))作用就是得到類的RunTime信息,即返回class_name所屬CRuntimeClass的對象。在我們的應(yīng)用程序員類(CMyWinApp)的InitInstance()函數(shù)下面的CSingleDocTemplate函數(shù)中,有:RUNTIME_CLASS(CMyDoc),RUNTIME_CLASS(CMainFrame),//mainSDIframewindowRUNTIME_CLASS(CMyView)構(gòu)造文檔模板的時(shí)候就用這個(gè)宏得到文檔、框架和視的RunTime信息。有了RunTime信息,我們只要一條語句就可以動(dòng)態(tài)創(chuàng)立了,如:classMyView->CreateObject();//對象直接調(diào)用用CRuntimeClass本身的CreateObject()現(xiàn)在,細(xì)心的朋友已經(jīng)能清楚動(dòng)態(tài)創(chuàng)立需要的步驟:1、定義一個(gè)不帶參數(shù)的構(gòu)造函數(shù)〔默認(rèn)構(gòu)造函數(shù)〕;因?yàn)槲覀兪怯肅reateObject()動(dòng)態(tài)創(chuàng)立,它只有一條語句就是returnnewXXX,不帶任何參數(shù)。所以我們要有一個(gè)無參構(gòu)造函數(shù)。2、類說明中使用DECLARE_DYNCREATE〔CLASSNMAE〕宏;和在類的實(shí)現(xiàn)文件中使用IMPLEMENT_DYNCREATE〔CLASSNAME,BASECLASS〕宏;這個(gè)宏完成構(gòu)造CRuntimeClass對象,并參加到鏈表中。3、使用時(shí)先通過宏RUNTIME_CLASS得到類的RunTime信息,然后使用CRuntimeClass的成員函數(shù)CreateObject創(chuàng)立一個(gè)該類的實(shí)例。4、CObject*pObject=pRuntimeClass->CreateObject();//完成動(dòng)態(tài)創(chuàng)立。MFC六大關(guān)鍵技術(shù)之永久保存(串行化)先用一句話來說明永久保存的重要:弄懂它以后,你就越來越像個(gè)程序員了!如果我們的程序不需要永久保存,那幾乎可以肯定是一個(gè)小玩兒。那怕我們的記事本、畫圖等小程序,也需要保存才有真正的意義。對于MFC的很多地方我不甚滿意,總覺得它喜歡拿一組低能而神秘的宏來故弄玄虛,但對于它的連續(xù)存儲(chǔ)〔serialize〕機(jī)制,卻是我十分鐘愛的地方。在此,可讓大家感受到面向?qū)ο蟮男腋?。MFC的連續(xù)存儲(chǔ)〔serialize〕機(jī)制俗稱串行化?!霸谀愕某绦蛑斜M管有著各種各樣的數(shù)據(jù),serialize機(jī)制會(huì)象流水一樣按順序存儲(chǔ)到單一的文件中,而又能按順序地取出,變成各種不同的對象數(shù)據(jù)。〞不知我在說上面這一句話的時(shí)候,大家有什么反響,可能很多朋友直覺是一件很簡單的事情,只是說了一個(gè)“爽〞字就沒有下文了。要實(shí)現(xiàn)象流水一樣存儲(chǔ)其實(shí)是一個(gè)很大的難題。試想,在我們的程序里有各式各樣的對象數(shù)據(jù)。如畫圖程序中,里面設(shè)計(jì)了點(diǎn)類,矩形類,圓形類等等,它們的繪圖方式及對數(shù)據(jù)的處理各不相同,用它們實(shí)現(xiàn)了成百上千的對象之后,如何存儲(chǔ)起來?不想由可,一想頭都大了:我們要在程序中設(shè)計(jì)函數(shù)store(),在我們單擊“文件/保存〞時(shí)能把各對象往里存儲(chǔ)。那么這個(gè)store()函數(shù)要神通廣闊,它能清楚地知道我們設(shè)計(jì)的是什么樣的類,產(chǎn)生什么樣的對象。大家可能并不覺得這是一件很困難的事情,程序有能力知道我們的類的樣子,對象也不過是一塊初始化了存儲(chǔ)區(qū)域罷了。就把一大堆對象“轉(zhuǎn)換〞成磁盤文件就行了。即使上面的存儲(chǔ)能成立,但當(dāng)我們單擊“文件/翻開〞時(shí),程序當(dāng)然不能預(yù)測用戶想翻開哪個(gè)文件,并且當(dāng)翻開文件的時(shí)候,要根據(jù)你那一大堆垃圾數(shù)據(jù)new出數(shù)百個(gè)對象,復(fù)原為你原來存儲(chǔ)時(shí)的樣子,你又該怎么做呢?試想,要是我們有一個(gè)能容納各種不同對象的容器,這樣,用戶用我們的應(yīng)用程序翻開一個(gè)磁盤文件時(shí),就可以把文件的內(nèi)容讀進(jìn)我們程序的容器中。把磁盤文件讀進(jìn)內(nèi)存,然后識(shí)別它“是什么對象〞是一件很難的事情。首先,保存過程不像電影的膠片,把景物直接映射進(jìn)去,然后,看一下膠片就知道那是什么內(nèi)容??赡苡信笥颜f它象錄像磁帶,拿著錄像帶我們看不出里面變化的磁場信號(hào),但經(jīng)過錄像機(jī)就能把它復(fù)原出來。其實(shí)不是這樣的,比方保存一個(gè)矩形,程序并不是把矩形本身按點(diǎn)陣存儲(chǔ)到磁盤中,因?yàn)槲覀兝L制矩形的整個(gè)過程只不過是調(diào)用一個(gè)GDI函數(shù)罷了。它保存只是坐標(biāo)值、線寬和某些標(biāo)記等。程序面對“00FF〞這樣的東西,當(dāng)然不知道它是一個(gè)圓或是一個(gè)字符!拿剛剛錄像帶的例子,我們之所以能最后放映出來,前提我們知道這對象是“錄像帶〞,即確定了它是什么類對象。如果我們事先只知道它“里面保存有東西,但不知道它是什么類型的東西〞,這就導(dǎo)致我們無法把它讀出來。拿錄像帶到錄音機(jī)去放,對錄音機(jī)來說,那完全是垃圾數(shù)據(jù)。即是說,要了解永久保存,要對動(dòng)態(tài)創(chuàng)立有深刻的認(rèn)識(shí)?,F(xiàn)在大家可以知道困難的根源了吧。我們在寫程序的時(shí)候,會(huì)不斷創(chuàng)造新的類,構(gòu)造新的對象。這些對象,當(dāng)然是舊的類對象〔如MyDocument〕從未見過的。那么,我們?nèi)绾尾拍苁刮臋n對象可以保存自己新對象呢,又能動(dòng)態(tài)創(chuàng)立自己新的類對象呢?許多朋友在這個(gè)時(shí)候想起了CObject這個(gè)類,也想到了虛函數(shù)的概念。于是以為自己“大致了解〞串行化的概念。他們設(shè)想:“我們設(shè)計(jì)的MyClass〔我們想用于串行化的對象〕全部從CObject類派生,CObject類對象當(dāng)然是MyDocument能認(rèn)識(shí)的。〞這樣就實(shí)現(xiàn)了一個(gè)目的:本來MyDocument不能識(shí)別我們創(chuàng)立的MyClass對象,但它能識(shí)別CObject類對象。由于MyClass從CObject類派生,我產(chǎn)的新類對象“是一個(gè)CObject〞,所以MyDocument能把我們的新對象當(dāng)作CObiect對象讀出?;蛘吒鶕?jù)書本上所說的:翻開或保存文件的時(shí)候,MyDocument會(huì)調(diào)用Serialize〔〕,MyDocument的Serialize〔〕函會(huì)呼叫我們創(chuàng)立類的Serialize函數(shù)[即是在MyDocumentSerialize〔〕中調(diào)用:m_pObject->Serialize(),注意:在此m_pObject是CObject類指針,它可以指向我們設(shè)計(jì)的類對象]。最終結(jié)果是MyDocument的讀出和保存變成了我們創(chuàng)立的類對象的讀出和保存,這種認(rèn)識(shí)是不明朗的。有意思還有,在網(wǎng)上我遇到幾位自以為懂了Serialize的朋友,居然不約而同的犯了一個(gè)很低級(jí)得讓人不可思議的錯(cuò)誤。他們說:Serialize太簡單了!Serialize〔〕是一個(gè)虛函數(shù),虛函數(shù)的作用就是“優(yōu)先派生類的操作〞。所以MyDocument不實(shí)現(xiàn)Serialize〔〕函數(shù),留給我們自己的MyClass對象去調(diào)用Serialize〔〕……真是哭笑不得,我們創(chuàng)立的類MyClass并不是由MyDocument類派生,Serialize〔〕函數(shù)為虛在MyDocument和MyClass之間沒有任何意義。MyClass產(chǎn)生的MyObject對象僅僅是MyDocument的一個(gè)成員變量罷了。話說回來,由于MyClass從CObject派生,所以CObject類型指針能指向MyClass對象,并且能夠讓MyClass對象執(zhí)行某些函數(shù)〔特指重載的CObject虛函數(shù)〕,但前提必須在MyClass對象實(shí)例化了,即在內(nèi)存中占領(lǐng)了一塊存儲(chǔ)區(qū)域之后。不過,我們的問題恰恰就是在應(yīng)用程序隨便翻開一個(gè)文件,面對的是它不認(rèn)識(shí)的MyClass類,當(dāng)然實(shí)例化不了對象。幸好我們在上一節(jié)課中懂得了動(dòng)態(tài)創(chuàng)立。即想要從CObject派生的MyClass成為可以動(dòng)態(tài)創(chuàng)立的對象只要用到DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC宏就可以了〔注意:最終可以Serialize的對象僅僅用到了DECLARE_SERIAL/IMPLEMENT_SERIAL宏,這是因?yàn)镈ECLARE_SERIAL/IMPLEMENT_SERIAL包含了DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC宏〕。從解決上面的問題中,我們可以分步理解了:1、Serialize的目的:讓MyDocument對象在執(zhí)行翻開/保存操作時(shí),能讀出〔構(gòu)造〕和保存它不認(rèn)的MyClass類對象。2、MyDocument對象在執(zhí)行翻開/保存操作時(shí)會(huì)調(diào)用它本身的Serialize〔〕函數(shù)。但不要指望它會(huì)自動(dòng)保存和讀出我們的MyClass類對象。這個(gè)問題很容易解決,就直接在MyDocument::Serialize〔〕{//在此函數(shù)調(diào)用MyClass類的Serialize〔〕就行了!即MyObject.Serialize〔〕;}3、我們希望MyClass對象為可以動(dòng)態(tài)創(chuàng)立的對象,所以要求在MyClass類中加上DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC宏。但目前的Serialize機(jī)制還很抽象。我們僅僅知道了外表上的東西,實(shí)際又是如何的呢?下面作一個(gè)簡單深刻的詳解。先看一下我們文檔類的Serialize〔〕voidCMyDoc::Serialize(CArchive&ar){if(ar.IsStoring()){//TODO:addstoringcodehere}else{//TODO:addloadingcodehere}}目前這個(gè)子數(shù)什么也沒做〔沒有數(shù)據(jù)的讀出和寫入〕,CMyDoc類正等待著我們?nèi)ジ膶戇@個(gè)函數(shù)。現(xiàn)在假設(shè)CMyDoc有一個(gè)MFC可識(shí)別的成員變量m_MyVar,那么函數(shù)就可改寫成如下形式:voidCMyDoc::Serialize(CArchive&ar){if(ar.IsStoring())//讀寫判斷{ar<<m_MyVar;//寫}else{ar>>m_MyVar;//讀}}許多網(wǎng)友問:自己寫的類〔即MFC未包含的類〕為什么不行?我們在CMyDoc里包含自寫類的頭文件MyClass.h,這樣CMyDoc就認(rèn)識(shí)MyDoc類對象了。這是一般常識(shí)性的錯(cuò)誤,MyDoc類認(rèn)識(shí)MyClass類對象與否并沒有用,關(guān)鍵是CArchive類,即對象ar不認(rèn)識(shí)MyClass〔當(dāng)然你夢想重寫CArchive類當(dāng)別論〕。“>>〞、“<<〞都是CArchive重載的操作符。上面ar>>m_MyVar說白即是在執(zhí)行一個(gè)以ar和m_MyVar為參數(shù)的函數(shù),類似于function(ar,m_MyVar)罷了。我們當(dāng)然不能傳遞一個(gè)它不認(rèn)識(shí)的參數(shù)類型,也因此不會(huì)執(zhí)行function(ar,m_MyObject)了。[注:這里我們可以用指針。讓MyClass從Cobject派生,一切又起了質(zhì)的變化,假設(shè)我們定義了:MyClass*pMyClass=newMyClass;因?yàn)镸yClass從CObject派生,根據(jù)虛函數(shù)原理,pMyClass也是一個(gè)CObject*,即pMyClass指針是CArchive類可認(rèn)識(shí)的。所以執(zhí)行上述function(ar,pMyClass),即ar<<pMyClass是沒有太多的問題〔在保證了MyClass對象可以動(dòng)態(tài)創(chuàng)立的前提下〕。]回過頭來,如果想讓MyClass類對象能Serialize,就得讓MyClass從CObject派生,Serialize〔〕函數(shù)在CObject里為虛,MyClass從CObject派生之后就可以根據(jù)自己的要求去改寫它,象上面改寫CMyDoc::Serialize〔〕方法一樣。這樣MyClass就得到了屬于MyClass自己特有的Serialize〔〕函數(shù)?,F(xiàn)在,程序就可以這樣寫:……#include“MyClass.h〞……voidCMyDoc::Serialize(CArchive&ar){//在此調(diào)用MyClass重寫過的Serialize()m_MyObject.Serialize(ar);//m_MyObject為MyClass實(shí)例}至此,串行化工作就算完成了,一即簡單直觀:從CObject派生自己的類,重寫Serialize〔〕。在此過程中,我刻意安排:在沒有用到DECLARE_SERIAL/IMPLEMENT_SERIAL宏,也沒有用到CArray等模板類的前提下就完成了串行化的工作。我看過某些書,總是一開始就講DECLARE_SERIAL/IMPLEMENT_SERIAL宏或馬上用CArray模板,讓讀者覺得串行化就是這兩個(gè)東西,導(dǎo)致許多朋友因此找不著北。大家看到了,沒有DECLARE_SERIAL/IMPLEMENT_SERIAL宏和CArray等數(shù)據(jù)結(jié)構(gòu)模板也依然可以完成串行化工作?,F(xiàn)在可以騰出時(shí)間講一下大家覺得十分抽象的CArchive。我們先看以下程序〔注:以下程序包含動(dòng)態(tài)創(chuàng)立等,請包含DECLARE_SERIAL/IMPLEMENT_SERIAL宏〕voidMyClass::Serialize(CArchive&ar){if(ar.IsStoring())//讀寫判斷{ar<<m_pMyVar;//問題:ar如何把m_pMyVar所指的對象變量保存到磁盤?}else{pMyClass=newMyClass;//準(zhǔn)備存儲(chǔ)空間ar>>m_pMyVar;}}要答復(fù)上面的問題,即“ar<<XXX〞的問題。和我們得看一下模擬CArchive的代碼?!癮r<<XXX〞是執(zhí)行CArchive對運(yùn)算符“<<〞的重載動(dòng)作。ar和XXX都是該重載函數(shù)中的一參數(shù)而已。函數(shù)大致如下:CArchive&operator<<(CArchive&ar,constCObject*pOb){…………//以下為CRuntimeClass鏈表中找到、識(shí)別pOb資料。CRuntimeClass*pClassRef=pOb->GetRuntimeClass();//保存pClassRef即類信息〔略〕((CObject*)pOb)->Serialize();//保存MyClass數(shù)據(jù)…………}從上面可以看出,因?yàn)镾erialize()為虛函數(shù),即“ar<<XXX〞的結(jié)果是執(zhí)行了XXX所指向?qū)ο蟊旧淼腟erialize()。對于“ar>>XXX〞,雖然不是“ar<<XXX〞逆過程,大家可能根據(jù)動(dòng)態(tài)創(chuàng)立和虛函數(shù)的原理料想到它。至此,永久保存算是寫完了。在此過程中,我一直努力用最少的代碼,詳盡的解釋來說明問題。以前我為本課題寫過一個(gè)版本,并在幾個(gè)論壇上發(fā)表過,但不知怎么在網(wǎng)上遺失〔可能被刪除〕。所以這篇文章是我重寫的版本。記得第一個(gè)版本中,我是對DECLARE_SERIAL/IMPLEMENT_SERIAL和可串行化的數(shù)組及鏈表對象說了許多。這個(gè)版本中我對DECLARE_SERIAL/IMPLEMENT_SERIAL其中奧秘幾乎一句不提,目的是讓大家能找到中心,有更簡潔的永久保存的概念,我覺得這種感覺很好!MFC六大關(guān)鍵技術(shù)之消息映射與命令傳遞題外話:剛開始學(xué)視窗程序設(shè)計(jì)的時(shí)候,我就打印了一本W(wǎng)indows消息詳解,里面列舉了各種已定義消息的意義和作用,共10多頁,在編程的時(shí)候翻翻,有時(shí)覺得很受用。我覺察很多編程的朋友,雖然每天都面對消息,卻很少關(guān)注它。C++程序員有一個(gè)通病,很想寫“自己〞的程序,即每一行代碼都想自己寫出來。如果用了一些庫,總希望能完全理解庫里的類或函數(shù)是怎么一回事,否那么就“不踏實(shí)〞。對于消息,許多朋友只關(guān)心常用的幾個(gè),對其余的漠不關(guān)心。其實(shí),Windows中有很多不常用的消息卻很有用,程序員可能通過響應(yīng)這些消息實(shí)現(xiàn)更簡捷的編程。說到消息,在MFC中,“最熟悉的神秘〞可算是消息映射,那是我們剛開始接觸MFC時(shí)就要面對的東西。有過SDK編程經(jīng)驗(yàn)的朋友轉(zhuǎn)到MFC編程的時(shí)候,一下子覺得什么都變了樣。特別是窗口消息及對消息的處理跟以前相比,更是風(fēng)馬牛不相及的。如文檔不是窗口,是怎樣響應(yīng)命令消息的呢?初次用MFC編程,我們只會(huì)用MFCClassWizard為我們做大量的東西,最主要的是添加消息響應(yīng)。記憶中,如果是自已添加消息響應(yīng),我們應(yīng)何等的小心翼翼,對BEGIN_MESSAGE_MAP〔〕……END_MESSAGE_MAP()更要奉假設(shè)神靈。它就是一個(gè)魔盒子,把我們的咒語放入恰當(dāng)?shù)牡胤?,就?huì)發(fā)生神奇的力量,放錯(cuò)了,自己的程序就連“命〞都沒有。據(jù)說,知道得太多未必是好事。我也曾經(jīng)打算不去理解這神秘的區(qū)域,覺得編程的時(shí)候知道自己想做什么就行了。MFC外表上給我們提供了東西,直觀地說,不但給了我個(gè)一個(gè)程序的外殼,更給我們許多方便。微軟的出發(fā)點(diǎn)可能是希望到達(dá)“傻瓜編程〞的結(jié)果,試想,誰不會(huì)用ClassWizard?大家知道,Windows是基于消息的,有了ClassWizard,你又會(huì)添加類,又會(huì)添加消息,那么你所學(xué)的東西似乎學(xué)到頭了。于是許多程序員認(rèn)為“我們沒有必要走SDK的老路,直接用MFC編程,新的東西通常是簡單、直觀、易學(xué)……〞到你真正想用MFC編程的時(shí)候,你會(huì)覺察光會(huì)ClassWizard的你是多么的愚蠢。MFC不是一個(gè)普通的類庫,普通的類庫我們完全可以不理解里面的細(xì)節(jié),只要知道這些類庫能干什么,接口參數(shù)如何就萬事大吉。如string類,操作順序是定義一個(gè)string對象,然后修改屬性,調(diào)用方法。但對于MFC,你并不是在你的程序中寫上一句“#includeMFC.h〞,然后就在你的程序中用MFC類庫。MFC是一塊包著糖衣的牛骨頭。你很輕松地寫出一個(gè)單文檔窗口,在窗口中間打印一句“IloveMFC!〞,然后,惡夢開始了……想逃避,打算永遠(yuǎn)不去理解MFC內(nèi)幕?門都沒有!在MFC這個(gè)黑暗神秘的洞中,即使你打算摸著石頭前行,也注定找不到出口。對著MFC這塊牛骨頭,微軟溫和、民主地告訴你“你當(dāng)然可以選擇不啃掉它,咳咳……但你必然會(huì)因此而餓死!〞消息映射與命令傳遞表達(dá)了MFC與SDK的不同。在SDK編程中,沒有消息映射的概念,它有明確的回調(diào)函數(shù)中,通過一個(gè)switch語句去判斷收到了何種消息,然后對這個(gè)消息進(jìn)行處理。所以,在SDK編程中,會(huì)發(fā)送消息和在回調(diào)函數(shù)中處理消息就差不多可以寫SDK程序了。在MFC中,看上去發(fā)送消息和處理消息比SDK更簡單、直接,但可惜不直觀。舉個(gè)簡單的例子,如果我們想自定義一個(gè)消息,SDK是非常簡單直觀的,用一條語句:SendMessage(hwnd,message/*一個(gè)大于或等于WM_USER的數(shù)字*/,wparam,lparam),之后就可以在回調(diào)函數(shù)中處理了。但MFC就不同了,因?yàn)槟阃ǔ2恢苯尤ジ膶懘翱诘幕卣{(diào)函數(shù),所以只能亦步亦趨對照原來的MFC代碼,把消息放到恰當(dāng)?shù)牡胤健_@確實(shí)是一樣很痛苦的勞動(dòng)。要了解MFC消息映射原理并不是一件輕松的事情。我們可以逆向思維,想象一下消息映射為我們做了什么工作。MFC在自動(dòng)化給我們提供了很大的方便,比方,所有的MFC窗口都使用同一窗口過程,即所有的MFC窗口都有一個(gè)默認(rèn)的窗口過程。不象在SDK編程中,要為每個(gè)窗口類寫一個(gè)窗口過程。對于消息映射,最直截了當(dāng)?shù)夭聹y是:消息映射就是用一個(gè)數(shù)據(jù)結(jié)構(gòu)把“消息〞與“響應(yīng)消息函數(shù)名〞串聯(lián)起來。這樣,當(dāng)窗口感知消息發(fā)生時(shí),就對結(jié)構(gòu)查找,找到相應(yīng)的消息響應(yīng)函數(shù)執(zhí)行。其實(shí)這個(gè)想法也不能簡單地實(shí)現(xiàn):我們每個(gè)不同的MFC窗口類,對同一種消息,有不同的響應(yīng)方式。即是說,對同一種消息,不同的MFC窗口會(huì)有不同的消息響應(yīng)函數(shù)。這時(shí),大家又想了一個(gè)可行的方法。我們設(shè)計(jì)窗口基類〔CWnd〕時(shí),我們讓它對每種不同的消息都來一個(gè)消息響應(yīng),并把這個(gè)消息響應(yīng)函數(shù)定義為空虛函數(shù)。這樣,從CWnd派生的窗口類對所有消息都有了一個(gè)空響應(yīng),我們要響應(yīng)一個(gè)特定的消息就重載這個(gè)消息響應(yīng)函數(shù)就可以了。但這樣做的結(jié)果,一個(gè)幾乎什么也不做的CWnd類要有幾百個(gè)“多余〞的函數(shù),那怕這些消息響應(yīng)函數(shù)都為純虛函數(shù),每個(gè)CWnd對象也要背負(fù)著一個(gè)巨大的虛擬表,這也是得不償失的。許多朋友在學(xué)習(xí)消息映射時(shí)苦無突破,其原因是一開始就認(rèn)為MFC的消息映射的目的是為了替代SDK窗口過程的編寫——這本來沒有理解錯(cuò)。但他們還有多一層的理解,認(rèn)為既然是替代“舊〞的東西,那么MFC消息映身應(yīng)該是更高層次的抽象、更簡單、更容易認(rèn)識(shí)。但結(jié)果是,如果我們不通過ClassWizard工具,手動(dòng)添加消息是相當(dāng)迷茫的一件事。所以,我們在學(xué)習(xí)MFC消息映射時(shí),首先要弄清楚:消息映射的目的,不是為是更加快捷地向窗口過程添加代碼,而是一種機(jī)制的改變。如果不想改變窗口過程函數(shù),那么應(yīng)該在哪里進(jìn)行消息響應(yīng)呢?許多朋友一知半解地認(rèn)為:我們可以用HOOK技術(shù),搶在消息隊(duì)列前把消息抓取,把消息響應(yīng)提到窗口過程的外面。再者,不同的窗口,會(huì)有不同的感興趣的消息,所以每個(gè)MFC窗口都應(yīng)該有一個(gè)表把感興趣的消息和相應(yīng)消息響應(yīng)函數(shù)連系起來。然后得出——消息映射機(jī)制執(zhí)行步驟是:當(dāng)消息發(fā)生,我們用HOOK技術(shù)把本發(fā)送到窗口過程的消息抓獲,然后對照一下MFC窗口的消息映射表,如果是表里面有的消息,就執(zhí)行其對應(yīng)的函數(shù)。當(dāng)然,用HOOK技術(shù),我們理論上可以在不改變窗口過程函數(shù)的情況下,可以完成消息響應(yīng)。MFC確實(shí)是這樣做,但實(shí)際操作起來可能跟你的想象差異很大?,F(xiàn)在我們來編寫消息映射表,我們先定義一個(gè)結(jié)構(gòu),這個(gè)結(jié)構(gòu)至少有兩個(gè)項(xiàng):一是消息ID,二是響應(yīng)該消息的函數(shù)。如下:structAFX_MSGMAP_ENTRY{UINTnMessage;//感興趣的消息AFX_PMSGpfn;//響應(yīng)以上消息的函數(shù)指針}當(dāng)然,只有兩個(gè)成員的結(jié)構(gòu)連接起來的消息映射表是不成熟的。Windows消息分為標(biāo)準(zhǔn)消息、控件消息和命令消息,每類型的消息包含數(shù)百不同ID、不同意義、不同參數(shù)的消息。我們要準(zhǔn)確地判別發(fā)生了何種消息,必須再增加幾個(gè)成員。還有,對于AFX_PMSGpfn,實(shí)際上等于作以下聲明:void(CCmdTarget::*pfn)();(提示:AFX_PMSG為類型標(biāo)識(shí),具體聲明是:typedefvoid(AFX_MSG_CALLCCmdTarget::*AFX_PMSG)(void);)pfn是不一不帶參數(shù)和返回值的CCmdTarget類型函數(shù)指針,只能指向CCmdTarget類中不帶參數(shù)和返回值的成員函數(shù),這樣pfn更為通用,但我們響應(yīng)消息的函數(shù)許多需要傳入?yún)?shù)的。為了解決這個(gè)矛盾,我們還要增加一個(gè)表示參數(shù)類型的成員。當(dāng)然,還有其它……最后,MFC我們消息映射表成員結(jié)構(gòu)如下定義:structAFX_MSGMAP_ENTRY{UINTnMessage;//Windows消息IDUINTnCode;//控制消息的通知碼UINTnID;//命令消息ID范圍的起始值UINTnLastID;//命令消息ID范圍的終點(diǎn)UINTnSig;//消息的動(dòng)作標(biāo)識(shí)AFX_PMSGpfn;};有了以上消息映射表成員結(jié)構(gòu),我們就可以定義一個(gè)AFX_MSGMAP_ENTRY類型的數(shù)組,用來容納消息映射項(xiàng)。定義如下:AFX_MSGMAP_ENTRY_messageEntries[];但這樣還不夠,每個(gè)AFX_MSGMAP_ENTRY數(shù)組,只能保存著當(dāng)前類感興趣的消息,而這僅僅是我們想處理的消息中的一局部。對于一個(gè)MFC程序,一般有多個(gè)窗口類,里面都應(yīng)該有一個(gè)AFX_MSGMAP_ENTRY數(shù)組。我們知道,MFC還有一個(gè)消息傳遞機(jī)制,可以把自己不處理的消息傳送給別的類進(jìn)行處理。為了能查找各下MFC對象的消息映射表,我們還要增加一個(gè)結(jié)構(gòu),把所有的AFX_MSGMAP_ENTRY數(shù)組串聯(lián)起來。于是,我們定義了一個(gè)新結(jié)構(gòu)體:structAFX_MSGMAP{constAFX_MSGMAP*pBaseMap;//指向別的類的AFX_MSGMAP對象constAFX_MSGMAP_ENTRY*lpEntries;//指向自身的消息表};之后,在

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論