• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            road420

            導(dǎo)航

            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            統(tǒng)計(jì)

            常用鏈接

            留言簿(2)

            隨筆檔案

            文章檔案

            搜索

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            #

            進(jìn)程通信

            Win32應(yīng)用程序中進(jìn)程間通信方法分析與比較

            來(lái)源:Intetnet

            1 進(jìn)程與進(jìn)程通信

            進(jìn)程是裝入內(nèi)存并準(zhǔn)備執(zhí)行的程序,每個(gè)進(jìn)程都有私有的虛擬地址空間,由代碼、數(shù)據(jù)以及它可利用的系統(tǒng)資源(如文件、管道等)組成。多進(jìn)程/多線程是Windows操作系統(tǒng)的一個(gè)基本特征。Microsoft Win32應(yīng)用編程接口(Application Programming Interface, API)提供了大量支持應(yīng)用程序間數(shù)據(jù)共享和交換的機(jī)制,這些機(jī)制行使的活動(dòng)稱(chēng)為進(jìn)程間通信(InterProcess Communication, IPC),進(jìn)程通信就是指不同進(jìn)程間進(jìn)行數(shù)據(jù)共享和數(shù)據(jù)交換。
            正因?yàn)槭褂肳in32 API進(jìn)行進(jìn)程通信方式有多種,如何選擇恰當(dāng)?shù)耐ㄐ欧绞骄统蔀閼?yīng)用開(kāi)發(fā)中的一個(gè)重要問(wèn)題,下面本文將對(duì)Win32中進(jìn)程通信的幾種方法加以分析和比較。

            2 進(jìn)程通信方法

            2.1

            文件映射


            文件映射(Memory-Mapped Files)能使進(jìn)程把文件內(nèi)容當(dāng)作進(jìn)程地址區(qū)間一塊內(nèi)存那樣來(lái)對(duì)待。因此,進(jìn)程不必使用文件I/O操作,只需簡(jiǎn)單的指針操作就可讀取和修改文件的內(nèi)容。
            Win32 API允許多個(gè)進(jìn)程訪問(wèn)同一文件映射對(duì)象,各個(gè)進(jìn)程在它自己的地址空間里接收內(nèi)存的指針。通過(guò)使用這些指針,不同進(jìn)程就可以讀或修改文件的內(nèi)容,實(shí)現(xiàn)了對(duì)文件中數(shù)據(jù)的共享。
            應(yīng)用程序有三種方法來(lái)使多個(gè)進(jìn)程共享一個(gè)文件映射對(duì)象。
            (1)繼承:第一個(gè)進(jìn)程建立文件映射對(duì)象,它的子進(jìn)程繼承該對(duì)象的句柄。
            (2)命名文件映射:第一個(gè)進(jìn)程在建立文件映射對(duì)象時(shí)可以給該對(duì)象指定一個(gè)名字(可與文件名不同)。第二個(gè)進(jìn)程可通過(guò)這個(gè)名字打開(kāi)此文件映射對(duì)象。另外,第一個(gè)進(jìn)程也可以通過(guò)一些其它IPC機(jī)制(有名管道、郵件槽等)把名字傳給第二個(gè)進(jìn)程。
            (3)句柄復(fù)制:第一個(gè)進(jìn)程建立文件映射對(duì)象,然后通過(guò)其它IPC機(jī)制(有名管道、郵件槽等)把對(duì)象句柄傳遞給第二個(gè)進(jìn)程。第二個(gè)進(jìn)程復(fù)制該句柄就取得對(duì)該文件映射對(duì)象的訪問(wèn)權(quán)限。
            文件映射是在多個(gè)進(jìn)程間共享數(shù)據(jù)的非常有效方法,有較好的安全性。但文件映射只能用于本地機(jī)器的進(jìn)程之間,不能用于網(wǎng)絡(luò)中,而開(kāi)發(fā)者還必須控制進(jìn)程間的同步
            2.2

            共享內(nèi)存


            Win32 API中共享內(nèi)存(Shared Memory)實(shí)際就是文件映射的一種特殊情況。進(jìn)程在創(chuàng)建文件映射對(duì)象時(shí)用0xFFFFFFFF來(lái)代替文件句柄(HANDLE),就表示了對(duì)應(yīng)的文件映射對(duì)象是從操作系統(tǒng)頁(yè)面文件訪問(wèn)內(nèi)存,其它進(jìn)程打開(kāi)該文件映射對(duì)象就可以訪問(wèn)該內(nèi)存塊。由于共享內(nèi)存是用文件映射實(shí)現(xiàn)的,所以它也有較好的安全性,也只能運(yùn)行于同一計(jì)算機(jī)上的進(jìn)程之間。
            2.3

            匿名管道


            管道(Pipe)是一種具有兩個(gè)端點(diǎn)的通信通道:有一端句柄的進(jìn)程可以和有另一端句柄的進(jìn)程通信。管道可以是單向-一端是只讀的,另一端點(diǎn)是只寫(xiě)的;也可以是雙向的一管道的兩端點(diǎn)既可讀也可寫(xiě)。
            匿名管道(Anonymous Pipe)是在父進(jìn)程和子進(jìn)程之間,或同一父進(jìn)程的兩個(gè)子進(jìn)程之間傳輸數(shù)據(jù)的無(wú)名字的單向管道。通常由父進(jìn)程創(chuàng)建管道,然后由要通信的子進(jìn)程繼承通道的讀端點(diǎn)句柄或?qū)懚它c(diǎn)句柄,然后實(shí)現(xiàn)通信。父進(jìn)程還可以建立兩個(gè)或更多個(gè)繼承匿名管道讀和寫(xiě)句柄的子進(jìn)程。這些子進(jìn)程可以使用管道直接通信,不需要通過(guò)父進(jìn)程。
            匿名管道是單機(jī)上實(shí)現(xiàn)子進(jìn)程標(biāo)準(zhǔn)I/O重定向的有效方法,它不能在網(wǎng)上使用,也不能用于兩個(gè)不相關(guān)的進(jìn)程之間。
            2.4

            命名管道


            命名管道(Named Pipe)是服務(wù)器進(jìn)程和一個(gè)或多個(gè)客戶(hù)進(jìn)程之間通信的單向或雙向管道。不同于匿名管道的是命名管道可以在不相關(guān)的進(jìn)程之間和不同計(jì)算機(jī)之間使用,服務(wù)器建立命名管道時(shí)給它指定一個(gè)名字,任何進(jìn)程都可以通過(guò)該名字打開(kāi)管道的另一端,根據(jù)給定的權(quán)限和服務(wù)器進(jìn)程通信。
            命名管道提供了相對(duì)簡(jiǎn)單的編程接口,使通過(guò)網(wǎng)絡(luò)傳輸數(shù)據(jù)并不比同一計(jì)算機(jī)上兩進(jìn)程之間通信更困難,不過(guò)如果要同時(shí)和多個(gè)進(jìn)程通信它就力不從心了。
            2.5

            郵件槽


            郵件槽(Mailslots)提供進(jìn)程間單向通信能力,任何進(jìn)程都能建立郵件槽成為郵件槽服務(wù)器。其它進(jìn)程,稱(chēng)為郵件槽客戶(hù),可以通過(guò)郵件槽的名字給郵件槽服務(wù)器進(jìn)程發(fā)送消息。進(jìn)來(lái)的消息一直放在郵件槽中,直到服務(wù)器進(jìn)程讀取它為止。一個(gè)進(jìn)程既可以是郵件槽服務(wù)器也可以是郵件槽客戶(hù),因此可建立多個(gè)郵件槽實(shí)現(xiàn)進(jìn)程間的雙向通信。
            通過(guò)郵件槽可以給本地計(jì)算機(jī)上的郵件槽、其它計(jì)算機(jī)上的郵件槽或指定網(wǎng)絡(luò)區(qū)域中所有計(jì)算機(jī)上有同樣名字的郵件槽發(fā)送消息。廣播通信的消息長(zhǎng)度不能超過(guò)400字節(jié),非廣播消息的長(zhǎng)度則受郵件槽服務(wù)器指定的最大消息長(zhǎng)度的限制。
            郵件槽與命名管道相似,不過(guò)它傳輸數(shù)據(jù)是通過(guò)不可靠的數(shù)據(jù)報(bào)(如TCP/IP協(xié)議中的UDP包)完成的,一旦網(wǎng)絡(luò)發(fā)生錯(cuò)誤則無(wú)法保證消息正確地接收,而命名管道傳輸數(shù)據(jù)則是建立在可靠連接基礎(chǔ)上的。不過(guò)郵件槽有簡(jiǎn)化的編程接口和給指定網(wǎng)絡(luò)區(qū)域內(nèi)的所有計(jì)算機(jī)廣播消息的能力,所以郵件槽不失為應(yīng)用程序發(fā)送和接收消息的另一種選擇。
            2.6

            剪貼板


            剪貼板(Clipped Board)實(shí)質(zhì)是Win32 API中一組用來(lái)傳輸數(shù)據(jù)的函數(shù)和消息,為Windows應(yīng)用程序之間進(jìn)行數(shù)據(jù)共享提供了一個(gè)中介,Windows已建立的剪切(復(fù)制)-粘貼的機(jī)制為不同應(yīng)用程序之間共享不同格式數(shù)據(jù)提供了一條捷徑。當(dāng)用戶(hù)在應(yīng)用程序中執(zhí)行剪切或復(fù)制操作時(shí),應(yīng)用程序把選取的數(shù)據(jù)用一種或多種格式放在剪貼板上。然后任何其它應(yīng)用程序都可以從剪貼板上拾取數(shù)據(jù),從給定格式中選擇適合自己的格式。
            剪貼板是一個(gè)非常松散的交換媒介,可以支持任何數(shù)據(jù)格式,每一格式由一無(wú)符號(hào)整數(shù)標(biāo)識(shí),對(duì)標(biāo)準(zhǔn)(預(yù)定義)剪貼板格式,該值是Win32 API定義的常量;對(duì)非標(biāo)準(zhǔn)格式可以使用Register Clipboard Format函數(shù)注冊(cè)為新的剪貼板格式。利用剪貼板進(jìn)行交換的數(shù)據(jù)只需在數(shù)據(jù)格式上一致或都可以轉(zhuǎn)化為某種格式就行。但剪貼板只能在基于Windows的程序中使用,不能在網(wǎng)絡(luò)上使用。
            2.7 動(dòng)態(tài)數(shù)據(jù)交換
            動(dòng)態(tài)數(shù)據(jù)交換(DDE)是使用共享內(nèi)存在應(yīng)用程序之間進(jìn)行數(shù)據(jù)交換的一種進(jìn)程間通信形式。應(yīng)用程序可以使用DDE進(jìn)行一次性數(shù)據(jù)傳輸,也可以當(dāng)出現(xiàn)新數(shù)據(jù)時(shí),通過(guò)發(fā)送更新值在應(yīng)用程序間動(dòng)態(tài)交換數(shù)據(jù)。
            DDE和剪貼板一樣既支持標(biāo)準(zhǔn)數(shù)據(jù)格式(如文本、位圖等),又可以支持自己定義的數(shù)據(jù)格式。但它們的數(shù)據(jù)傳輸機(jī)制卻不同,一個(gè)明顯區(qū)別是剪貼板操作幾乎總是用作對(duì)用戶(hù)指定操作的一次性應(yīng)答-如從菜單中選擇Paste命令。盡管DDE也可以由用戶(hù)啟動(dòng),但它繼續(xù)發(fā)揮作用一般不必用戶(hù)進(jìn)一步干預(yù)。DDE有三種數(shù)據(jù)交換方式:
            (1) 冷鏈:數(shù)據(jù)交換是一次性數(shù)據(jù)傳輸,與剪貼板相同。
            (2) 溫鏈:當(dāng)數(shù)據(jù)交換時(shí)服務(wù)器通知客戶(hù),然后客戶(hù)必須請(qǐng)求新的數(shù)據(jù)。
            (3) 熱鏈:當(dāng)數(shù)據(jù)交換時(shí)服務(wù)器自動(dòng)給客戶(hù)發(fā)送數(shù)據(jù)。
            DDE交換可以發(fā)生在單機(jī)或網(wǎng)絡(luò)中不同計(jì)算機(jī)的應(yīng)用程序之間。開(kāi)發(fā)者還可以定義定制的DDE數(shù)據(jù)格式進(jìn)行應(yīng)用程序之間特別目的IPC,它們有更緊密耦合的通信要求。大多數(shù)基于Windows的應(yīng)用程序都支持DDE。
            2.8 對(duì)象連接與嵌入
            應(yīng)用程序利用對(duì)象連接與嵌入(OLE)技術(shù)管理復(fù)合文檔(由多種數(shù)據(jù)格式組成的文檔),OLE提供使某應(yīng)用程序更容易調(diào)用其它應(yīng)用程序進(jìn)行數(shù)據(jù)編輯的服務(wù)。例如,OLE支持的字處理器可以嵌套電子表格,當(dāng)用戶(hù)要編輯電子表格時(shí)OLE庫(kù)可自動(dòng)啟動(dòng)電子表格編輯器。當(dāng)用戶(hù)退出電子表格編輯器時(shí),該表格已在原始字處理器文檔中得到更新。在這里電子表格編輯器變成了字處理器的擴(kuò)展,而如果使用DDE,用戶(hù)要顯式地啟動(dòng)電子表格編輯器。
            同DDE技術(shù)相同,大多數(shù)基于Windows的應(yīng)用程序都支持OLE技術(shù)。
            2.9 動(dòng)態(tài)連接庫(kù)
            Win32動(dòng)態(tài)連接庫(kù)(DLL)中的全局?jǐn)?shù)據(jù)可以被調(diào)用DLL的所有進(jìn)程共享,這就又給進(jìn)程間通信開(kāi)辟了一條新的途徑,當(dāng)然訪問(wèn)時(shí)要注意同步問(wèn)題。
            雖然可以通過(guò)DLL進(jìn)行進(jìn)程間數(shù)據(jù)共享,但從數(shù)據(jù)安全的角度考慮,我們并不提倡這種方法,使用帶有訪問(wèn)權(quán)限控制的共享內(nèi)存的方法更好一些。
            2.10 遠(yuǎn)程過(guò)程調(diào)用
            Win32 API提供的遠(yuǎn)程過(guò)程調(diào)用(RPC)使應(yīng)用程序可以使用遠(yuǎn)程調(diào)用函數(shù),這使在網(wǎng)絡(luò)上用RPC進(jìn)行進(jìn)程通信就像函數(shù)調(diào)用那樣簡(jiǎn)單。RPC既可以在單機(jī)不同進(jìn)程間使用也可以在網(wǎng)絡(luò)中使用。
            由于Win32 API提供的RPC服從OSF-DCE(Open Software Foundation Distributed Computing Environment)標(biāo)準(zhǔn)。所以通過(guò)Win32 API編寫(xiě)的RPC應(yīng)用程序能與其它操作系統(tǒng)上支持DEC的RPC應(yīng)用程序通信。使用RPC開(kāi)發(fā)者可以建立高性能、緊密耦合的分布式應(yīng)用程序。
            2.11 NetBios函數(shù)
            Win32 API提供NetBios函數(shù)用于處理低級(jí)網(wǎng)絡(luò)控制,這主要是為IBM NetBios系統(tǒng)編寫(xiě)與Windows的接口。除非那些有特殊低級(jí)網(wǎng)絡(luò)功能要求的應(yīng)用程序,其它應(yīng)用程序最好不要使用NetBios函數(shù)來(lái)進(jìn)行進(jìn)程間通信。
            2.12

            Sockets


            Windows Sockets規(guī)范是以U.C.Berkeley大學(xué)BSD UNIX中流行的Socket接口為范例定義的一套Windows下的網(wǎng)絡(luò)編程接口。除了Berkeley Socket原有的庫(kù)函數(shù)以外,還擴(kuò)展了一組針對(duì)Windows的函數(shù),使程序員可以充分利用Windows的消息機(jī)制進(jìn)行編程。
            現(xiàn)在通過(guò)Sockets實(shí)現(xiàn)進(jìn)程通信的網(wǎng)絡(luò)應(yīng)用越來(lái)越多,這主要的原因是Sockets的跨平臺(tái)性要比其它IPC機(jī)制好得多,另外WinSock 2.0不僅支持TCP/IP協(xié)議,而且還支持其它協(xié)議(如IPX)。Sockets的唯一缺點(diǎn)是它支持的是底層通信操作,這使得在單機(jī)的進(jìn)程間進(jìn)行簡(jiǎn)單數(shù)據(jù)傳遞不太方便,這時(shí)使用下面將介紹的WM_COPYDATA消息將更合適些。
            2.13 WM_COPYDATA消息
            WM_COPYDATA是一種非常強(qiáng)大卻鮮為人知的消息。當(dāng)一個(gè)應(yīng)用向另一個(gè)應(yīng)用傳送數(shù)據(jù)時(shí),發(fā)送方只需使用調(diào)用SendMessage函數(shù),參數(shù)是目的窗口的句柄、傳遞數(shù)據(jù)的起始地址、WM_COPYDATA消息。接收方只需像處理其它消息那樣處理WM_COPY DATA消息,這樣收發(fā)雙方就實(shí)現(xiàn)了數(shù)據(jù)共享。
            WM_COPYDATA是一種非常簡(jiǎn)單的方法,它在底層實(shí)際上是通過(guò)文件映射來(lái)實(shí)現(xiàn)的。它的缺點(diǎn)是靈活性不高,并且它只能用于Windows平臺(tái)的單機(jī)環(huán)境下。

            3 結(jié)束語(yǔ)

            Win32 API為應(yīng)用程序?qū)崿F(xiàn)進(jìn)程間通信提供了如此多種選擇方案,那么開(kāi)發(fā)者如何進(jìn)行選擇呢?通常在決定使用哪種IPC方法之前應(yīng)考慮下一些問(wèn)題,如應(yīng)用程序是在網(wǎng)絡(luò)環(huán)境下還是在單機(jī)環(huán)境下工作等。

            posted @ 2009-10-23 09:01 深邃者 閱讀(219) | 評(píng)論 (0)編輯 收藏

            vs2005 調(diào)試

            1,斷點(diǎn)設(shè)置有技巧:

            1)設(shè)置條件斷點(diǎn),比如i==10,變量改變時(shí)斷點(diǎn);

            2)如何讓斷點(diǎn)在指定的命中次數(shù)或者大于某個(gè)次數(shù)時(shí)觸發(fā)呢?方法是設(shè)定幾個(gè)斷點(diǎn)的HitCount,右鍵單擊斷點(diǎn),在彈出菜單中選擇Hit Count;

            3)When Hit,這個(gè)選項(xiàng)可以讓我們?cè)诿袛帱c(diǎn)后做一些事情,包括輸出一些內(nèi)容,或者調(diào)用宏,比如輸出一個(gè)程序中變量的值;

            4)利用斷點(diǎn)的Filter功能,比如我希望斷點(diǎn)只有被機(jī)器名為yizhu的機(jī)器訪問(wèn)才能觸發(fā);

            具體參見(jiàn):一篇介紹VS2005調(diào)試斷點(diǎn)技巧的文章

            2. 怎樣判斷加載的dll的正確性?

               調(diào)試時(shí),打開(kāi)Debug->Window->Modules,在窗口中顯示的就是當(dāng)前進(jìn)程加載的所有dll及其詳細(xì)信息,如果

            斷點(diǎn)無(wú)法擊中,可以檢查這里,看是否有匹配的pdb文件或者是加載了錯(cuò)誤的dll

            3. 已經(jīng)開(kāi)始調(diào)試的工程加入另外的進(jìn)程并且調(diào)試

               如果你在調(diào)試客戶(hù)端,但是服務(wù)器需要調(diào)試,那么使用菜單中的Tools-> Attach to process進(jìn)行進(jìn)程掛接,這種方法可以掛接所有windows下的程序,能否調(diào)試,就看其是否調(diào)試版和有調(diào)試用的PDB文件

            4. 同時(shí)啟動(dòng)多進(jìn)程進(jìn)行調(diào)試

              在Solution的屬性中的Common Properties->Startup Project。選擇Multiple startup projects。這個(gè)選項(xiàng)是可以記憶的,下次打開(kāi)可以直接調(diào)試,非常方便

            5.調(diào)試Windows Service

             MSDN推薦的方法
            1、調(diào)試windows服務(wù)的初始化、啟動(dòng)
              另寫(xiě)一個(gè)程序控制服務(wù)的初始化和啟動(dòng)
              注意:OnStart里寫(xiě)Log, OnStart里要在30秒返回. 不然啟動(dòng)就失敗了! 所以O(shè)nStart里不要放太多代碼! 可以用異步或線程.
            2、調(diào)試windows服務(wù)的其他方面
            1 ) 安裝您的服務(wù) : intallutils xx.exe
            2) 可從“服務(wù)控制管理器”、“服務(wù)器資源管理器”或代碼啟動(dòng)服務(wù)
            3) vs: 設(shè)置相關(guān)斷點(diǎn),啟動(dòng)調(diào)試,再在工具欄中選擇 調(diào)試->附加到進(jìn)程..., 選擇您的服務(wù), 確定。
            3、trace方法
            1)添加調(diào)試方法

            private static void DebugRun(string[] args)

            2)改寫(xiě)程序入口為如下:

            public static void Main(string[] args)
            {
            #if DEBUG
            DebugRun(args);
            #else
            /*
            初始化服務(wù)
            */
            #endif
            }

            3)加入2種調(diào)試代碼

            EventLog.WriteEntry("...");
            System.Diagnostics.Debug.WriteLine("...");

             

            6,遠(yuǎn)程調(diào)試技術(shù)


                顧名思義,就是要調(diào)試的程序和調(diào)試器本身并不在一臺(tái)機(jī)器上。由于虛擬機(jī)技術(shù)的盛行,在虛擬機(jī)里面運(yùn)行待調(diào)試的程序,而在外面運(yùn)行調(diào)試器,也是一種比較流行的做法。

            1 為什么使用遠(yuǎn)程調(diào)試
               遠(yuǎn)程調(diào)試有如下好處:
                a. 能讓產(chǎn)品運(yùn)行在一個(gè)比較干凈的環(huán)境。有的時(shí)候如果產(chǎn)品安裝在一個(gè)裝好集成環(huán)境的機(jī)器上,某些bug并不能顯示出來(lái)。
                b. 易于部署調(diào)試環(huán)境。很多產(chǎn)品都非常復(fù)雜,比如很多都以service方式運(yùn)行或者要load很復(fù)雜的resource,想在調(diào)試器里面直接按F5運(yùn)行,越來(lái)越難。
                c. 對(duì)于游戲等全屏方式運(yùn)行的程序,尤其有用。以前我對(duì)調(diào)試directx程序非常頭疼。

            2 怎樣使用遠(yuǎn)程調(diào)試
               使用vs2005進(jìn)行遠(yuǎn)程調(diào)試,詳細(xì)的介紹參考:http://support.microsoft.com/kb/910448

               簡(jiǎn)單的來(lái)說(shuō),
               1)在被調(diào)試的機(jī)器上面運(yùn)行Msvsmon.exe
               2)在調(diào)試機(jī)器上面運(yùn)行vs2005,并attach到遠(yuǎn)程機(jī)器的某個(gè)進(jìn)程

            3 注意事項(xiàng)

               a. 設(shè)置好正確的權(quán)限
               被調(diào)試機(jī)器和調(diào)試機(jī)器需要互相信任的權(quán)限(two-way)。如果兩臺(tái)機(jī)器在同一個(gè)workgroup,讓兩臺(tái)機(jī)器擁有一個(gè)相同的賬號(hào)和密碼,然后以這個(gè)賬號(hào)運(yùn)行。如果兩臺(tái)機(jī)器在一個(gè)域里面,比較簡(jiǎn)單,Msvsmon可以設(shè)置權(quán)限。如果一臺(tái)機(jī)器在domain里面,另外一臺(tái)不在,同樣是讓兩臺(tái)機(jī)器擁有一個(gè)相同的賬號(hào)和密碼,然后以這個(gè)賬號(hào)運(yùn)行。
               對(duì)于Windows XP要特別注意一下,設(shè)置匿名的訪問(wèn)權(quán)限才可以work , http://support.microsoft.com/kb/908099

               b.設(shè)置好symbol
               什么,你不知道什么是symbol?簡(jiǎn)單的來(lái)說(shuō),symbol file(*.pdb) is for source-level debugging. VS2005就是靠它來(lái)調(diào)試exe的。默認(rèn)情況下debug版本生成的,而release版本不生成pdb.設(shè)置好 [Project proerties]-[C/C++]-[Debug Information Format]-Program Database就可以了。
               為了減少symbol方面的麻煩,最簡(jiǎn)單的做法是讓被調(diào)試機(jī)器上的binary版本和本地compile出來(lái)保持一致。

            posted @ 2009-10-23 08:59 深邃者 閱讀(839) | 評(píng)論 (0)編輯 收藏

            windbg調(diào)試器

            WinDbg調(diào)試器

            你可以從微軟網(wǎng)站上下載到的調(diào)試器:

            · KD-內(nèi)核調(diào)試器。你可以用它來(lái)調(diào)試藍(lán)屏一類(lèi)的系統(tǒng)問(wèn)題。如果是開(kāi)發(fā)設(shè)備驅(qū)動(dòng)程序是少不了它的。

            · CDB-命令行調(diào)試器。這是一個(gè)命令行程序

            · NTSD-NT調(diào)試器。這是一個(gè)用戶(hù)模式調(diào)試器,可以用來(lái)調(diào)試用戶(hù)模式應(yīng)用程序。它實(shí)際上是一個(gè)CDB的windows UI增強(qiáng)。

            · WinDbg-用一個(gè)漂亮的UI包裝了KD和NTSD。WinDbg即可以調(diào)試內(nèi)核模式,也可以調(diào)試用戶(hù)模式程序。

            ·  VS, VS.net-使用同KD和NTSD相同的調(diào)試引擎,并且相比于同樣用于調(diào)試目的的WinDbg,提供了功能更豐富的界面。

             

            WinDbg實(shí)際上包裝了NTSD和KD并且提供了一個(gè)更好用的用戶(hù)界面。它也提供了命令行開(kāi)關(guān),比如最小化啟動(dòng)(-m),附加到一PID指定的進(jìn)程(-p)以及自動(dòng)打開(kāi)崩潰文件(-z)。它支持三種類(lèi)型的命令。

            ·  Regular commands(比如: k) 用來(lái)調(diào)試進(jìn)程

            ·  Dot commands(比如:.sympath)用來(lái)控制調(diào)試器

            ·  Extension commands(比如: !handle)-這些命令屬于可以用來(lái)添加到WinDbg的自定義命令;它們用擴(kuò)展DLL的輸出函數(shù)來(lái)實(shí)現(xiàn)。

             

            PDB文件

                 PDB文件, 是鏈接器生成程序數(shù)據(jù)庫(kù)文件(Program database files)。私有的PDB文件包括私有以及公有符號(hào),源代碼行號(hào),類(lèi)型,局部以及全局變量。公有的PDB文件不包含類(lèi)型,局部變量以及源代碼行號(hào)信息。

            配置WinDbg


                  運(yùn)行WinDbg->菜單->File->Symbol File Path->按照下面的方法設(shè)置_NT_SYMBOL_PATH變量:
            在彈出的框中輸入“C:\MyCodesSymbols; SRV*C:\MyLocalSymbols*http://msdl.microsoft.com/download/symbols”(按照這樣設(shè)置,WinDbg將先從本地文件夾C:\MyCodesSymbols中查找Symbol,如果找不到,則自動(dòng)從MS的Symbol Server上下載Symbols)。另一種做法是從這個(gè)Symbol下載地址中http://www.microsoft.com/whdc/devtools/debugging/symbolpkg.mspx,下載相應(yīng)操作系統(tǒng)所需要的完整的Symbol安裝包,并進(jìn)行安裝,例如我將其安裝在D:\WINDOWS\Symbols,在該框中輸入“D:\WINDOWS\Symbols”。(這里要注意下載的Symbols的版本一定要正確,在我的Win2003+Sp1上,我曾經(jīng)以為安裝Win2003+Sp2的Symbols可能會(huì)牛×點(diǎn),但結(jié)果證明我錯(cuò)了,用WinDbg打開(kāi)可執(zhí)行文件時(shí),提示“PDB symbol for mscorwks.dll not loaded;Defaulted to export symbols for ntdll.dll”的錯(cuò)誤,我有重新裝上Win2003+Sp1的Symbols, 現(xiàn)在一切運(yùn)行正常^_^)

            set _NT_SYMBOL_PATH=srv*C:\MySymbols*http://msdl.microsoft.com/download/symbols

             

            WINDBG命令

            調(diào)試前的必備工作

            在開(kāi)始調(diào)試前首先要做的工作是設(shè)置好符號(hào)(Symbols)路徑。沒(méi)有符號(hào),你看到的調(diào)用堆棧基本上毫無(wú)意義。Microsoft的操作系統(tǒng)符號(hào)文件(PDB)是對(duì)外公開(kāi)的。另外請(qǐng)注意在編譯你自己的程序選擇生成PDB文件的選項(xiàng)。如果設(shè)置好符號(hào)路徑后,調(diào)用堆棧看起來(lái)還是不對(duì)。可以使用lm, !sym noisy, !reload 等命令來(lái)驗(yàn)證符號(hào)路徑是否正確。

            Windbg也支持源碼級(jí)的調(diào)試。在開(kāi)始源碼調(diào)試前,你需要用.srcpath設(shè)置源代碼路徑。如果你是在生成所執(zhí)行代碼的機(jī)器上進(jìn)行調(diào)試,符號(hào)文件中的源碼路徑會(huì)指向正確的位置,所以不需要設(shè)置源代碼路徑。如果所執(zhí)行代碼是在另一臺(tái)機(jī)器上生成的,你可以將所用的源碼拷貝(保持原有的目錄結(jié)構(gòu))的一個(gè)可以訪問(wèn)的文件夾(可以是網(wǎng)絡(luò)路徑)并將源代碼路徑設(shè)為該文件夾的路徑。注意如果是遠(yuǎn)程調(diào)試,你需要使用.lsrcpath來(lái)設(shè)置源碼路徑。

             

            2)啟動(dòng)Debugger

            Windbg可以用于如下三種調(diào)試:

            (1)遠(yuǎn)程調(diào)試:你可以從機(jī)器A上調(diào)試在機(jī)器B上執(zhí)行的程序。具體步驟如下:
              在機(jī)器B上啟動(dòng)一個(gè)調(diào)試窗口(Debug Session)。你可以直接在Windbg下運(yùn)行一個(gè)程序或者將Windbg附加(Attach)到一個(gè)進(jìn)程。
              在機(jī)器B的Windbg命令窗口上啟動(dòng)一個(gè)遠(yuǎn)程調(diào)試接口(remote):

                .server npipe:pipe=PIPE_NAME

                 PIPE_NAME是該接口的名字。
            在機(jī)器A上運(yùn)行:

            windbg –remote npipe:server=SERVER_NAME,pipe=PIPE_NAME

            SERVER_NAME是機(jī)器B的名字。

            Dump文件調(diào)試:如果在你的客戶(hù)的機(jī)器上出現(xiàn)問(wèn)題,你可能不能使用遠(yuǎn)程調(diào)試來(lái)解決問(wèn)題。你可以要求你的用戶(hù)將Windbg附加到出現(xiàn)問(wèn)題的進(jìn)程上,然后在命令窗口中輸入:

            .dump /ma File Name

            創(chuàng)建一個(gè)Dump文件。在得到Dump文件后,使用如下的命令來(lái)打開(kāi)它:

            windbg –z DUMP_FILE_NAME

            (2)本地進(jìn)程調(diào)試:你可以在Windbg下直接運(yùn)行一個(gè)程序:

            Windbg “path to executable” arguments     

                也可以將Windbg附加到一個(gè)正在運(yùn)行的程序:

                Windbg –p “process id”  

            Windbg –pn “process name”

                注意有一種非侵入(Noninvasive)模式可以用來(lái)檢查一個(gè)進(jìn)程的狀態(tài)并不進(jìn)程的執(zhí)行。當(dāng)然在這種模式下無(wú)法控制被調(diào)試程序的執(zhí)行。這種模式也可以用于查看一個(gè)已經(jīng)在Debugger控制下運(yùn)行的進(jìn)程。具體命令如下:

                Windbg –pv –p “process id” 

            Windbg –pv –pn “process name” 

            (3)調(diào)試多個(gè)進(jìn)程和線程

            如果你想控制一個(gè)進(jìn)程以及它的子進(jìn)程的執(zhí)行,在Windbg的命令行上加上-o選項(xiàng)。Windbg中還有一個(gè)新的命令.childdbg 可以用來(lái)控制子進(jìn)程的調(diào)試。如果你同時(shí)調(diào)試幾個(gè)進(jìn)程,可以使用 | 命令來(lái)顯示并切換到不同的進(jìn)程。

            在同一個(gè)進(jìn)程中可能有多個(gè)線程。~命令可以用來(lái)顯示和切換線程。

            .hh keyword  如何得到幫助

            靜態(tài)命令:

            顯示調(diào)用堆棧:在連接到一個(gè)調(diào)試窗口后,首先要知道的就是程序當(dāng)前的執(zhí)行情況k* 命令顯示當(dāng)前線程的堆棧。~*kb會(huì)顯示所有線程的調(diào)用堆棧。如果堆棧太長(zhǎng),Windbg只會(huì)顯示堆棧的一部分。.kframes可以用來(lái)設(shè)置缺省顯示框架數(shù)。

            顯示局部變量:接下來(lái)要做通常是用dv顯示局部變量的信息。CTRL+ALT+V可以切換到更詳細(xì)的顯示模式。關(guān)于dv要注意的是在優(yōu)化過(guò)的代碼中dv的輸出極有可能是不準(zhǔn)確的。這時(shí)后你能做的就是閱讀匯編代碼來(lái)發(fā)現(xiàn)你感興趣的值是否存儲(chǔ)在寄存器中或堆棧上。有時(shí)后當(dāng)前的框架(Frame)上可能找不到你想知道的數(shù)據(jù)。如果該數(shù)據(jù)是作為參數(shù)傳到當(dāng)前的方法中的,可以讀一讀上一個(gè)或幾個(gè)框架的匯編代碼,有可能該數(shù)據(jù)還在堆棧的某個(gè)地址上。靜態(tài)變量是儲(chǔ)存在固定地址中的,所以找出靜態(tài)變量的值較為容易。.Frame(或者在調(diào)用堆棧窗口中雙擊)可以用來(lái)切換當(dāng)前的框架。注意dv命令顯示的是當(dāng)前框架的內(nèi)容。你也可在watch窗口中觀察局部變量的值。

            顯示類(lèi)和鏈表: dt可以顯示數(shù)據(jù)結(jié)構(gòu)。比如dt PEB 會(huì)顯示操作系統(tǒng)進(jìn)程結(jié)構(gòu)。在后面跟上一個(gè)進(jìn)程結(jié)構(gòu)的地址會(huì)顯示該結(jié)構(gòu)的詳細(xì)信息:dt PEB 7ffdf000。

            Dl命令可以顯示一些特定的鏈表結(jié)構(gòu)。

            顯示當(dāng)前線程的錯(cuò)誤值:!gle會(huì)顯示當(dāng)前線程的上一個(gè)錯(cuò)誤值和狀態(tài)值。!error命令可以解碼HRESULT。

            搜索或修改內(nèi)存:使用s 命令來(lái)搜索字節(jié),字或雙字,QWORD或字符串。使用e命令來(lái)修改內(nèi)存。

            計(jì)算表達(dá)式:?命令可以用來(lái)進(jìn)行計(jì)算。關(guān)于表達(dá)式的格式請(qǐng)參照幫助文檔。使用n命令來(lái)切換輸入數(shù)字的進(jìn)制。

            顯示當(dāng)前線程,進(jìn)程和模塊信息:!teb顯示當(dāng)前線程的環(huán)境信息。最常見(jiàn)的用途是查看當(dāng)前線程堆棧的起始地址,然后在堆棧中搜索值。!peb顯示當(dāng)前進(jìn)程的環(huán)境信息,比如執(zhí)行文件的路徑等等。lm顯示進(jìn)程中加載的模塊信息。

            顯示寄存器的值:r命令可以顯示和修改寄存器的值。如果要在表達(dá)式中使用寄存器的值,在寄存器名前加@符號(hào)(比如@eax)。

            顯示最相近的符號(hào):ln Address。如果你有一個(gè)C++對(duì)象的指針,可以用來(lái)ln來(lái)查看該對(duì)象類(lèi)型。

            查找符號(hào):x命令可以用來(lái)查找全局變量的地址或過(guò)程的地址。x命令支持匹配符號(hào)。x kernel32!*顯示Kernel32.dll中的所有可見(jiàn)變量,數(shù)據(jù)結(jié)構(gòu)和過(guò)程。

            查看lock:!locks顯示各線程的鎖資源使用情況。對(duì)調(diào)試死鎖很有用。

            查看handle:!handle顯示句柄信息。如果一段代碼導(dǎo)致句柄泄漏,你只需要在代碼執(zhí)行前后使用!handle命令并比較兩次輸出的區(qū)別。有一個(gè)命令!htrace對(duì)調(diào)試與句柄有關(guān)的Bug非常有用。在開(kāi)始調(diào)試前輸入:

            !htrace –enable

            然后在調(diào)試過(guò)程中使用!htrace handle_value 來(lái)顯示所有與該句柄有關(guān)的調(diào)用堆棧。

            顯示匯編代碼:u。

             

            程序執(zhí)行控制命令:

            設(shè)置代碼斷點(diǎn):bp/bu/bm 可以用來(lái)設(shè)置代碼斷點(diǎn)。你可以指定斷點(diǎn)被跳過(guò)的次數(shù)。假設(shè)一段代碼KERNEL32!SetLastError在運(yùn)行很多次后會(huì)出錯(cuò),你可以設(shè)置如下斷點(diǎn):

                bp KERNEL32!SetLastError 0x100.

            在出錯(cuò)后使用bl 來(lái)顯示斷點(diǎn)信息(注意粗體顯示的值):

            0 e 77e7a3b0     004f (0100)  0:*** KERNEL32!SetLastError

            重新啟動(dòng)調(diào)試(.restart命令)并設(shè)置如下的斷點(diǎn):

            bp Kernel32!SetLastError 0x100-0x4f

            Debugger會(huì)停在出錯(cuò)前最后一次調(diào)用該過(guò)程的地方。

            你可以指定斷點(diǎn)被激活時(shí)Debugger應(yīng)當(dāng)執(zhí)行的命令串。在該命令串中使用J命令可以用來(lái)設(shè)置條件斷點(diǎn):

            bp `mysource.cpp:143` "j (poi(MyVar)”0n20) ''; 'g' "

            上面的斷點(diǎn)只在MyVar的值大于32時(shí)被激活(g命令

            條件斷點(diǎn)的用途極為廣泛。你可以指定一個(gè)斷點(diǎn)只在特殊的情況下被激活,比如傳入的參數(shù)滿(mǎn)足一定的條件,調(diào)用者是某個(gè)特殊的過(guò)程,某個(gè)全局變量被設(shè)為特殊的值等等。

            設(shè)置內(nèi)存斷點(diǎn):ba可以用來(lái)設(shè)置內(nèi)存斷點(diǎn)。調(diào)試過(guò)程中一個(gè)常見(jiàn)的問(wèn)題是跟蹤某些數(shù)據(jù)的變化。如下的斷點(diǎn):

            ba w4 0x40000000 "kb; g"

            可以打印出所有修改0x40000000的調(diào)用堆棧。

            控制程序執(zhí)行:p, pa,t, ta等命令可以用來(lái)控制程序的執(zhí)行。

            控制異常和事件處理:Debugger的缺省設(shè)置是跳過(guò)首次異常(first chance expcetion),在二次異常(second chance exception)時(shí)中斷程序的執(zhí)行。sx命令顯示Debugger的設(shè)置。sxe和sxd可以改變Debugger的設(shè)置。

                sxe clr

            可以控制Debugger在托管異常發(fā)生時(shí)中斷程序的執(zhí)行。常用的Debugger事件有:

                av    訪問(wèn)異常 

                eh    C++異常

                clr   托管異常

                ld    模塊加載

            -c 選項(xiàng)可以用來(lái)指定在事件發(fā)生時(shí)執(zhí)行的調(diào)試命令。

             

             

             

             

            堆棧顯示指令kb , kp, kP , kv
            反匯編指令 u,uf
            跟蹤指令 T,TA,TB,TC
            執(zhí)行相關(guān)指令 P,PA,PC
            跟蹤查看指令 WT

            ----------------------------------------------------------------------------

            堆棧顯示指令 

            k [b|p|P|v]

            在內(nèi)核調(diào)試的時(shí)候,k命令用來(lái)顯示內(nèi)核棧的內(nèi)容

            先說(shuō)說(shuō)內(nèi)核棧用來(lái)干嘛的 看了些資料個(gè)人理解是這樣的

            比如我們的代碼運(yùn)行時(shí),肯定會(huì)有函數(shù)函數(shù)然后還會(huì)調(diào)用函數(shù) 但是系統(tǒng)如何記錄是哪個(gè)父函數(shù)調(diào)用了這個(gè)子函數(shù),在子函數(shù)調(diào)用之前整個(gè)狀態(tài)又是怎樣的,其實(shí)系統(tǒng)是利用了堆棧記錄的 棧這個(gè)東西好阿 先進(jìn)后出 最近調(diào)用的函數(shù)記錄在最頂層 函數(shù)執(zhí)行完后就從棧內(nèi)彈出之前記錄的參數(shù),如果調(diào)用函數(shù) 一樣的把函數(shù)壓進(jìn)棧內(nèi)就好了 這樣一來(lái) 一旦子函數(shù)執(zhí)行完,從棧內(nèi)彈出的第一個(gè)函數(shù)肯定是該子函數(shù)的老爹 我們可以看上層堆棧的狀態(tài)等等 功能大家慢慢去體會(huì)吧我也沒(méi)用過(guò) 呵呵 不好說(shuō)什么 下面說(shuō)些細(xì)節(jié)的東西

            b

            顯示傳給函數(shù)的前三個(gè)參數(shù)

            p

            顯示傳給函數(shù)的全部參數(shù)

            P( 大寫(xiě))

            跟上面那個(gè)一樣 只不過(guò)是顯示形式不同而已

            V

            外加顯示一些額外的信息

            ----------------------------------------------------------------------------

            u [f]

            反匯編指令,嘿嘿 超級(jí)有用的指令喲雖然說(shuō)內(nèi)核很多東西很復(fù)雜 認(rèn)識(shí)偶爾小小反下也是可以的

            u

            反匯編當(dāng)前寄存器指向的代碼

            uf 函數(shù)名(比如nt!ZwCreateFile)

            反匯編指定的函數(shù)

            ----------------------------------------------------------------------------

            t [r]

            單步跟蹤

            r 打開(kāi)指顯示寄存器的詳細(xì)信息,狀態(tài)的開(kāi)關(guān)(下面指令一樣有效,在用1次就會(huì)關(guān)閉哦~)

            ta 地址

            讓程序執(zhí)行到指定地址

            tb

            讓程序運(yùn)行到分支語(yǔ)句時(shí)停止

            tc

            讓程序運(yùn)行到下一個(gè)函數(shù)調(diào)用停止

            ----------------------------------------------------------------------------

            p [r]

            單步執(zhí)行一跳指令

            r 打開(kāi)指顯示寄存器的詳細(xì)信息,狀態(tài)的開(kāi)關(guān)(下面指令一樣有效,在用1次就會(huì)關(guān)閉哦~)

            pa

            讓程序執(zhí)行到指定地址

            pc

            讓程序執(zhí)行到函數(shù)調(diào)用就停止

            ----------------------------------------------------------------------------

            wt

            在想查看指定函數(shù)的信息而又不想單步通過(guò)該函數(shù)時(shí)很有用。可以到函數(shù)的起始地址并執(zhí)行 wt 命令。(摘自翻譯文檔)

            這個(gè)感覺(jué)用處不是很大.不細(xì)細(xì)研究了

            ----------------------------------------------------------------------------

            Ps: 很多人不清楚到底p指令和t指令有什么區(qū)別 其實(shí)很簡(jiǎn)單 p指令執(zhí)行到函數(shù)時(shí)把這個(gè)當(dāng)做一個(gè)指令來(lái)執(zhí)行也就是說(shuō)不會(huì)進(jìn)入函數(shù)執(zhí)行,但是t指令會(huì)進(jìn)入到函數(shù)里面執(zhí)行 就這么簡(jiǎn)單~~呵呵

             

             

            遠(yuǎn)程調(diào)試

            使用WinDbg進(jìn)行遠(yuǎn)程調(diào)試是很容易的,而且有很多種可行的方法。在下文中,’調(diào)試服務(wù)器’指的是運(yùn)行在你所要調(diào)試的遠(yuǎn)程機(jī)器上的調(diào)試器。’調(diào)試客戶(hù)端’指的是控制當(dāng)前會(huì)話的調(diào)試器。

            ·      使用調(diào)試器:你需要CDB, NTSD或者WinDbg已經(jīng)安裝在遠(yuǎn)程機(jī)器上。WinDbg客戶(hù)端可以連接到CDB, NTSD或者WinDbg中的任何一個(gè)作為服務(wù)器,反之亦然。在客戶(hù)端和服務(wù)器直接可以選擇TCP或者命名管道作為通訊協(xié)議。

            o   在服務(wù)器端的啟動(dòng)過(guò)程:

            §  WinDbg –server npipe:pipe=pipename(注:可以允許多個(gè)客戶(hù)端連接)

            §  從WinDbg內(nèi)部: .server npipe:pipe=pipename(注,連接單個(gè)客戶(hù)端)

            你可以用多種協(xié)議開(kāi)啟不同的服務(wù)會(huì)話。并且可用密碼來(lái)保護(hù)一個(gè)會(huì)話。

            o   從客戶(hù)端連接:

            §  WinDbg -remote npipe:server=Server, pipe=PipeName[,password=Password]

            §  從WinDbg內(nèi)部: File->Connect to Remote Session: for connection string, enter npipe:server=Server, pipe=PipeName [,password=Password]

            ·     使用Remote.exe: Remote.exe使用命名管道作為通訊的方式。如果你使用的是一個(gè)命令行接口的程序,比如KD,CDB或者NTSD。你可以使用remote.exe來(lái)遠(yuǎn)程調(diào)試。注意:使用@q(不是q)來(lái)退出客戶(hù)端,不用關(guān)掉服務(wù)端。

            o   要啟動(dòng)一個(gè)服務(wù)端:

            §  Remote.exe /s “cdp –p <pid>” test1

            o   從客戶(hù)端連接:

            §  Remote.exe /c <machinename> test1

            上面的test1是我們所選擇的命名管道的名字。

            服務(wù)端會(huì)顯示那個(gè)客戶(hù)端從那個(gè)服務(wù)器連接以及執(zhí)行過(guò)的命令。你可以使用‘qq’命令來(lái)退出服務(wù)端;或者使用File->Exit來(lái)退出客戶(hù)端。另外,如果要進(jìn)行遠(yuǎn)程調(diào)試,你必須屬于遠(yuǎn)程機(jī)器的”Debugger User”組并且服務(wù)器必須允許遠(yuǎn)程連接。
            即時(shí)調(diào)試

            在WinDbg的文檔的”Enabling Postmorten Debugging”部分對(duì)此有很詳細(xì)的討論。簡(jiǎn)而言之,你可以把WinDbg設(shè)置成默認(rèn)的即時(shí)調(diào)試器,命令就是:Windbg –I。這個(gè)命令實(shí)際上是把注冊(cè)表中 HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug的鍵值設(shè)置成WinDbg。如果要把WinDbg設(shè)置成為默認(rèn)的托管調(diào)試器,你需要顯示設(shè)置如下的注冊(cè)表鍵值:
            HKLM\Software\Microsoft\.NETFramework\DbgJITDebugLaunchSetting 設(shè)置成 2
            HKLM\Software\Microsoft\.NETFramework\DbgManagedDebugger 設(shè)置成Windbg.(注意其中的啟動(dòng)參數(shù)設(shè)置)

            通過(guò)JIT的設(shè)置,當(dāng)一個(gè)應(yīng)用程序在不是調(diào)試的狀態(tài)下拋出了未處理的異常之時(shí),WinDbg就會(huì)被啟動(dòng)。
            64位調(diào)試

            所有這些調(diào)試器均支持在AMD64和IA64上的64位調(diào)試環(huán)境。


            托管應(yīng)用程序的調(diào)試

            WinDbg 6.3以后的版本支持在Widbey(VS2005和.net 2.0的內(nèi)部開(kāi)發(fā)代號(hào)) .net CLR托管調(diào)試。在文檔中針對(duì)托管調(diào)試有很好的討論。需要注意的是,對(duì)于托管程序來(lái)說(shuō),沒(méi)有剛才所說(shuō)的PDB(譯注:托管代碼實(shí)際上也是有PDB的,但是這個(gè)PDB實(shí)際上記錄了C#代碼和IL代碼的對(duì)應(yīng)關(guān)系以及相關(guān)的一些信息)的概念,因?yàn)樗械某绦蚨际蔷幾g成為ILASM。調(diào)試器通過(guò)CLR來(lái)查詢(xún)所需的附加信息。

            有幾點(diǎn)需要注意:

            你只能在托段函數(shù)的代碼被執(zhí)行過(guò)至少一次之后才能設(shè)置斷點(diǎn)。只有這樣它才能被編譯成匯編代碼。記住以下的幾點(diǎn):

            ·         關(guān)于函數(shù)的地址的復(fù)雜化以及對(duì)應(yīng)的斷點(diǎn)設(shè)置:

            o   CLR有可能丟棄已經(jīng)編譯好的代碼,所以函數(shù)的入口地址有可能改變。

            o   同樣的代碼有可能被多次編譯,如果多個(gè)應(yīng)用程序域沒(méi)有共享這段代碼的話。如果你設(shè)置了一個(gè)斷點(diǎn),它就會(huì)被設(shè)置在當(dāng)前線程(譯注:CLR的邏輯線程)所在的應(yīng)用程序域內(nèi)。

            o   泛型的特殊實(shí)例可能導(dǎo)致同一個(gè)函數(shù)有不同的地址。.

            ·         數(shù)據(jù)存儲(chǔ)布局的復(fù)雜化以及對(duì)應(yīng)的數(shù)據(jù)檢查:
            CLR可能會(huì)在運(yùn)行的時(shí)候任意改變數(shù)據(jù)的存儲(chǔ)布局,所以一個(gè)結(jié)構(gòu)體成員的偏移量可能會(huì)被改變掉. (譯注:實(shí)際上是在一個(gè)類(lèi)型被加載的時(shí)候決定的數(shù)據(jù)布局,之后是不會(huì)改變的。)
            一個(gè)類(lèi)型的信息是在第一次使用的時(shí)候被加載,所以你可能不能夠查看一個(gè)數(shù)據(jù)成員如果它還沒(méi)有被使用過(guò).

            ·         調(diào)試器命令的復(fù)雜化

            o   當(dāng)跟蹤托管代碼的時(shí)候,你會(huì)需要穿越大段的CLR自己的代碼比如JIT編譯器的代碼,原因可能是你第一次進(jìn)入一個(gè)函數(shù),或者是你在托管和非托管代碼之間進(jìn)行切換。
            調(diào)試Windows服務(wù)

            使用WinDbg,你可以像調(diào)試其它應(yīng)用程序那樣調(diào)試Windows服務(wù)程序。即可以通過(guò)附加進(jìn)程的方法啟動(dòng)Windows服務(wù),也可以把WinDbg當(dāng)作一個(gè)即時(shí)調(diào)試器,并且在代碼中調(diào)用DbgBreakPoint 或者 DebugBreak,或者在x86機(jī)器上加入一條int 3匯編指令。
            調(diào)試異常

            一個(gè)調(diào)試器會(huì)得到兩次的異常通知-第一次在應(yīng)用程序有機(jī)會(huì)處理異常之前(‘first chance exception’);如果應(yīng)用程序沒(méi)有處理這個(gè)異常,這時(shí)候調(diào)試器就會(huì)有機(jī)會(huì)來(lái)處理異常(‘second-chance exception’)。如果調(diào)試器沒(méi)有處理二次機(jī)會(huì)的異常,應(yīng)用程序就會(huì)退出。

            .lastevent或者,!analyze –v命令會(huì)給你顯示異常的記錄以及異常拋出所在函數(shù)的堆棧跟蹤信息。

            你也可以使用 .exr, .cxr以及 .ecxr命令來(lái)顯示異常和上下文記錄。同時(shí)需要注意的是,你也可以改變first-chance的處理選項(xiàng)。對(duì)應(yīng)的命令就是: sxe, sxd, sxn和sxi。


            虛擬機(jī)調(diào)試驅(qū)動(dòng)

             

            1)
            將 WinDbg 發(fā)送一個(gè)快捷方式,并修改在快捷方式上右鍵=>"屬性"
            將"目標(biāo)"中的 WinDbg 文件名后添加 "-k com:port=\\.\pipe\com_1,baud=115200,pipe" , 如下:
            "C:\Program Files\Debugging Tools for Windows\windbg.exe" -k com:port=\\.\pipe\com_1,baud=115200,pipe

            2)
            打開(kāi)虛擬機(jī)中的 c:\boot.ini 文件(之前去掉"只讀"屬性),復(fù)制一行
            multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise" /fastdetect
            即添加了一個(gè)啟動(dòng)選項(xiàng),并修改為:
            multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise[Debug]" /fastdetect /debug /debugport=com1 /baudrate=115200
            即添加了調(diào)試選項(xiàng),調(diào)試端口以及串口的速率.
            保存.

            3)
            關(guān)閉虛擬機(jī)里的目標(biāo)windows系統(tǒng)(必須,否則在"Settings..."里的"Add..."將為灰色,不可選狀態(tài)),
            選擇目標(biāo)windows系統(tǒng)的"Settings..."選項(xiàng),在"Hardware"選項(xiàng)中,點(diǎn)擊下面的"Add..."按鈕.
            選擇"Serial Port"點(diǎn)擊"Next",再選擇"Output to named pipe","Next",
            這一向?qū)е?前兩項(xiàng)不修改,最后一項(xiàng)修改為"The other end is an application.",
            如果這里存在"高級(jí)"選項(xiàng),則在其中選擇"Yield CPU on poll"[注:有些虛擬機(jī)在這里并沒(méi)有"高級(jí)"選項(xiàng),則在"Finish"后,選擇"Serial Port",再勾選右下角的"Yield CPU on poll"],
            "Finish".

            "OK",完成"Virtual Maching Setting".

            4)
            打開(kāi)虛擬機(jī)中的目標(biāo)Windows系統(tǒng),選擇"Windows Server 2003, Enterprise[Debug]"后,立即打開(kāi)之前創(chuàng)建的WinDbg快捷方式(若先打開(kāi)WinDbg,則會(huì)報(bào)找不到'com:port=....'等信息).

            5)
            連接上虛擬機(jī)中的目標(biāo)windows系統(tǒng)后,立即發(fā)送一個(gè)WinDbg中的"Debug"=>"Break"來(lái)中斷它.

            6)
            設(shè)置"Symbol"路徑,"Source"路徑和"ImagePath"路徑(若有多個(gè),則用";"號(hào)隔開(kāi)).
            如:
            "File"=>"Symbol" 里設(shè)置:"G:\虛擬機(jī)共享文件\Win2003Symbols;G:\虛擬機(jī)共享文件\GiveIO",勾選"Reload","OK". (第一個(gè)是Windows的符號(hào)[這里要注意,符號(hào)應(yīng)該是虛擬機(jī)內(nèi)Windows系統(tǒng)對(duì)應(yīng)的],第二個(gè)是要調(diào)試的驅(qū)動(dòng)的符號(hào))

            "File"=>"Source" 里設(shè)置:"G:\虛擬機(jī)共享文件\GiveIO", "OK". (要調(diào)試的驅(qū)動(dòng)的源碼我放在了此目錄中)

            "File"=>"Image File Path" 里設(shè)置: "G:\虛擬機(jī)共享文件\GiveIO", "Reload", "OK". (我把要調(diào)試的驅(qū)動(dòng)程序放在了此目錄中)

            "File"=>"Open Source File", 打開(kāi)驅(qū)動(dòng)源文件.

            7)
            在要調(diào)試的驅(qū)動(dòng)程序上下斷點(diǎn)(這里我調(diào)試的驅(qū)動(dòng)模塊名為"GiveIO",后面是在"DriverEntry"入口點(diǎn)下斷點(diǎn)[bu設(shè)置的是延遲斷點(diǎn)]):
            bu GiveIO!DriverEntry
            按"F5"(或在命令行中用"g"命令),即可讓虛擬機(jī)中的目標(biāo)Windows順利啟動(dòng).

            8)
            在虛擬機(jī)中運(yùn)行要調(diào)試的驅(qū)動(dòng)程序,即可運(yùn)行到斷點(diǎn)處.

            9)
            一些說(shuō)明:
            在已運(yùn)行的Windows目標(biāo)系統(tǒng)中調(diào)試指定驅(qū)動(dòng):
            首先應(yīng)使WinDbg產(chǎn)生一個(gè)斷點(diǎn),使用WinDbg中的"Debug"=>"Break",此時(shí)目標(biāo)Windows系統(tǒng)就會(huì)中斷.
            此時(shí)使用:
            bu GiveIO!DriverEntry
            命令,在指定驅(qū)動(dòng)的模塊名的入口點(diǎn)下斷點(diǎn)(GiveIO為此處的驅(qū)動(dòng)模塊名).
            運(yùn)行驅(qū)動(dòng)后,即會(huì)在驅(qū)動(dòng)的DriverEntry處斷下來(lái),此時(shí)就可以進(jìn)行跟蹤調(diào)試了.

            bl命令可以查看已下的斷點(diǎn)
            bc清除斷點(diǎn),如果后面跟"*",則清除所有斷點(diǎn)

            posted @ 2009-10-23 08:58 深邃者 閱讀(2049) | 評(píng)論 (0)編輯 收藏

            #pragma 預(yù)處理指令詳解

            #pragma 預(yù)處理指令詳解

            在所有的預(yù)處理指令中,#pragma 指令的作用是設(shè)定編譯器的狀態(tài)或者是指示編譯器完成一些特定的動(dòng)作。

            依據(jù)定義,編譯指示是機(jī)器或操作系統(tǒng)專(zhuān)有的,且對(duì)于每個(gè)編譯器都是不同的。 
                其格式一般為: #pragma  para 
                其中para為參數(shù),下面來(lái)看一些常用的參數(shù)。 
             

            (1)message 信息參數(shù)

                #pragma  message("消息文本") 
                當(dāng)編譯器遇到這條指令時(shí)就在編譯輸出窗口中將消息文本打印出來(lái)。 
            判斷自己有沒(méi)有在源代碼的什么地方定義了_X86這個(gè)宏,可以用下面的方法:
                #ifdef  _X86 
                #pragma  message("_X86  macro  activated!") 
                #endif  
                定義了_X86這個(gè)宏以后,應(yīng)用程序在編譯時(shí)就會(huì)在編譯輸出窗口里顯示"_86  macro  activated!"。  
                  

            (2)code_seg代碼段參數(shù)

                #pragma  code_seg( ["section-name" [, "section-class"] ] ) 
                它能夠設(shè)置程序中函數(shù)代碼存放的代碼段,當(dāng)我們開(kāi)發(fā)驅(qū)動(dòng)程序的時(shí)候就會(huì)使用到它。 
             

            (3)#pragma once 

                只要在頭文件的最開(kāi)始加入這條指令就能夠保證頭文件被編譯一次,這條指令實(shí)際上在VC6中就已經(jīng)有了,
            但是考慮到兼容性并沒(méi)有太多的使用它。 

                  
            (4)#pragma  hdrstop

                表示預(yù)編譯頭文件到此為止,后面的頭文件不進(jìn)行預(yù)編譯。BCB可以預(yù)編譯頭文件以加快鏈接的速度,
            但如果所有頭文件都進(jìn)行預(yù)編譯又可能占太多磁盤(pán)空間,所以使用這個(gè)選項(xiàng)排除一些頭文件。   
                有時(shí)單元之間有依賴(lài)關(guān)系,比如單元A依賴(lài)單元B,所以單元B要先于單元A編譯。
            你可以用#pragma  startup指定編譯優(yōu)先級(jí),如果使用了#pragma  package(smart_init),
            BCB就會(huì)根據(jù)優(yōu)先級(jí)的大小先后編譯。   

                  
            (5)#pragma  resource  "*.dfm"

                表示把*.dfm文件中的資源加入工程。*.dfm中包括窗體  外觀的定義。   

                    
            (6)#pragma  warning( disable: 4507 34; once: 4385; error: 164 )
             
                等價(jià)于: 
                #pragma  warning( disable: 4507 34 )    //  不顯示4507和34號(hào)警告信息 
                #pragma  warning( once: 4385 )          //  4385號(hào)警告信息僅報(bào)告一次 
                #pragma  warning( error: 164 )          //  把164號(hào)警告信息作為一個(gè)錯(cuò)誤。 

                同時(shí)這個(gè)pragma  warning  也支持如下格式: 
                #pragma  warning( push [, n ] ) 
                #pragma  warning( pop ) 
                這里n代表一個(gè)警告等級(jí)(1---4)。 
                #pragma  warning( push )保存所有警告信息的現(xiàn)有的警告狀態(tài)。 
                #pragma  warning( push, n )保存所有警告信息的現(xiàn)有的警告狀態(tài),并且把全局警告等級(jí)設(shè)定為n。   
                #pragma  warning( pop )向棧中彈出最后一個(gè)警告信息,在入棧和出棧之間所作的一切改動(dòng)取消。例如: 
                #pragma  warning( push ) 
                #pragma  warning( disable: 4705 ) 
                #pragma  warning( disable: 4706 ) 
                #pragma  warning( disable: 4707 ) 
                //....... 
                #pragma  warning(  pop  )   
                在這段代碼的最后,重新保存所有的警告信息(包括4705,4706和4707)。 


            (7)#pragma  comment(...) 

                該指令將一個(gè)注釋記錄放入一個(gè)對(duì)象文件或可執(zhí)行文件中。 
            常用的lib關(guān)鍵字,可以幫我們連入一個(gè)庫(kù)文件。如:
                #pragma  comment(lib, "comctl32.lib")
                #pragma  comment(lib, "vfw32.lib")
                #pragma  comment(lib, "wsock32.lib")
             
               
            每個(gè)編譯程序可以用#pragma指令激活或終止該編譯程序支持的一些編譯功能。

            例如,對(duì)循環(huán)優(yōu)化功能: 
            #pragma  loop_opt(on)     //  激活 
            #pragma  loop_opt(off)    //  終止 

            有時(shí),程序中會(huì)有些函數(shù)會(huì)使編譯器發(fā)出你熟知而想忽略的警告,
            如“Parameter  xxx  is  never  used  in  function  xxx”,可以這樣: 
            #pragma  warn  —100         //  Turn  off  the  warning  message  for  warning  #100 
            int  insert_record(REC  *r) 
            {  /*  function  body  */  } 
            #pragma  warn  +100          //  Turn  the  warning  message  for  warning  #100  back  on 
            函數(shù)會(huì)產(chǎn)生一條有唯一特征碼100的警告信息,如此可暫時(shí)終止該警告。 

            每個(gè)編譯器對(duì)#pragma的實(shí)現(xiàn)不同,在一個(gè)編譯器中有效在別的編譯器中幾乎無(wú)效。可從編譯器的文檔中查看。


            補(bǔ)充 —— #pragma pack 與 內(nèi)存對(duì)齊問(wèn)題


                許多實(shí)際的計(jì)算機(jī)系統(tǒng)對(duì)基本類(lèi)型數(shù)據(jù)在內(nèi)存中存放的位置有限制,它們會(huì)要求這些數(shù)據(jù)的首地址的值是某個(gè)數(shù)k
            (通常它為4或8)的倍數(shù),這就是所謂的內(nèi)存對(duì)齊,而這個(gè)k則被稱(chēng)為該數(shù)據(jù)類(lèi)型的對(duì)齊模數(shù)(alignment modulus)。

                Win32平臺(tái)下的微軟C編譯器(cl.exe for 80x86)在默認(rèn)情況下采用如下的對(duì)齊規(guī)則:
                任何基本數(shù)據(jù)類(lèi)型T的對(duì)齊模數(shù)就是T的大小,即sizeof(T)。比如對(duì)于double類(lèi)型(8字節(jié)),
            就要求該類(lèi)型數(shù)據(jù)的地址總是8的倍數(shù),而char類(lèi)型數(shù)據(jù)(1字節(jié))則可以從任何一個(gè)地址開(kāi)始。

                Linux下的GCC奉行的是另外一套規(guī)則(在資料中查得,并未驗(yàn)證,如錯(cuò)誤請(qǐng)指正):
                任何2字節(jié)大小(包括單字節(jié)嗎?)的數(shù)據(jù)類(lèi)型(比如short)的對(duì)齊模數(shù)是2,而其它所有超過(guò)2字節(jié)的數(shù)據(jù)類(lèi)型
            (比如long,double)都以4為對(duì)齊模數(shù)。

                ANSI C規(guī)定一種結(jié)構(gòu)類(lèi)型的大小是它所有字段的大小以及字段之間或字段尾部的填充區(qū)大小之和。
            填充區(qū)就是為了使結(jié)構(gòu)體字段滿(mǎn)足內(nèi)存對(duì)齊要求而額外分配給結(jié)構(gòu)體的空間。那么結(jié)構(gòu)體本身有什么對(duì)齊要求嗎?
            有的,ANSI C標(biāo)準(zhǔn)規(guī)定結(jié)構(gòu)體類(lèi)型的對(duì)齊要求不能比它所有字段中要求最嚴(yán)格的那個(gè)寬松,可以更嚴(yán)格。


            如何使用c/c++中的對(duì)齊選項(xiàng)

                vc6中的編譯選項(xiàng)有 /Zp[1|2|4|8|16] ,/Zp1表示以1字節(jié)邊界對(duì)齊,相應(yīng)的,/Zpn表示以n字節(jié)邊界對(duì)齊。
            n字節(jié)邊界對(duì)齊的意思是說(shuō),一個(gè)成員的地址必須安排在成員的尺寸的整數(shù)倍地址上或者是n的整數(shù)倍地址上,取它們中的最小值。
            也就是:
                min ( sizeof ( member ),  n)

                實(shí)際上,1字節(jié)邊界對(duì)齊也就表示了結(jié)構(gòu)成員之間沒(méi)有空洞。
                /Zpn選項(xiàng)是應(yīng)用于整個(gè)工程的,影響所有的參與編譯的結(jié)構(gòu)。
                要使用這個(gè)選項(xiàng),可以在vc6中打開(kāi)工程屬性頁(yè),c/c++頁(yè),選擇Code Generation分類(lèi),在Struct member alignment可以選擇。

                要專(zhuān)門(mén)針對(duì)某些結(jié)構(gòu)定義使用對(duì)齊選項(xiàng),可以使用#pragma pack編譯指令:


            (1) #pragma  pack( [ n ] )

                該指令指定結(jié)構(gòu)和聯(lián)合成員的緊湊對(duì)齊。而一個(gè)完整的轉(zhuǎn)換單元的結(jié)構(gòu)和聯(lián)合的緊湊對(duì)齊由/Zp 選項(xiàng)設(shè)置。
            緊湊對(duì)齊用pack編譯指示在數(shù)據(jù)說(shuō)明層設(shè)置。該編譯指示在其出現(xiàn)后的第一個(gè)結(jié)構(gòu)或聯(lián)合說(shuō)明處生效。
            該編譯指示對(duì)定義無(wú)效。
                當(dāng)你使用#pragma  pack ( n ) 時(shí), 這里n 為1、2、4、8 或16。
                第一個(gè)結(jié)構(gòu)成員之后的每個(gè)結(jié)構(gòu)成員都被存儲(chǔ)在更小的成員類(lèi)型或n 字節(jié)界限內(nèi)。
            如果你使用無(wú)參量的#pragma  pack, 結(jié)構(gòu)成員被緊湊為以/Zp 指定的值。該缺省/Zp 緊湊值為/Zp8 。


            (2) 編譯器也支持以下增強(qiáng)型語(yǔ)法:
                #pragma  pack( [ [ { push | pop } , ] [ identifier, ] ] [ n] )

                若不同的組件使用pack編譯指示指定不同的緊湊對(duì)齊, 這個(gè)語(yǔ)法允許你把程序組件組合為一個(gè)單獨(dú)的轉(zhuǎn)換單元。
            帶push參量的pack編譯指示的每次出現(xiàn)將當(dāng)前的緊湊對(duì)齊存儲(chǔ)到一個(gè)內(nèi)部編譯器堆棧中。
                編譯指示的參量表從左到右讀取。如果你使用push, 則當(dāng)前緊湊值被存儲(chǔ)起來(lái);
            如果你給出一個(gè)n 的值, 該值將成為新的緊湊值。若你指定一個(gè)標(biāo)識(shí)符, 即你選定一個(gè)名稱(chēng),
            則該標(biāo)識(shí)符將和這個(gè)新的的緊湊值聯(lián)系起來(lái)。

                帶一個(gè)pop參量的pack編譯指示的每次出現(xiàn)都會(huì)檢索內(nèi)部編譯器堆棧頂?shù)闹?并且使該值為新的緊湊對(duì)齊值。
            如果你使用pop參量且內(nèi)部編譯器堆棧是空的,則緊湊值為命令行給定的值, 并且將產(chǎn)生一個(gè)警告信息。
            若你使用pop且指定一個(gè)n的值, 該值將成為新的緊湊值。若你使用p o p 且指定一個(gè)標(biāo)識(shí)符,
            所有存儲(chǔ)在堆棧中的值將從棧中刪除, 直到找到一個(gè)匹配的標(biāo)識(shí)符, 這個(gè)與標(biāo)識(shí)符相關(guān)的緊湊值也從棧中移出,
            并且這個(gè)僅在標(biāo)識(shí)符入棧之前存在的緊湊值成為新的緊湊值。如果未找到匹配的標(biāo)識(shí)符,
            將使用命令行設(shè)置的緊湊值, 并且將產(chǎn)生一個(gè)一級(jí)警告。缺省緊湊對(duì)齊為8 。

               pack編譯指示的新的增強(qiáng)功能讓你編寫(xiě)頭文件, 確保在遇到該頭文件的前后的
            緊湊值是一樣的。


            (3) 棧內(nèi)存對(duì)齊

                在vc6中棧的對(duì)齊方式不受結(jié)構(gòu)成員對(duì)齊選項(xiàng)的影響。它總是保持對(duì)齊,而且對(duì)齊在4字節(jié)邊界上。

            posted @ 2009-10-23 08:56 深邃者 閱讀(102) | 評(píng)論 (0)編輯 收藏

            c++內(nèi)存布局

            0712月,我寫(xiě)了一篇《C++虛函數(shù)表解析》的文章,引起了大家的興趣。有很多朋友對(duì)我的文章留了言,有鼓勵(lì)我的,有批評(píng)我的,還有很多問(wèn)問(wèn)題的。我在這里一并對(duì)大家的留言表示感謝。這也是我為什么再寫(xiě)一篇續(xù)言的原因。因?yàn)椋谏弦黄恼轮校矣昧说氖纠际欠浅:?jiǎn)單的,主要是為了說(shuō)明一些機(jī)理上的問(wèn)題,也是為了圖一些表達(dá)上方便和簡(jiǎn)單。不想,這篇文章成為了打開(kāi)C++對(duì)象模型內(nèi)存布局的一個(gè)引子,引發(fā)了大家對(duì)C++對(duì)象的更深層次的討論。當(dāng)然,我之前的文章還有很多方面沒(méi)有涉及,從我個(gè)人感覺(jué)下來(lái),在談?wù)撎摵瘮?shù)表里,至少有以下這些內(nèi)容沒(méi)有涉及:

            1)有成員變量的情況。

            2)有重復(fù)繼承的情況。

            3)有虛擬繼承的情況。

            4)有鉆石型虛擬繼承的情況。

             

            這些都是我本篇文章需要向大家說(shuō)明的東西。所以,這篇文章將會(huì)是《C++虛函數(shù)表解析》的一個(gè)續(xù)篇,也是一篇高級(jí)進(jìn)階的文章。我希望大家在讀這篇文章之前對(duì)C++有一定的基礎(chǔ)和了解,并能先讀我的上一篇文章。因?yàn)檫@篇文章的深度可能會(huì)比較深,而且會(huì)比較雜亂,我希望你在讀本篇文章時(shí)不會(huì)有大腦思維紊亂導(dǎo)致大腦死機(jī)的情況。;-)

             

            對(duì)象的影響因素

             

            簡(jiǎn)而言之,我們一個(gè)類(lèi)可能會(huì)有如下的影響因素:

             

            1)成員變量

            2)虛函數(shù)(產(chǎn)生虛函數(shù)表)

            3)單一繼承(只繼承于一個(gè)類(lèi))

            4)多重繼承(繼承多個(gè)類(lèi))

            5)重復(fù)繼承(繼承的多個(gè)父類(lèi)中其父類(lèi)有相同的超類(lèi))

            6)虛擬繼承(使用virtual方式繼承,為了保證繼承后父類(lèi)的內(nèi)存布局只會(huì)存在一份)

            上述的東西通常是C++這門(mén)語(yǔ)言在語(yǔ)義方面對(duì)對(duì)象內(nèi)部的影響因素,當(dāng)然,還會(huì)有編譯器的影響(比如優(yōu)化),還有字節(jié)對(duì)齊的影響。在這里我們都不討論,我們只討論C++語(yǔ)言上的影響。

             

            本篇文章著重討論下述幾個(gè)情況下的C++對(duì)象的內(nèi)存布局情況。

             

            1)單一的一般繼承(帶成員變量、虛函數(shù)、虛函數(shù)覆蓋)

            2)單一的虛擬繼承(帶成員變量、虛函數(shù)、虛函數(shù)覆蓋)

            3)多重繼承(帶成員變量、虛函數(shù)、虛函數(shù)覆蓋)

            4)重復(fù)多重繼承(帶成員變量、虛函數(shù)、虛函數(shù)覆蓋)

            5)鉆石型的虛擬多重繼承(帶成員變量、虛函數(shù)、虛函數(shù)覆蓋)

             

            我們的目標(biāo)就是,讓事情越來(lái)越復(fù)雜。

             

            知識(shí)復(fù)習(xí)

             

            我們簡(jiǎn)單地復(fù)習(xí)一下,我們可以通過(guò)對(duì)象的地址來(lái)取得虛函數(shù)表的地址,如:

             

                      typedef void(*Fun)(void);

             

                        Base b;

             

                        Fun pFun = NULL;

             

                        cout << "虛函數(shù)表地址:" << (int*)(&b) << endl;

                        cout << "虛函數(shù)表第一個(gè)函數(shù)地址:" << (int*)*(int*)(&b) << endl;

             

                        // Invoke the first virtual function 

                        pFun = (Fun)*((int*)*(int*)(&b));

                        pFun();

             

            我們同樣可以用這種方式來(lái)取得整個(gè)對(duì)象實(shí)例的內(nèi)存布局。因?yàn)檫@些東西在內(nèi)存中都是連續(xù)分布的,我們只需要使用適當(dāng)?shù)牡刂菲屏浚覀兙涂梢垣@得整個(gè)內(nèi)存對(duì)象的布局。

             

            本篇文章中的例程或內(nèi)存布局主要使用如下編譯器和系統(tǒng):

            1)Windows XP VC++ 2003

            2)Cygwin G++ 3.4.4

             

            單一的一般繼承

             

            下面,我們假設(shè)有如下所示的一個(gè)繼承關(guān)系:

             

             

            請(qǐng)注意,在這個(gè)繼承關(guān)系中,父類(lèi),子類(lèi),孫子類(lèi)都有自己的一個(gè)成員變量。而了類(lèi)覆蓋了父類(lèi)的f()方法,孫子類(lèi)覆蓋了子類(lèi)的g_child()及其超類(lèi)的f()

             

            我們的源程序如下所示:

             

            class Parent {

            public:

                int iparent;

                Parent ():iparent (10) {}

                virtual void f() { cout << " Parent::f()" << endl; }

                virtual void g() { cout << " Parent::g()" << endl; }

                virtual void h() { cout << " Parent::h()" << endl; }

             

            };

             

            class Child : public Parent {

            public:

                int ichild;

                Child():ichild(100) {}

                virtual void f() { cout << "Child::f()" << endl; }

                virtual void g_child() { cout << "Child::g_child()" << endl; }

                virtual void h_child() { cout << "Child::h_child()" << endl; }

            };

             

            class GrandChild : public Child{

            public:

                int igrandchild;

                GrandChild():igrandchild(1000) {}

                virtual void f() { cout << "GrandChild::f()" << endl; }

                virtual void g_child() { cout << "GrandChild::g_child()" << endl; }

                virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; }

            };

            我們使用以下程序作為測(cè)試程序:(下面程序中,我使用了一個(gè)int** pVtab 來(lái)作為遍歷對(duì)象內(nèi)存布局的指針,這樣,我就可以方便地像使用數(shù)組一樣來(lái)遍歷所有的成員包括其虛函數(shù)表了,在后面的程序中,我也是用這樣的方法的,請(qǐng)不必感到奇怪,)

             

                typedef void(*Fun)(void);

                GrandChild gc;

               

             

                int** pVtab = (int**)&gc;

             

                cout << "[0] GrandChild::_vptr->" << endl;

                for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){

                            pFun = (Fun)pVtab[0][i];

                            cout << "    ["<<i<<"] ";

                            pFun();

                }

                cout << "[1] Parent.iparent = " << (int)pVtab[1] << endl;

                cout << "[2] Child.ichild = " << (int)pVtab[2] << endl;

                cout << "[3] GrandChild.igrandchild = " << (int)pVtab[3] << endl;

             

            其運(yùn)行結(jié)果如下所示:(在VC++ 2003G++ 3.4.4下)

             

            [0] GrandChild::_vptr->

                [0] GrandChild::f()

                [1] Parent::g()

                [2] Parent::h()

                [3] GrandChild::g_child()

                [4] Child::h1()

                [5] GrandChild::h_grandchild()

            [1] Parent.iparent = 10

            [2] Child.ichild = 100

            [3] GrandChild.igrandchild = 1000

             

            使用圖片表示如下:

             

             

             

            可見(jiàn)以下幾個(gè)方面:

            1)虛函數(shù)表在最前面的位置。

            2)成員變量根據(jù)其繼承和聲明順序依次放在后面。

            3)在單一的繼承中,被overwrite的虛函數(shù)在虛函數(shù)表中得到了更新。

             

             

             

             

             

            多重繼承

             

            下面,再讓我們來(lái)看看多重繼承中的情況,假設(shè)有下面這樣一個(gè)類(lèi)的繼承關(guān)系。注意:子類(lèi)只overwrite了父類(lèi)的f()函數(shù),而還有一個(gè)是自己的函數(shù)(我們這樣做的目的是為了用g1()作為一個(gè)標(biāo)記來(lái)標(biāo)明子類(lèi)的虛函數(shù)表)。而且每個(gè)類(lèi)中都有一個(gè)自己的成員變量:

             

             

             

            我們的類(lèi)繼承的源代碼如下所示:父類(lèi)的成員初始為102030,子類(lèi)的為100

             

            class Base1 {

            public:

                int ibase1;

                Base1():ibase1(10) {}

                virtual void f() { cout << "Base1::f()" << endl; }

                virtual void g() { cout << "Base1::g()" << endl; }

                virtual void h() { cout << "Base1::h()" << endl; }

             

            };

             

            class Base2 {

            public:

                int ibase2;

                Base2():ibase2(20) {}

                virtual void f() { cout << "Base2::f()" << endl; }

                virtual void g() { cout << "Base2::g()" << endl; }

                virtual void h() { cout << "Base2::h()" << endl; }

            };

             

            class Base3 {

            public:

                int ibase3;

                Base3():ibase3(30) {}

                virtual void f() { cout << "Base3::f()" << endl; }

                virtual void g() { cout << "Base3::g()" << endl; }

                virtual void h() { cout << "Base3::h()" << endl; }

            };

             

             

            class Derive : public Base1, public Base2, public Base3 {

            public:

                int iderive;

                Derive():iderive(100) {}

                virtual void f() { cout << "Derive::f()" << endl; }

                virtual void g1() { cout << "Derive::g1()" << endl; }

            };

             

            我們通過(guò)下面的程序來(lái)查看子類(lèi)實(shí)例的內(nèi)存布局:下面程序中,注意我使用了一個(gè)s變量,其中用到了sizof(Base)來(lái)找下一個(gè)類(lèi)的偏移量。(因?yàn)槲衣暶鞯氖?/span>int成員,所以是4個(gè)字節(jié),所以沒(méi)有對(duì)齊問(wèn)題。關(guān)于內(nèi)存的對(duì)齊問(wèn)題,大家可以自行試驗(yàn),我在這里就不多說(shuō)了)

             

                         typedef void(*Fun)(void);

                           Derive d;

             

                            int** pVtab = (int**)&d;

             

                            cout << "[0] Base1::_vptr->" << endl;

                            pFun = (Fun)pVtab[0][0];

                            cout << "     [0] ";

                            pFun();

             

                            pFun = (Fun)pVtab[0][1];

                            cout << "     [1] ";pFun();

             

                            pFun = (Fun)pVtab[0][2];

                            cout << "     [2] ";pFun();

             

                            pFun = (Fun)pVtab[0][3];

                            cout << "     [3] "; pFun();

             

                            pFun = (Fun)pVtab[0][4];

                            cout << "     [4] "; cout<<pFun<<endl;

             

                            cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl;

             

             

                            int s = sizeof(Base1)/4;

             

                            cout << "[" << s << "] Base2::_vptr->"<<endl;

                            pFun = (Fun)pVtab[s][0];

                            cout << "     [0] "; pFun();

             

                            Fun = (Fun)pVtab[s][1];

                            cout << "     [1] "; pFun();

             

                            pFun = (Fun)pVtab[s][2];

                            cout << "     [2] "; pFun();

             

                            pFun = (Fun)pVtab[s][3];

                            out << "     [3] ";

                            cout<<pFun<<endl;

             

                            cout << "["<< s+1 <<"] Base2.ibase2 = " << (int)pVtab[s+1] << endl;

             

                            s = s + sizeof(Base2)/4;

                            cout << "[" << s << "] Base3::_vptr->"<<endl;

                            pFun = (Fun)pVtab[s][0];

                            cout << "     [0] "; pFun();

             

                            pFun = (Fun)pVtab[s][1];

                            cout << "     [1] "; pFun();

             

                            pFun = (Fun)pVtab[s][2];

                            cout << "     [2] "; pFun();

             

                            pFun = (Fun)pVtab[s][3];

                             cout << "     [3] ";

                            cout<<pFun<<endl;

             

                            s++;

                            cout << "["<< s <<"] Base3.ibase3 = " << (int)pVtab[s] << endl;

                            s++;

                            cout << "["<< s <<"] Derive.iderive = " << (int)pVtab[s] << endl;

             

            其運(yùn)行結(jié)果如下所示:(在VC++ 2003G++ 3.4.4下)

            [0] Base1::_vptr->

                 [0] Derive::f()

                 [1] Base1::g()

                 [2] Base1::h()

                 [3] Driver::g1()

                 [4] 00000000      ç 注意:在GCC下,這里是1

            [1] Base1.ibase1 = 10

            [2] Base2::_vptr->

                 [0] Derive::f()

                 [1] Base2::g()

                 [2] Base2::h()

                 [3] 00000000      ç 注意:在GCC下,這里是1

            [3] Base2.ibase2 = 20

            [4] Base3::_vptr->

                 [0] Derive::f()

                 [1] Base3::g()

                 [2] Base3::h()

                 [3] 00000000

            [5] Base3.ibase3 = 30

            [6] Derive.iderive = 100


            使用圖片表示是下面這個(gè)樣子:

             

             

            我們可以看到:

            1) 每個(gè)父類(lèi)都有自己的虛表。

            2) 子類(lèi)的成員函數(shù)被放到了第一個(gè)父類(lèi)的表中。

            3) 內(nèi)存布局中,其父類(lèi)布局依次按聲明順序排列。

            4) 每個(gè)父類(lèi)的虛表中的f()函數(shù)都被overwrite成了子類(lèi)的f()。這樣做就是為了解決不同的父類(lèi)類(lèi)型的指針指向同一個(gè)子類(lèi)實(shí)例,而能夠調(diào)用到實(shí)際的函數(shù)。

            posted @ 2008-10-18 17:31 深邃者 閱讀(189) | 評(píng)論 (0)編輯 收藏

            c++虛函數(shù)表解析

            前言

             

            C++中的虛函數(shù)的作用主要是實(shí)現(xiàn)了多態(tài)的機(jī)制。關(guān)于多態(tài),簡(jiǎn)而言之就是用父類(lèi)型別的指針指向其子類(lèi)的實(shí)例,然后通過(guò)父類(lèi)的指針調(diào)用實(shí)際子類(lèi)的成員函數(shù)。這種技術(shù)可以讓父類(lèi)的指針有“多種形態(tài)”,這是一種泛型技術(shù)。所謂泛型技術(shù),說(shuō)白了就是試圖使用不變的代碼來(lái)實(shí)現(xiàn)可變的算法。比如:模板技術(shù),RTTI技術(shù),虛函數(shù)技術(shù),要么是試圖做到在編譯時(shí)決議,要么試圖做到運(yùn)行時(shí)決議。

             

             

            關(guān)于虛函數(shù)的使用方法,我在這里不做過(guò)多的闡述。大家可以看看相關(guān)的C++的書(shū)籍。在這篇文章中,我只想從虛函數(shù)的實(shí)現(xiàn)機(jī)制上面為大家 一個(gè)清晰的剖析。

             

            當(dāng)然,相同的文章在網(wǎng)上也出現(xiàn)過(guò)一些了,但我總感覺(jué)這些文章不是很容易閱讀,大段大段的代碼,沒(méi)有圖片,沒(méi)有詳細(xì)的說(shuō)明,沒(méi)有比較,沒(méi)有舉一反三。不利于學(xué)習(xí)和閱讀,所以這是我想寫(xiě)下這篇文章的原因。也希望大家多給我提意見(jiàn)。

             

            言歸正傳,讓我們一起進(jìn)入虛函數(shù)的世界。

             

             

            虛函數(shù)表

             

            對(duì)C++ 了解的人都應(yīng)該知道虛函數(shù)(Virtual Function)是通過(guò)一張?zhí)摵瘮?shù)表(Virtual Table)來(lái)實(shí)現(xiàn)的。簡(jiǎn)稱(chēng)為V-Table。在這個(gè)表中,主是要一個(gè)類(lèi)的虛函數(shù)的地址表,這張表解決了繼承、覆蓋的問(wèn)題,保證其容真實(shí)反應(yīng)實(shí)際的函數(shù)。這樣,在有虛函數(shù)的類(lèi)的實(shí)例中這個(gè)表被分配在了這個(gè)實(shí)例的內(nèi)存中,所以,當(dāng)我們用父類(lèi)的指針來(lái)操作一個(gè)子類(lèi)的時(shí)候,這張?zhí)摵瘮?shù)表就顯得由為重要了,它就像一個(gè)地圖一樣,指明了實(shí)際所應(yīng)該調(diào)用的函數(shù)。

             

            這里我們著重看一下這張?zhí)摵瘮?shù)表。C++的編譯器應(yīng)該是保證虛函數(shù)表的指針存在于對(duì)象實(shí)例中最前面的位置(這是為了保證取到虛函數(shù)表的有最高的性能——如果有多層繼承或是多重繼承的情況下)。 這意味著我們通過(guò)對(duì)象實(shí)例的地址得到這張?zhí)摵瘮?shù)表,然后就可以遍歷其中函數(shù)指針,并調(diào)用相應(yīng)的函數(shù)。

             

            聽(tīng)我扯了那么多,我可以感覺(jué)出來(lái)你現(xiàn)在可能比以前更加暈頭轉(zhuǎn)向了。 沒(méi)關(guān)系,下面就是實(shí)際的例子,相信聰明的你一看就明白了。

             

            假設(shè)我們有這樣的一個(gè)類(lèi):

             

            class Base {

                 public:

                        virtual void f() { cout << "Base::f" << endl; }

                        virtual void g() { cout << "Base::g" << endl; }

                        virtual void h() { cout << "Base::h" << endl; }

             

            };

             

            按照上面的說(shuō)法,我們可以通過(guò)Base的實(shí)例來(lái)得到虛函數(shù)表。 下面是實(shí)際例程:

             

                      typedef void(*Fun)(void);

             

                        Base b;

             

                        Fun pFun = NULL;

             

                        cout << "虛函數(shù)表地址:" << (int*)(&b) << endl;

                        cout << "虛函數(shù)表第一個(gè)函數(shù)地址:" << (int*)*(int*)(&b) << endl;

             

                        // Invoke the first virtual function 

                        pFun = (Fun)*((int*)*(int*)(&b));

                        pFun();

             

            實(shí)際運(yùn)行經(jīng)果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

             

            虛函數(shù)表地址:0012FED4

            虛函數(shù)表第一個(gè)函數(shù)地址:0044F148

            Base::f

             

             

            通過(guò)這個(gè)示例,我們可以看到,我們可以通過(guò)強(qiáng)行把&b轉(zhuǎn)成int *,取得虛函數(shù)表的地址,然后,再次取址就可以得到第一個(gè)虛函數(shù)的地址了,也就是Base::f(),這在上面的程序中得到了驗(yàn)證(把int* 強(qiáng)制轉(zhuǎn)成了函數(shù)指針)。通過(guò)這個(gè)示例,我們就可以知道如果要調(diào)用Base::g()Base::h(),其代碼如下:

             

                        (Fun)*((int*)*(int*)(&b)+0); // Base::f()

                        (Fun)*((int*)*(int*)(&b)+1); // Base::g()

                        (Fun)*((int*)*(int*)(&b)+2); // Base::h()

             

            這個(gè)時(shí)候你應(yīng)該懂了吧。什么?還是有點(diǎn)暈。也是,這樣的代碼看著太亂了。沒(méi)問(wèn)題,讓我畫(huà)個(gè)圖解釋一下。如下所示:

            注意:在上面這個(gè)圖中,我在虛函數(shù)表的最后多加了一個(gè)結(jié)點(diǎn),這是虛函數(shù)表的結(jié)束結(jié)點(diǎn),就像字符串的結(jié)束符“\0”一樣,其標(biāo)志了虛函數(shù)表的結(jié)束。這個(gè)結(jié)束標(biāo)志的值在不同的編譯器下是不同的。在WinXP+VS2003下,這個(gè)值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,這個(gè)值是如果1,表示還有下一個(gè)虛函數(shù)表,如果值是0,表示是最后一個(gè)虛函數(shù)表。

             

             

            下面,我將分別說(shuō)明“無(wú)覆蓋”和“有覆蓋”時(shí)的虛函數(shù)表的樣子。沒(méi)有覆蓋父類(lèi)的虛函數(shù)是毫無(wú)意義的。我之所以要講述沒(méi)有覆蓋的情況,主要目的是為了給一個(gè)對(duì)比。在比較之下,我們可以更加清楚地知道其內(nèi)部的具體實(shí)現(xiàn)。

             

            一般繼承(無(wú)虛函數(shù)覆蓋)

             

            下面,再讓我們來(lái)看看繼承時(shí)的虛函數(shù)表是什么樣的。假設(shè)有如下所示的一個(gè)繼承關(guān)系:

             

             

            請(qǐng)注意,在這個(gè)繼承關(guān)系中,子類(lèi)沒(méi)有重載任何父類(lèi)的函數(shù)。那么,在派生類(lèi)的實(shí)例中,其虛函數(shù)表如下所示:

             

            對(duì)于實(shí)例:Derive d; 的虛函數(shù)表如下:

             

            我們可以看到下面幾點(diǎn):

            1)虛函數(shù)按照其聲明順序放于表中。

            2)父類(lèi)的虛函數(shù)在子類(lèi)的虛函數(shù)前面。

             

            我相信聰明的你一定可以參考前面的那個(gè)程序,來(lái)編寫(xiě)一段程序來(lái)驗(yàn)證。

             

             

             

            一般繼承(有虛函數(shù)覆蓋)

             

            覆蓋父類(lèi)的虛函數(shù)是很顯然的事情,不然,虛函數(shù)就變得毫無(wú)意義。下面,我們來(lái)看一下,如果子類(lèi)中有虛函數(shù)重載了父類(lèi)的虛函數(shù),會(huì)是一個(gè)什么樣子?假設(shè),我們有下面這樣的一個(gè)繼承關(guān)系。

             

             

             

            為了讓大家看到被繼承過(guò)后的效果,在這個(gè)類(lèi)的設(shè)計(jì)中,我只覆蓋了父類(lèi)的一個(gè)函數(shù):f()。那么,對(duì)于派生類(lèi)的實(shí)例,其虛函數(shù)表會(huì)是下面的一個(gè)樣子:

             

             

            我們從表中可以看到下面幾點(diǎn),

            1)覆蓋的f()函數(shù)被放到了虛表中原來(lái)父類(lèi)虛函數(shù)的位置。

            2)沒(méi)有被覆蓋的函數(shù)依舊。

             

            這樣,我們就可以看到對(duì)于下面這樣的程序,

             

                        Base *b = new Derive();

             

                        b->f();

             

            b所指的內(nèi)存中的虛函數(shù)表的f()的位置已經(jīng)被Derive::f()函數(shù)地址所取代,于是在實(shí)際調(diào)用發(fā)生時(shí),是Derive::f()被調(diào)用了。這就實(shí)現(xiàn)了多態(tài)。

             

             

             

            多重繼承(無(wú)虛函數(shù)覆蓋)

             

            下面,再讓我們來(lái)看看多重繼承中的情況,假設(shè)有下面這樣一個(gè)類(lèi)的繼承關(guān)系。注意:子類(lèi)并沒(méi)有覆蓋父類(lèi)的函數(shù)。

             

             

             

            對(duì)于子類(lèi)實(shí)例中的虛函數(shù)表,是下面這個(gè)樣子:

             

            我們可以看到:

            1) 每個(gè)父類(lèi)都有自己的虛表。

            2) 子類(lèi)的成員函數(shù)被放到了第一個(gè)父類(lèi)的表中。(所謂的第一個(gè)父類(lèi)是按照聲明順序來(lái)判斷的)

             

            這樣做就是為了解決不同的父類(lèi)類(lèi)型的指針指向同一個(gè)子類(lèi)實(shí)例,而能夠調(diào)用到實(shí)際的函數(shù)。

             

             

             

             

            多重繼承(有虛函數(shù)覆蓋)

             

            下面我們?cè)賮?lái)看看,如果發(fā)生虛函數(shù)覆蓋的情況。

             

            下圖中,我們?cè)谧宇?lèi)中覆蓋了父類(lèi)的f()函數(shù)。

             

             

             

            下面是對(duì)于子類(lèi)實(shí)例中的虛函數(shù)表的圖:

             

             

            我們可以看見(jiàn),三個(gè)父類(lèi)虛函數(shù)表中的f()的位置被替換成了子類(lèi)的函數(shù)指針。這樣,我們就可以任一靜態(tài)類(lèi)型的父類(lèi)來(lái)指向子類(lèi),并調(diào)用子類(lèi)的f()了。如:

             

                        Derive d;

                        Base1 *b1 = &d;

                        Base2 *b2 = &d;

                        Base3 *b3 = &d;

                        b1->f(); //Derive::f()

                        b2->f(); //Derive::f()

                        b3->f(); //Derive::f()

             

                        b1->g(); //Base1::g()

                        b2->g(); //Base2::g()

                        b3->g(); //Base3::g()

             

             

            安全性

             

            每次寫(xiě)C++的文章,總免不了要批判一下C++。這篇文章也不例外。通過(guò)上面的講述,相信我們對(duì)虛函數(shù)表有一個(gè)比較細(xì)致的了解了。水可載舟,亦可覆舟。下面,讓我們來(lái)看看我們可以用虛函數(shù)表來(lái)干點(diǎn)什么壞事吧。

             

            一、通過(guò)父類(lèi)型的指針訪問(wèn)子類(lèi)自己的虛函數(shù)

            我們知道,子類(lèi)沒(méi)有重載父類(lèi)的虛函數(shù)是一件毫無(wú)意義的事情。因?yàn)槎鄳B(tài)也是要基于函數(shù)重載的。雖然在上面的圖中我們可以看到Base1的虛表中有Derive的虛函數(shù),但我們根本不可能使用下面的語(yǔ)句來(lái)調(diào)用子類(lèi)的自有虛函數(shù):

             

                      Base1 *b1 = new Derive();

                        b1->f1(); //編譯出錯(cuò)

             

            任何妄圖使用父類(lèi)指針想調(diào)用子類(lèi)中的未覆蓋父類(lèi)的成員函數(shù)的行為都會(huì)被編譯器視為非法,所以,這樣的程序根本無(wú)法編譯通過(guò)。但在運(yùn)行時(shí),我們可以通過(guò)指針的方式訪問(wèn)虛函數(shù)表來(lái)達(dá)到違反C++語(yǔ)義的行為。(關(guān)于這方面的嘗試,通過(guò)閱讀后面附錄的代碼,相信你可以做到這一點(diǎn))

             

             

            二、訪問(wèn)non-public的虛函數(shù)

            另外,如果父類(lèi)的虛函數(shù)是private或是protected的,但這些非public的虛函數(shù)同樣會(huì)存在于虛函數(shù)表中,所以,我們同樣可以使用訪問(wèn)虛函數(shù)表的方式來(lái)訪問(wèn)這些non-public的虛函數(shù),這是很容易做到的。

             

            如:

             

            class Base {

                private:

                        virtual void f() { cout << "Base::f" << endl; }

             

            };

             

            class Derive : public Base{

             

            };

             

            typedef void(*Fun)(void);

             

            void main() {

                Derive d;

                Fun pFun = (Fun)*((int*)*(int*)(&d)+0);

                pFun();

            }

             

             

            結(jié)束語(yǔ)

            C++這門(mén)語(yǔ)言是一門(mén)Magic的語(yǔ)言,對(duì)于程序員來(lái)說(shuō),我們似乎永遠(yuǎn)摸不清楚這門(mén)語(yǔ)言背著我們?cè)诟闪耸裁础P枰煜み@門(mén)語(yǔ)言,我們就必需要了解C++里面的那些東西,需要去了解C++中那些危險(xiǎn)的東西。不然,這是一種搬起石頭砸自己腳的編程語(yǔ)言。

             

            在文章束之前還是介紹一下自己吧。我從事軟件研發(fā)有十個(gè)年頭了,目前是軟件開(kāi)發(fā)技術(shù)主管,技術(shù)方面,主攻Unix/C/C++,比較喜歡網(wǎng)絡(luò)上的技術(shù),比如分布式計(jì)算,網(wǎng)格計(jì)算,P2PAjax等一切和互聯(lián)網(wǎng)相關(guān)的東西。管理方面比較擅長(zhǎng)于團(tuán)隊(duì)建設(shè),技術(shù)趨勢(shì)分析,項(xiàng)目管理。歡迎大家和我交流,我的MSNEmail是:haoel@hotmail.com 

             

            附錄一:VC中查看虛函數(shù)表

             

            我們可以在VCIDE環(huán)境中的Debug狀態(tài)下展開(kāi)類(lèi)的實(shí)例就可以看到虛函數(shù)表了(并不是很完整的)

            附錄 二:例程

            下面是一個(gè)關(guān)于多重繼承的虛函數(shù)表訪問(wèn)的例程:

             

            #include <iostream>

            using namespace std;

             

            class Base1 {

            public:

                        virtual void f() { cout << "Base1::f" << endl; }

                        virtual void g() { cout << "Base1::g" << endl; }

                        virtual void h() { cout << "Base1::h" << endl; }

             

            };

             

            class Base2 {

            public:

                        virtual void f() { cout << "Base2::f" << endl; }

                        virtual void g() { cout << "Base2::g" << endl; }

                        virtual void h() { cout << "Base2::h" << endl; }

            };

             

            class Base3 {

            public:

                        virtual void f() { cout << "Base3::f" << endl; }

                        virtual void g() { cout << "Base3::g" << endl; }

                        virtual void h() { cout << "Base3::h" << endl; }

            };

             

             

            class Derive : public Base1, public Base2, public Base3 {

            public:

                        virtual void f() { cout << "Derive::f" << endl; }

                        virtual void g1() { cout << "Derive::g1" << endl; }

            };

             

             

            typedef void(*Fun)(void);

             

            int main()

            {

                        Fun pFun = NULL;

             

                        Derive d;

                        int** pVtab = (int**)&d;

             

                        //Base1's vtable

                        //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);

                        pFun = (Fun)pVtab[0][0];

                        pFun();

             

                        //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);

                        pFun = (Fun)pVtab[0][1];

                        pFun();

             

                        //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);

                        pFun = (Fun)pVtab[0][2];

                        pFun();

             

                        //Derive's vtable

                        //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);

                        pFun = (Fun)pVtab[0][3];

                        pFun();

             

                        //The tail of the vtable

                        pFun = (Fun)pVtab[0][4];

                        cout<<pFun<<endl;

             

             

                        //Base2's vtable

                        //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

                        pFun = (Fun)pVtab[1][0];

                        pFun();

             

                        //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

                        pFun = (Fun)pVtab[1][1];

                        pFun();

             

                        pFun = (Fun)pVtab[1][2];

                        pFun();

             

                        //The tail of the vtable

                        pFun = (Fun)pVtab[1][3];

                        cout<<pFun<<endl;

             

             

             

                        //Base3's vtable

                        //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

                        pFun = (Fun)pVtab[2][0];

                        pFun();

             

                        //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

                        pFun = (Fun)pVtab[2][1];

                        pFun();

             

                        pFun = (Fun)pVtab[2][2];

                        pFun();

             

                        //The tail of the vtable

                        pFun = (Fun)pVtab[2][3];

                        cout<<pFun<<endl;

             

                        return 0;

            }

            posted @ 2008-10-18 17:29 深邃者 閱讀(109) | 評(píng)論 (0)編輯 收藏

            如何用 Win32 APIs 枚舉應(yīng)用程序窗口和進(jìn)程

            如何用 Win32 APIs 枚舉應(yīng)用程序窗口和進(jìn)程

            編譯:NorthTibet

            下載源代碼

            摘要

              我們?cè)诰帉?xiě)程序時(shí),常常遇到的一件事情就是要準(zhǔn)確列出系統(tǒng)中所有正在運(yùn)行的程序或者進(jìn)程。Windows 任務(wù)管理器就是這樣的一個(gè)程序。它既能列出運(yùn)行的桌面應(yīng)用程序,又能列出系統(tǒng)中所有運(yùn)行的進(jìn)程。那么,我們?cè)诔绦蛑腥绾螌?shí)現(xiàn)這樣的任務(wù)呢?本文下面將詳細(xì)討論這個(gè)問(wèn)題。


            枚舉頂層(top-level)窗口

              枚舉桌面頂層窗口相對(duì)于枚舉進(jìn)程來(lái)說(shuō)可能要容易一些。枚舉桌面頂層窗口的方法是用 EnumWindows() 函數(shù)。不要用 GetWindow()來(lái)創(chuàng)建窗口列表,因?yàn)榇翱谥g復(fù)雜的父子及同胞關(guān)系(Z-Order)容易造成混亂而使得枚舉結(jié)果不準(zhǔn)確。
              EnumWindows()有兩個(gè)參數(shù),一個(gè)是指向回調(diào)函數(shù)的指針,一個(gè)是用戶(hù)定義的 LPARAM 值, 針對(duì)每個(gè)桌面窗口(或者頂層窗口)它調(diào)用回調(diào)函數(shù)一次。然后回調(diào)函數(shù)用該窗口句柄做一些處理,比如將它添加到列表中。這個(gè)方法保證枚舉結(jié)果不會(huì)被窗口復(fù)雜的層次關(guān)系搞亂,因此,一旦有了窗口句柄,我們就可以通過(guò) GetWindowText() 得到窗口標(biāo)題。


            枚舉進(jìn)程

              建立系統(tǒng)進(jìn)程列表比枚舉窗口稍微復(fù)雜一些。這主要是因?yàn)樗玫?API 函數(shù)對(duì)于不同的 Win32 操作系統(tǒng)有依賴(lài)性。在 Windows 9x、Windows Me、Windows 2000 Professional 以及 Windows XP 中,我們可以用 ToolHelp32 庫(kù)中的 APIs 函數(shù)。但是在 Windows NT 里,我們必須用 PSAPI 庫(kù)中的 APIs 函數(shù), PSAPI 庫(kù)是 SDK 的一部分。本文我們將討論上述所有平臺(tái)中的實(shí)現(xiàn)。附帶的例子程序?qū)?duì)上述庫(kù)中的 APIs 進(jìn)行包裝,以便包裝后的函數(shù)能支持所有 Win32 操作系統(tǒng)。


            使用 ToolHelp32 庫(kù)枚舉進(jìn)程

              ToolHelp32 庫(kù)函數(shù)在 KERNEL32.dll 中,它們都是標(biāo)準(zhǔn)的 API 函數(shù)。但是 Windows NT 4.0 不提供這些函。
              ToolHelp32 庫(kù)中有各種各樣的函數(shù)可以用來(lái)枚舉系統(tǒng)中的進(jìn)程、線程以及獲取內(nèi)存和模塊信息。其中枚舉進(jìn)程 只需用如下三個(gè)的函數(shù):CreateToolhelp32Snapshot()、Process32First()和 Process32Next()。
              使用 ToolHelp32 函數(shù)的第一步是用 CreateToolhelp32Snapshot() 函數(shù)創(chuàng)建系統(tǒng)信息“快照”。這個(gè)函數(shù)可以讓你選擇存儲(chǔ)在快照中的信息類(lèi)型。如果你只是對(duì)進(jìn)程信息感興趣,那么只要包含 TH32CS_SNAPPROCESS 標(biāo)志即可。 CreateToolhelp32Snapshot() 函數(shù)返回一個(gè) HANDLE,完成調(diào)用之后,必須將此 HANDLE 傳給 CloseHandle()。
              接下來(lái)是調(diào)用一次 Process32First 函數(shù),從快照中獲取進(jìn)程列表,然后重復(fù)調(diào)用 Process32Next,直到函數(shù)返回 FALSE 為止。這樣將遍歷快照中進(jìn)程列表。這兩個(gè)函數(shù)都帶兩個(gè)參數(shù),它們分別是快照句柄和一個(gè)   PROCESSENTRY32 結(jié)構(gòu)。
              調(diào)用完 Process32First 或 Process32Next 之后,PROCESSENTRY32 中將包含系統(tǒng)中某個(gè)進(jìn)程的關(guān)鍵信息。其中進(jìn)程 ID 就存儲(chǔ)在此結(jié)構(gòu)的 th32ProcessID。此 ID 可以被傳給 OpenProcess() API 以獲得該進(jìn)程的句柄。對(duì)應(yīng)的可執(zhí)行文件名及其存放路徑存放在 szExeFile  結(jié)構(gòu)成員中。在該結(jié)構(gòu)中還可以找到其它一些有用的信息。
              注意:在調(diào)用 Process32First() 之前,一定要記住將 PROCESSENTRY32  結(jié)構(gòu)的 dwSize 成員設(shè)置成 sizeof(PROCESSENTRY32)。


            使用 PSAPI 庫(kù)枚舉進(jìn)程

              在 Windows NT 中,創(chuàng)建進(jìn)程列表使用 PSAPI 函數(shù),這些函數(shù)在 PSAPI.DLL 中。這個(gè)文件是隨 Platform SDK 一起分發(fā)的,最新版本的 Platform SDK 可以從這里下載

            使用這個(gè)庫(kù)所需的 PSAPI.h 和 PSAPI.lib 文件也在該 Platform SDK 中。
              為了使用 PSAPI 庫(kù)中的函數(shù),需將 PSAPI.lib 添加到代碼項(xiàng)目中,同時(shí)在所有調(diào)用 PSAPI API 的模塊中包含 PSAPI.h 文件。記住一定要隨可執(zhí)行文件一起分發(fā) PSAPI.DLL,因?yàn)樗浑S Windows NT 一起分發(fā)。你可以點(diǎn)擊這里單獨(dú)下載 PSAPI.DLL 的可分發(fā)版本(不用完全下載 Platform SDK)。
              與 ToolHelp32 一樣,PSAPI 庫(kù)也包含各種各樣有用的函數(shù)。由于篇幅所限,本文只討論與枚舉進(jìn)程有關(guān)函數(shù):EnumProcesses()、 EnumProcessModules()、GetModuleFileNameEx()和 GetModuleBaseName()。
              創(chuàng)建進(jìn)程列表的第一步是調(diào)用 EnumProcesses()。該函數(shù)的聲明如下:

            BOOL EnumProcesses( DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded );
              EnumProcesses()帶三個(gè)參數(shù),DWORD 類(lèi)型的數(shù)組指針 lpidProcess;該數(shù)組的大小尺寸 cb;以及一個(gè)指向 DWORD 的指針 cbNeeded,它接收返回?cái)?shù)據(jù)的長(zhǎng)度。DWORD 數(shù)組用于保存當(dāng)前運(yùn)行的進(jìn)程 IDs。cbNeeded 返回?cái)?shù)組所用的內(nèi)存大小。下面算式可以得出返回了多少進(jìn)程:nReturned = cbNeeded / sizeof(DWORD)。
              注意:雖然文檔將返回的 DWORD 命名為“cbNeeded”,實(shí)際上是沒(méi)有辦法知道到底要傳多大的數(shù)組的。EnumProcesses()根本不會(huì)在 cbNeeded 中返回一個(gè)大于 cb 參數(shù)傳遞的數(shù)組值。結(jié)果,唯一確保 EnumProcesses()函數(shù)成功的方法是分配一個(gè) DWORD 數(shù)組,并且,如果返回的 cbNeeded 等于 cb,分配一個(gè)較大的數(shù)組,并不停地嘗試直到 cbNeeded 小于 cb
              現(xiàn)在,你獲得了一個(gè)數(shù)組,其元素保存著系統(tǒng)中每個(gè)進(jìn)程的ID。如果你要想獲取進(jìn)程名,那么你必須首先獲取一個(gè)句柄。要想從進(jìn)程 ID 得到句柄,就得調(diào)用 OpenProcess()。
              一旦有了句柄,則需要得到該進(jìn)程的第一個(gè)模塊。為此調(diào)用 EnumProcessModules() API:
            EnumProcessModules( hProcess, &hModule, sizeof(hModule), &cbReturned );
              調(diào)用之后,hModule 變量中保存的將是進(jìn)程中的第一個(gè)模塊。記住進(jìn)程其實(shí)沒(méi)有名字,但進(jìn)程的第一個(gè)模塊既是該進(jìn)程的可執(zhí)行模塊。現(xiàn)在你可以用 hModule 中返回的模塊句柄調(diào)用 GetModuleFileNameEx() 或 GetModuleBaseName() API 函數(shù)獲取全路徑名,或者僅僅是進(jìn)程可執(zhí)行模塊名。兩個(gè)函數(shù)均帶四個(gè)參數(shù):進(jìn)程句柄,模塊句柄,返回名字的緩沖指針以及緩沖大小尺寸。
              用 EnumProcesses() API 返回的每一個(gè)進(jìn)程 ID 重復(fù)這個(gè)調(diào)用過(guò)程,你便可以創(chuàng)建 Windows NT 的進(jìn)程列表。


            16位進(jìn)程的處理方法

              在 Windows 95,Windows 98 和 Windows ME 中,ToolHelp32 對(duì)待16位程序一視同仁,它們與 Win32 程序一樣有自己的進(jìn)程IDs。但是在 Windows NT,Windows 2000 或 Windows XP 中情況并不是這樣。在這些操作系統(tǒng)中,16位程序運(yùn)行在所謂的 VDM 當(dāng)中(也就是DOS機(jī))。
              為了在 Windows NT,Windows 2000 和 Windows XP 中枚舉16位程序,你必須使用一個(gè)名為 VDMEnumTaskWOWEx()的函數(shù)。在源代碼模塊中必須包含 VDMDBG.h,并且 VDMDBG.lib 文件必須與項(xiàng)目鏈接。這兩個(gè)文件都在 Platform SDK 中。該函數(shù)的聲明如下:
            INT WINAPI VDMEnumTaskWOWEx( DWORD dwProcessId, TASKENUMPROCEX fp,LPARAM lparam );

              此處 dwProcessId 是 NTVDM 中擬枚舉的16位任務(wù)進(jìn)程標(biāo)示符。參數(shù) fp 是回調(diào)枚舉函數(shù)的指針。參數(shù) lparam 是用戶(hù)定義的值,它被傳遞到枚舉函數(shù)。枚舉函數(shù)應(yīng)該被定義成如下這樣:

            BOOL WINAPI Enum16( DWORD dwThreadId, 
            WORD hMod16,
            WORD hTask16,
            PSZ pszModName,
            PSZ pszFileName,
            LPARAM lpUserDefined );
              該函數(shù)針對(duì)每個(gè)運(yùn)行在 NTVDM 進(jìn)程中的16位任務(wù)調(diào)用一次,NTVDM 進(jìn)程ID將被傳入 VDMEnumTaskWOWEx()。如果想繼續(xù)枚舉則返回 FALSE,終止枚舉則返回 TRUE。注意這是與 EnumWindows()相對(duì)的。


            關(guān)于代碼

              本文附帶的代碼例子將 PSAPI 和 ToolHelp32 封裝到一個(gè)名為 EnumProcs() 的函數(shù)中。該函數(shù)的工作原理類(lèi)似 EnumWindows(),有一個(gè)指向回調(diào)函數(shù)的指針,并要對(duì)該函數(shù)進(jìn)行重復(fù)調(diào)用,針對(duì)系統(tǒng)中的每個(gè)進(jìn)程調(diào)用一次。另一個(gè)參數(shù)是用戶(hù)定義的 lParam。下面是該函數(shù)的聲明:
            BOOL WINAPI EnumProcs( PROCENUMPROC lpProc, LPARAM lParam );

            使用該函數(shù)時(shí),要象下面這樣聲明回調(diào)函數(shù):

            BOOL CALLBACK Proc( DWORD dw, WORD w16, LPCSTR lpstr, LPARAM lParam );
              參數(shù) dw 包含 ID,“w16”是16位任務(wù)的任務(wù)號(hào),如果為32位進(jìn)程則為0(在 Windows 95 中總是0),參數(shù)lpstr 指向文件名,lParam 是用戶(hù)定義的,要被傳入 EnumProcs()。
              EnumProcs() 函數(shù)通過(guò)顯示鏈接使用 ToolHelp32 和 PSAPI,而非通常所用的隱式鏈接。之所以要這樣做,主要是為了讓代碼能夠在二進(jìn)制一級(jí)兼容,從可以在所有 Win32 操作系統(tǒng)平臺(tái)上運(yùn)行。

            posted @ 2008-09-22 18:34 深邃者 閱讀(223) | 評(píng)論 (0)編輯 收藏

            如何“干凈地”終止 Win32 中的應(yīng)用程序

            如何“干凈地”終止 Win32 中的應(yīng)用程序

            編譯:Northtibet

          1. 摘要
          2. 32 位進(jìn)程(和 Windows 95 下的 16 位進(jìn)程)
          3. 16 位問(wèn)題(在 Windows NT 下)
          4. 示例代碼
          5. 摘要

              在理想環(huán)境中,某一進(jìn)程可能會(huì)通過(guò)某種形式的進(jìn)程間通信要求另一進(jìn)程關(guān)閉。不過(guò),如果你對(duì)希望其關(guān)閉的應(yīng)用程序沒(méi)有源代碼級(jí)控制權(quán),可能就沒(méi)有辦法做這樣的選擇。盡管沒(méi)有哪種方法能保證“干凈地”關(guān)閉 Win32 中的應(yīng)用程序,但你可以采取一些步驟來(lái)確保應(yīng)用程序使用最佳方法清除資源。

            32 位進(jìn)程(和 Windows 95 下的 16 位進(jìn)程)

              在 Win32 下,操作系統(tǒng)可保證在進(jìn)程關(guān)閉時(shí)清除進(jìn)程所擁有的資源。但是,這并不意味著進(jìn)程本身將有機(jī)會(huì)對(duì)磁盤(pán)執(zhí)行任何最后的信息刷新或通過(guò)遠(yuǎn)程連接執(zhí)行任何最后的通信,也不意味著進(jìn)程的 DLL 將有機(jī)會(huì)執(zhí)行其 PROCESS_DETACH 代碼。這就是通常最好避免在 Windows 95 和 Windows NT 下終止應(yīng)用程序的原因。

            如果你必須關(guān)閉進(jìn)程,請(qǐng)按照下列步驟操作:

            1. 向你打算關(guān)閉的進(jìn)程所擁有的所有頂級(jí)窗口發(fā)送一條 WM_CLOSE 消息。許多 Windows 應(yīng)用程序會(huì)通過(guò)關(guān)閉它自身來(lái)響應(yīng)此消息。

              注意:控制臺(tái)應(yīng)用程序?qū)?WM_CLOSE 的響應(yīng)取決于它是否安裝了控制處理程序。

              使用 EnumWindows() 找到目標(biāo)窗口的句柄。在回調(diào)函數(shù)中,檢查該窗口的進(jìn)程 ID 是否與要關(guān)閉的進(jìn)程相匹配。你可以通過(guò)調(diào)用 GetWindowThreadProcessId() 來(lái)執(zhí)行此操作。確定匹配項(xiàng)后,使用 PostMessage() 或 SendMessageTimeout() 向該窗口發(fā)送 WM_CLOSE 消息。
            2. 使用 WaitForSingleObject() 等待進(jìn)程的句柄。確保你使用超時(shí)值等待,因?yàn)樵诤芏嗲闆r下 WM_CLOSE 不會(huì)關(guān)閉應(yīng)用程序。記住,應(yīng)使超時(shí)值足夠長(zhǎng)(通過(guò) WaitForSingleObject() 或 SendMessageTimeout()),以便用戶(hù)可以響應(yīng)為了 處理 WM_CLOSE 消息而創(chuàng)建的任何對(duì)話框。
            3. 如果返回值為 WAIT_OBJECT_0,則應(yīng)用程序已干凈地將其自身關(guān)閉。如果返回值為 WAIT_TIMEOUT,則必須使用 TerminateProcess() 關(guān)閉應(yīng)用程序。

              注意:如果從 WaitForSingleObject() 得到的返回值不是 WAIT_OBJECT_0 或 WAIT_TIMEOUT,則應(yīng)使用 GetLastError() 找出原因。
            通過(guò)執(zhí)行上述這些步驟,你便完全有可能干凈地關(guān)閉應(yīng)用程序(無(wú)需 IPC 或用戶(hù)干預(yù))。

            16 位問(wèn)題(在 Windows NT 下)

              上述步驟適用于 Windows 95 下的 16 位應(yīng)用程序,而 Windows NT 下的 16 位應(yīng)用程序與 Windows 95 下的 16 位應(yīng)用程序的工作方式差別非常大。
              在 Windows NT 下,所有 16 位應(yīng)用程序都在虛擬 DOS 機(jī) (VDM) 中運(yùn)行。此 VDM 是作為 Windows NT 下的一個(gè) Win32 進(jìn)程 (NTVDM) 運(yùn)行的。NTVDM 進(jìn)程具有進(jìn)程 ID。你可以通過(guò) OpenProcess() 獲取該進(jìn)程的句柄,就像處理其它任何 Win32 進(jìn)程一樣。不過(guò),在 VDM 中運(yùn)行的 16 位應(yīng)用程序都沒(méi)有進(jìn)程 ID,因此你無(wú)法從 OpenProcess() 獲取進(jìn)程句柄。VDM 中的每個(gè) 16 位應(yīng)用程序都有一個(gè) 16 位任務(wù)句柄和一個(gè) 32 位執(zhí)行線程。可通過(guò)調(diào)用函數(shù) VDMEnumTaskWOWEx() 找到該任務(wù)句柄和線程 ID。有關(guān)這方面的其它信息,請(qǐng)參見(jiàn):“如何用 Win32 APIs 枚舉應(yīng)用程序窗口和進(jìn)程”。
              關(guān)閉 Windows NT 下的 16 位應(yīng)用程序的首選和最直接的方法是關(guān)閉整個(gè) NTVDM 進(jìn)程。你可以通過(guò)執(zhí)行前面所描述的步驟來(lái)完成此操作。你只需知道 NTVDM 的進(jìn)程 ID 即可,參考“如何用 Win32 APIs 枚舉應(yīng)用程序窗口和進(jìn)程”所講的方法來(lái)查找 NTVDM 的進(jìn)程 ID。此方法的缺點(diǎn)是它會(huì)關(guān)閉在該 VDM 中運(yùn)行的所有 16 位應(yīng)用程序。如果這不是你想要的結(jié)果,則需要采取其它方法。
              如果你希望關(guān)閉 NTVDM 進(jìn)程中的單個(gè) 16 位應(yīng)用程序,需要按照下列步驟操作:
            1. 向該進(jìn)程所擁有的以及與你要關(guān)閉的 16 位任務(wù)具有相同線程 ID 的所有頂級(jí)窗口發(fā)送一條 WM_CLOSE 消息。執(zhí)行此操作最有效的方法是使用 EnumWindows()。在回調(diào)函數(shù)中,檢查窗口的進(jìn)程 ID 和線程 ID 是否與要關(guān)閉的 16 位任務(wù)相匹配。請(qǐng)記住,該進(jìn)程 ID 將成為在其中運(yùn)行 16 位應(yīng)用程序的 NTVDM 的進(jìn)程 ID。
            2. 盡管你有線程 ID,但無(wú)法等待 16 位進(jìn)程的終止。因此,你必須等待任意時(shí)間長(zhǎng)度(以允許干凈關(guān)閉),然后嘗試關(guān)閉應(yīng)用程序。如果應(yīng)用程序已關(guān)閉,則此操作無(wú)效。如果應(yīng)用程序尚未關(guān)閉,則它將終止應(yīng)用程序。
            3. 使用稱(chēng)為 VDMTerminateTaskWOW() 的函數(shù)終止應(yīng)用程序,該函數(shù)可在 Vdmdbg.dll 中找到。它采用 VDM 的進(jìn)程 ID 和 16 位任務(wù)的任務(wù)編號(hào)。

              此方法允許你關(guān)閉 Windows NT 下 VDM 中的單個(gè) 16 位應(yīng)用程序。不過(guò),16 位 Windows 以及 VDM 中運(yùn)行的 WOWExec 都不能有效地清除已終止任務(wù)的資源。如果你要尋找最有可能干凈地終止 Windows NT 下的 16 位應(yīng)用程序的方法,應(yīng)考慮終止整個(gè) VDM 進(jìn)程。注意:如果你要啟動(dòng)以后可能會(huì)終止的 16 位應(yīng)用程序,請(qǐng)將 CREATE_SEPARATE_WOW_VDM 與 CreateProcess() 結(jié)合使用。

            示例代碼

              下面的示例代碼使用以下兩個(gè)函數(shù)實(shí)現(xiàn)上述用于 16 位和 32 位應(yīng)用程序的方法:TerminateApp() 和 Terminate16App()。TerminateApp() 采用一個(gè) 32 位進(jìn)程 ID 和一個(gè)超時(shí)值(以毫秒為單位)。Terminate16App()。這兩個(gè)函數(shù)都使用 DLL 函數(shù)的顯式鏈接,以便它們的二進(jìn)制文件與 Windows NT 和 Windows 95 都兼容。

               //******************

            // 頭文件 TermApp.h

            //******************

            #include <windows.h>

            #define TA_FAILED 0

            #define TA_SUCCESS_CLEAN 1

            #define TA_SUCCESS_KILL 2

            #define TA_SUCCESS_16 3


            DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout ) ;

            DWORD WINAPI Terminate16App( DWORD dwPID, DWORD dwThread,

            WORD w16Task, DWORD dwTimeout );



            //*********************

            // 實(shí)現(xiàn)代碼 TermApp.cpp

            //*********************

            #include "TermApp.h"

            #include <vdmdbg.h>

            typedef struct

            {

            DWORD dwID ;

            DWORD dwThread ;

            } TERMINFO ;

            // 聲明回調(diào)枚舉函數(shù).

            BOOL CALLBACK TerminateAppEnum( HWND hwnd, LPARAM lParam ) ;

            BOOL CALLBACK Terminate16AppEnum( HWND hwnd, LPARAM lParam ) ;

            /*----------------------------------------------------------------

            DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout )

            功能:

            關(guān)閉 32-位進(jìn)程(或 Windows 95 下的 16-位進(jìn)程)

            參數(shù):

            dwPID

            要關(guān)閉之進(jìn)程的進(jìn)程 ID.

            dwTimeout

            進(jìn)程關(guān)閉前等待的毫秒時(shí)間.

            返回值:

            TA_FAILED —— 如果關(guān)閉失敗.

            TA_SUCCESS_CLEAN —— 如果使用 WM_CLOSE 關(guān)閉了進(jìn)程.

            TA_SUCCESS_KILL —— 如果使用 TerminateProcess() 關(guān)閉了進(jìn)程.

            返回值的定義參見(jiàn)頭文件.

            ----------------------------------------------------------------*/

            DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout )

            {

            HANDLE hProc ;

            DWORD dwRet ;

            // 如果無(wú)法用 PROCESS_TERMINATE 權(quán)限打開(kāi)進(jìn)程,那么立即放棄。

            hProc = OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, FALSE,dwPID);

            if(hProc == NULL)

            {
            return TA_FAILED ;
            }


            // TerminateAppEnum() 將 WM_CLOSE 消息發(fā)到所有其進(jìn)程ID 與你所提供的進(jìn)程ID 匹配的窗口.

            EnumWindows((WNDENUMPROC)TerminateAppEnum, (LPARAM) dwPID) ;


            // 等待處理,如果成功,OK。如果超時(shí),則干掉它.

            if(WaitForSingleObject(hProc, dwTimeout)!=WAIT_OBJECT_0)

            dwRet=(TerminateProcess(hProc,0)?TA_SUCCESS_KILL:TA_FAILED);

            else

            dwRet = TA_SUCCESS_CLEAN ;

            CloseHandle(hProc) ;

            return dwRet ;

            }


            /*----------------------------------------------------------------

            DWORD WINAPI Terminate16App( DWORD dwPID, DWORD dwThread,

            WORD w16Task, DWORD dwTimeout )

            功能:

            關(guān)閉 Win16 應(yīng)用程序.

            參數(shù):

            dwPID

            16-位程序運(yùn)行其中的 NTVDM 進(jìn)程 ID.

            dwThread

            16-位程序中執(zhí)行線程的線程 ID.

            w16Task

            應(yīng)用程序的 16-位任務(wù)句柄.

            dwTimeout

            任務(wù)關(guān)閉前等待的毫秒時(shí)間.



            返回值:

            如果成功, 返回 TA_SUCCESS_16

            如果不成功, 返回 TA_FAILED.

            返回值的定義參見(jiàn)該函數(shù)的頭文件.

            注意:

            你可以通過(guò) VDMEnumTaskWOW() 或 VDMEnumTaskWOWEx() 函數(shù)獲得 Win16 和線程 ID.

            ----------------------------------------------------------------*/

            DWORD WINAPI Terminate16App( DWORD dwPID, DWORD dwThread,

            WORD w16Task, DWORD dwTimeout )

            {

            HINSTANCE hInstLib ;

            TERMINFO info ;

            // 你必須通過(guò)外部鏈接調(diào)用函數(shù),以便代碼在所有 Win32 平臺(tái)上都兼容。

            BOOL (WINAPI *lpfVDMTerminateTaskWOW)(DWORD dwProcessId,WORD htask) ;

            hInstLib = LoadLibraryA( "VDMDBG.DLL" ) ;

            if( hInstLib == NULL )
            return TA_FAILED ;

            // 獲得函數(shù)過(guò)程地址.

            lpfVDMTerminateTaskWOW = (BOOL (WINAPI *)(DWORD, WORD ))

            GetProcAddress( hInstLib, "VDMTerminateTaskWOW" ) ;

            if( lpfVDMTerminateTaskWOW == NULL )

            {

            FreeLibrary( hInstLib ) ;

            return TA_FAILED ;

            }

            // 向所有匹配進(jìn)程 ID 和線程的窗口發(fā)送 WM_CLOSE 消息.

            info.dwID = dwPID ;

            info.dwThread = dwThread ;

            EnumWindows((WNDENUMPROC)Terminate16AppEnum, (LPARAM) &info) ;

            // 等待.

            Sleep( dwTimeout ) ;

            // 然后終止.

            lpfVDMTerminateTaskWOW(dwPID, w16Task) ;

            FreeLibrary( hInstLib ) ;

            return TA_SUCCESS_16 ;

            }



            BOOL CALLBACK TerminateAppEnum( HWND hwnd, LPARAM lParam )

            {

            DWORD dwID ;

            GetWindowThreadProcessId(hwnd, &dwID) ;

            if(dwID == (DWORD)lParam)

            {

            PostMessage(hwnd, WM_CLOSE, 0, 0) ;

            }

            return TRUE ;
            }



            BOOL CALLBACK Terminate16AppEnum( HWND hwnd, LPARAM lParam )

            {

            DWORD dwID ;

            DWORD dwThread ;

            TERMINFO *termInfo ;

            termInfo = (TERMINFO *)lParam ;

            dwThread = GetWindowThreadProcessId(hwnd, &dwID) ;

            if(dwID == termInfo->dwID && termInfo->dwThread == dwThread )

            {

            PostMessage(hwnd, WM_CLOSE, 0, 0) ;

            }

            return TRUE ;

            }

            posted @ 2008-09-22 18:33 深邃者 閱讀(221) | 評(píng)論 (0)編輯 收藏

            SQL

                 摘要: SQL操作全集 SQL操作全集 下列語(yǔ)句部分是Mssql語(yǔ)句,不可以在access中使用。 SQL分類(lèi):  DDL—數(shù)據(jù)定義語(yǔ)言(CREATE,ALTER,DROP,DECLARE)  DML—數(shù)據(jù)操縱語(yǔ)言(SELECT,DELETE,UPDATE,INSERT)  DCL—數(shù)據(jù)控制語(yǔ)言(GRANT,REVOKE,COMMIT,ROLLBACK) 首先,簡(jiǎn)要介紹基...  閱讀全文

            posted @ 2008-09-17 09:45 深邃者 閱讀(108) | 評(píng)論 (0)編輯 收藏

            LIST控件

            CListCtrl使用技巧

            以下未經(jīng)說(shuō)明,listctrl默認(rèn)view 風(fēng)格為report


            1. CListCtrl 風(fēng)格

                  LVS_ICON: 為每個(gè)item顯示大圖標(biāo)
                  LVS_SMALLICON: 為每個(gè)item顯示小圖標(biāo)
                  LVS_LIST: 顯示一列帶有小圖標(biāo)的item
                  LVS_REPORT: 顯示item詳細(xì)資料

                  直觀的理解:windows資源管理器,“查看”標(biāo)簽下的“大圖標(biāo),小圖標(biāo),列表,詳細(xì)資料”



            2. 設(shè)置listctrl 風(fēng)格及擴(kuò)展風(fēng)格

                  LONG lStyle;
                  lStyle = GetWindowLong(m_list.m_hWnd, GWL_STYLE);//獲取當(dāng)前窗口style
                  lStyle &= ~LVS_TYPEMASK; //清除顯示方式位
                  lStyle |= LVS_REPORT; //設(shè)置style
                  SetWindowLong(m_list.m_hWnd, GWL_STYLE, lStyle);//設(shè)置style
             
                  DWORD dwStyle = m_list.GetExtendedStyle();
                  dwStyle |= LVS_EX_FULLROWSELECT;//選中某行使整行高亮(只適用與report風(fēng)格的listctrl)
                  dwStyle |= LVS_EX_GRIDLINES;//網(wǎng)格線(只適用與report風(fēng)格的listctrl)
                  dwStyle |= LVS_EX_CHECKBOXES;//item前生成checkbox控件
                  m_list.SetExtendedStyle(dwStyle); //設(shè)置擴(kuò)展風(fēng)格
             
                  注:listview的style請(qǐng)查閱msdn
                  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wceshellui5/html/wce50lrflistviewstyles.asp

             


            3. 插入數(shù)據(jù)

                  m_list.InsertColumn( 0, "ID", LVCFMT_LEFT, 40 );//插入列
                  m_list.InsertColumn( 1, "NAME", LVCFMT_LEFT, 50 );
                  int nRow = m_list.InsertItem(0, “11”);//插入行
                  m_list.SetItemText(nRow, 1, “jacky”);//設(shè)置數(shù)據(jù)

             


            4. 一直選中item

                選中style中的Show selection always,或者在上面第2點(diǎn)中設(shè)置LVS_SHOWSELALWAYS



            5. 選中和取消選中一行

                int nIndex = 0;
                //選中
                m_list.SetItemState(nIndex, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED);
                //取消選中
                m_list.SetItemState(nIndex, 0, LVIS_SELECTED|LVIS_FOCUSED);
             


            6. 得到listctrl中所有行的checkbox的狀態(tài)

                  m_list.SetExtendedStyle(LVS_EX_CHECKBOXES);
                  CString str;
                  for(int i=0; i<m_list.GetItemCount(); i++)
                  {
                       if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED || m_list.GetCheck(i))
                       {
                            str.Format(_T("第%d行的checkbox為選中狀態(tài)"), i);
                            AfxMessageBox(str);
                       }
                  }



            7. 得到listctrl中所有選中行的序號(hào)


                  方法一:
                  CString str;
                  for(int i=0; i<m_list.GetItemCount(); i++)
                  {
                       if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED )
                       {
                            str.Format(_T("選中了第%d行"), i);
                            AfxMessageBox(str);
                       }
                  }

                  方法二:
                  POSITION pos = m_list.GetFirstSelectedItemPosition();
                  if (pos == NULL)
                       TRACE0("No items were selected!\n");
                  else
                  {
                       while (pos)
                       {
                            int nItem = m_list.GetNextSelectedItem(pos);
                            TRACE1("Item %d was selected!\n", nItem);
                            // you could do your own processing on nItem here
                       }
                  }



            8. 得到item的信息

                  TCHAR szBuf[1024];
                  LVITEM lvi;
                  lvi.iItem = nItemIndex;
                  lvi.iSubItem = 0;
                  lvi.mask = LVIF_TEXT;
                  lvi.pszText = szBuf;
                  lvi.cchTextMax = 1024;
                  m_list.GetItem(&lvi);

                  關(guān)于得到設(shè)置item的狀態(tài),還可以參考msdn文章
                  Q173242: Use Masks to Set/Get Item States in CListCtrl
                           http://support.microsoft.com/kb/173242/en-us



            9. 得到listctrl的所有列的header字符串內(nèi)容

                  LVCOLUMN lvcol;
                  char  str[256];
                  int   nColNum;
                  CString  strColumnName[4];//假如有4列

                  nColNum = 0;
                  lvcol.mask = LVCF_TEXT;
                  lvcol.pszText = str;
                  lvcol.cchTextMax = 256;
                  while(m_list.GetColumn(nColNum, &lvcol))
                  {
                       strColumnName[nColNum] = lvcol.pszText;
                       nColNum++;
                  }



            10. 使listctrl中一項(xiàng)可見(jiàn),即滾動(dòng)滾動(dòng)條

                m_list.EnsureVisible(i, FALSE);


            11. 得到listctrl列數(shù)

                int nHeadNum = m_list.GetHeaderCtrl()->GetItemCount();


            12. 刪除所有列

                  方法一:
                     while ( m_list.DeleteColumn (0))
                   因?yàn)槟銊h除了第一列后,后面的列會(huì)依次向上移動(dòng)。

                  方法二:
                  int nColumns = 4;
                  for (int i=nColumns-1; i>=0; i--)
                      m_list.DeleteColumn (i);



            13. 得到單擊的listctrl的行列號(hào)

                  添加listctrl控件的NM_CLICK消息相應(yīng)函數(shù)
                  void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
                  {
                       // 方法一:
                       /*
                       DWORD dwPos = GetMessagePos();
                       CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
              
                       m_list.ScreenToClient(&point);
              
                       LVHITTESTINFO lvinfo;
                       lvinfo.pt = point;
                       lvinfo.flags = LVHT_ABOVE;
                
                       int nItem = m_list.SubItemHitTest(&lvinfo);
                       if(nItem != -1)
                       {
                            CString strtemp;
                            strtemp.Format("單擊的是第%d行第%d列", lvinfo.iItem, lvinfo.iSubItem);
                            AfxMessageBox(strtemp);
                       }
                      */
              
                      // 方法二:
                      /*
                       NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
                       if(pNMListView->iItem != -1)
                       {
                            CString strtemp;
                            strtemp.Format("單擊的是第%d行第%d列",
                                            pNMListView->iItem, pNMListView->iSubItem);
                            AfxMessageBox(strtemp);
                       }
                      */
                       *pResult = 0;
                  }

             


            14. 判斷是否點(diǎn)擊在listctrl的checkbox上

                  添加listctrl控件的NM_CLICK消息相應(yīng)函數(shù)
                  void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
                  {
                       DWORD dwPos = GetMessagePos();
                       CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
              
                       m_list.ScreenToClient(&point);
              
                       LVHITTESTINFO lvinfo;
                       lvinfo.pt = point;
                       lvinfo.flags = LVHT_ABOVE;
                
                       UINT nFlag;
                       int nItem = m_list.HitTest(point, &nFlag);
                       //判斷是否點(diǎn)在checkbox上
                       if(nFlag == LVHT_ONITEMSTATEICON)
                       {
                            AfxMessageBox("點(diǎn)在listctrl的checkbox上");
                       }
                       *pResult = 0;
                  }



            15. 右鍵點(diǎn)擊listctrl的item彈出菜單

                  添加listctrl控件的NM_RCLICK消息相應(yīng)函數(shù)
                  void CTest6Dlg::OnRclickList1(NMHDR* pNMHDR, LRESULT* pResult)
                  {
                       NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
                       if(pNMListView->iItem != -1)
                       {
                            DWORD dwPos = GetMessagePos();
                            CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
               
                            CMenu menu;
                            VERIFY( menu.LoadMenu( IDR_MENU1 ) );
                            CMenu* popup = menu.GetSubMenu(0);
                            ASSERT( popup != NULL );
                            popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this );
                       }
                       *pResult = 0;
              }


             


            16. item切換焦點(diǎn)時(shí)(包括用鍵盤(pán)和鼠標(biāo)切換item時(shí)),狀態(tài)的一些變化順序

                  添加listctrl控件的LVN_ITEMCHANGED消息相應(yīng)函數(shù)
                  void CTest6Dlg::OnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
                  {
                       NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
                       // TODO: Add your control notification handler code here
               
                       CString sTemp;
             
                       if((pNMListView->uOldState & LVIS_FOCUSED) == LVIS_FOCUSED &&
                        (pNMListView->uNewState & LVIS_FOCUSED) == 0)
                       {
                            sTemp.Format("%d losted focus",pNMListView->iItem);
                       }
                       else if((pNMListView->uOldState & LVIS_FOCUSED) == 0 &&
                           (pNMListView->uNewState & LVIS_FOCUSED) == LVIS_FOCUSED)
                       {
                            sTemp.Format("%d got focus",pNMListView->iItem);
                       }
             
                       if((pNMListView->uOldState & LVIS_SELECTED) == LVIS_SELECTED &&
                        (pNMListView->uNewState & LVIS_SELECTED) == 0)
                       {
                            sTemp.Format("%d losted selected",pNMListView->iItem);
                       }
                       else if((pNMListView->uOldState & LVIS_SELECTED) == 0 &&
                        (pNMListView->uNewState & LVIS_SELECTED) == LVIS_SELECTED)
                       {
                            sTemp.Format("%d got selected",pNMListView->iItem);
                       }
               
                       *pResult = 0;
                  }




            17. 得到另一個(gè)進(jìn)程里的listctrl控件的item內(nèi)容

            http://www.codeproject.com/threads/int64_memsteal.asp



            18. 選中l(wèi)istview中的item

            Q131284: How To Select a Listview Item Programmatically
            http://support.microsoft.com/kb/131284/en-us



            19. 如何在CListView中使用CListCtrl的派生類(lèi)

            http://www.codeguru.com/cpp/controls/listview/introduction/article.php/c919/



            20. listctrl的subitem添加圖標(biāo)

                  m_list.SetExtendedStyle(LVS_EX_SUBITEMIMAGES);
                  m_list.SetItem(..); //具體參數(shù)請(qǐng)參考msdn

             


            21. 在CListCtrl顯示文件,并根據(jù)文件類(lèi)型來(lái)顯示圖標(biāo)

                  網(wǎng)上找到的代碼,share
                  BOOL CTest6Dlg::OnInitDialog()
                  {
                       CDialog::OnInitDialog();
              
                       HIMAGELIST himlSmall;
                       HIMAGELIST himlLarge;
                       SHFILEINFO sfi;
                       char  cSysDir[MAX_PATH];
                       CString  strBuf;
             
                       memset(cSysDir, 0, MAX_PATH);
              
                       GetWindowsDirectory(cSysDir, MAX_PATH);
                       strBuf = cSysDir;
                       sprintf(cSysDir, "%s", strBuf.Left(strBuf.Find("\\")+1));
             
                       himlSmall = (HIMAGELIST)SHGetFileInfo ((LPCSTR)cSysDir, 
                                  0, 
                                  &sfi,
                                  sizeof(SHFILEINFO), 
                                  SHGFI_SYSICONINDEX | SHGFI_SMALLICON );
              
                       himlLarge = (HIMAGELIST)SHGetFileInfo((LPCSTR)cSysDir, 
                                  0, 
                                  &sfi, 
                                  sizeof(SHFILEINFO), 
                                  SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
              
                       if (himlSmall && himlLarge)
                       {
                            ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST,
                                         (WPARAM)LVSIL_SMALL, (LPARAM)himlSmall);
                            ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST,
                                         (WPARAM)LVSIL_NORMAL, (LPARAM)himlLarge);
                       }
                       return TRUE;  // return TRUE  unless you set the focus to a control
                  }
             
                  void CTest6Dlg::AddFiles(LPCTSTR lpszFileName, BOOL bAddToDocument)
                  {
                       int nIcon = GetIconIndex(lpszFileName, FALSE, FALSE);
                       CString strSize;
                       CFileFind filefind;
             
                       //  get file size
                       if (filefind.FindFile(lpszFileName))
                       {
                            filefind.FindNextFile();
                            strSize.Format("%d", filefind.GetLength());
                       }
                       else
                            strSize = "0";
              
                       // split path and filename
                       CString strFileName = lpszFileName;
                       CString strPath;
             
                       int nPos = strFileName.ReverseFind('\\');
                       if (nPos != -1)
                       {
                            strPath = strFileName.Left(nPos);
                            strFileName = strFileName.Mid(nPos + 1);
                       }
              
                       // insert to list
                       int nItem = m_list.GetItemCount();
                       m_list.InsertItem(nItem, strFileName, nIcon);
                       m_list.SetItemText(nItem, 1, strSize);
                       m_list.SetItemText(nItem, 2, strFileName.Right(3));
                       m_list.SetItemText(nItem, 3, strPath);
                  }
             
                  int CTest6Dlg::GetIconIndex(LPCTSTR lpszPath, BOOL bIsDir, BOOL bSelected)
                  {
                       SHFILEINFO sfi;
                       memset(&sfi, 0, sizeof(sfi));
              
                       if (bIsDir)
                       {
                        SHGetFileInfo(lpszPath, 
                                     FILE_ATTRIBUTE_DIRECTORY, 
                                     &sfi, 
                                     sizeof(sfi), 
                                     SHGFI_SMALLICON | SHGFI_SYSICONINDEX |
                                     SHGFI_USEFILEATTRIBUTES |(bSelected ? SHGFI_OPENICON : 0)); 
                        return  sfi.iIcon;
                       }
                       else
                       {
                        SHGetFileInfo (lpszPath, 
                                     FILE_ATTRIBUTE_NORMAL, 
                                     &sfi, 
                                     sizeof(sfi), 
                                     SHGFI_SMALLICON | SHGFI_SYSICONINDEX | 
                                     SHGFI_USEFILEATTRIBUTES | (bSelected ? SHGFI_OPENICON : 0));
                        return   sfi.iIcon;
                       }
                       return  -1;
                  }



            22. listctrl內(nèi)容進(jìn)行大數(shù)據(jù)量更新時(shí),避免閃爍

                  m_list.SetRedraw(FALSE);
                  //更新內(nèi)容
                  m_list.SetRedraw(TRUE);
                  m_list.Invalidate();
                  m_list.UpdateWindow();
             
            或者參考

            http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_mfc_cwnd.3a3a.setredraw.asp



            23. listctrl排序

            Q250614:How To Sort Items in a CListCtrl in Report View
            http://support.microsoft.com/kb/250614/en-us



            24. 在listctrl中選中某個(gè)item時(shí)動(dòng)態(tài)改變其icon或bitmap

            Q141834: How to change the icon or the bitmap of a CListCtrl item in Visual C++
            http://support.microsoft.com/kb/141834/en-us



            25. 在添加item后,再I(mǎi)nsertColumn()后導(dǎo)致整列數(shù)據(jù)移動(dòng)的問(wèn)題

            Q151897: CListCtrl::InsertColumn() Causes Column Data to Shift
            http://support.microsoft.com/kb/151897/en-us



            26. 關(guān)于listctrl第一列始終居左的問(wèn)題

            解決辦法:把第一列當(dāng)一個(gè)虛列,從第二列開(kāi)始插入列及數(shù)據(jù),最后刪除第一列。
                 
            具體解釋參閱   http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/listview/structures/lvcolumn.asp

             


            27. 鎖定column header的拖動(dòng)

            http://msdn.microsoft.com/msdnmag/issues/03/06/CQA/



            28. 如何隱藏clistctrl的列

                把需隱藏的列的寬度設(shè)為0,然后檢測(cè)當(dāng)該列為隱藏列時(shí),用上面第27點(diǎn)的鎖定column 的拖動(dòng)來(lái)實(shí)現(xiàn)


            29. listctrl進(jìn)行大數(shù)據(jù)量操作時(shí),使用virtual list   

            http://www.codeguru.com/cpp/controls/listview/advanced/article.php/c4151/
            http://www.codeproject.com/listctrl/virtuallist.asp



            30. 關(guān)于item只能顯示259個(gè)字符的問(wèn)題

            解決辦法:需要在item上放一個(gè)edit。



            31. 響應(yīng)在listctrl的column header上的鼠標(biāo)右鍵單擊

            Q125694: How To Find Out Which Listview Column Was Right-Clicked
            http://support.microsoft.com/kb/125694/en-us



            32. 類(lèi)似于windows資源管理器的listview

            Q234310: How to implement a ListView control that is similar to Windows Explorer by using DirLV.exe
            http://support.microsoft.com/kb/234310/en-us

             


            33. 在ListCtrl中OnTimer只響應(yīng)兩次的問(wèn)題

            Q200054:
            PRB: OnTimer() Is Not Called Repeatedly for a List Control
            http://support.microsoft.com/kb/200054/en-us


            34. 以下為一些為實(shí)現(xiàn)各種自定義功能的listctrl派生類(lèi)

                      (1)    拖放       
                               http://www.codeproject.com/listctrl/dragtest.asp

                               在CListCtrl和CTreeCtrl間拖放
                               http://support.microsoft.com/kb/148738/en-us
             
                      (2)    多功能listctrl
                               支持subitem可編輯,圖標(biāo),radiobutton,checkbox,字符串改變顏色的類(lèi)
                               http://www.codeproject.com/listctrl/quicklist.asp
             
                               支持排序,subitem可編輯,subitem圖標(biāo),subitem改變顏色的類(lèi)
                               http://www.codeproject.com/listctrl/ReportControl.asp

                      (3)    subitem中顯示超鏈接
                               http://www.codeproject.com/listctrl/CListCtrlLink.asp

                      (4)    subitem的tooltip提示
                               http://www.codeproject.com/listctrl/ctooltiplistctrl.asp

                      (5)    subitem中顯示進(jìn)度條   
                               http://www.codeproject.com/listctrl/ProgressListControl.asp
                               http://www.codeproject.com/listctrl/napster.asp
                               http://www.codeguru.com/Cpp/controls/listview/article.php/c4187/

                      (6)    動(dòng)態(tài)改變subitem的顏色和背景色
                                http://www.codeproject.com/listctrl/highlightlistctrl.asp
                                http://www.codeguru.com/Cpp/controls/listbox/colorlistboxes/article.php/c4757/
             
                      (7)    類(lèi)vb屬性對(duì)話框
                                http://www.codeproject.com/listctrl/propertylistctrl.asp
                                http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c995/
                                http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c1041/
             
                      (8)    選中subitem(只高亮選中的item)
                                http://www.codeproject.com/listctrl/SubItemSel.asp
                                http://www.codeproject.com/listctrl/ListSubItSel.asp
             
                      (9)    改變行高
                                http://www.codeproject.com/listctrl/changerowheight.asp
             
                      (10)   改變行顏色
                                http://www.codeproject.com/listctrl/coloredlistctrl.asp
             
                      (11)   可編輯subitem的listctrl
                                http://www.codeproject.com/listctrl/nirs2000.asp
                                http://www.codeproject.com/listctrl/editing_subitems_in_listcontrol.asp
             
                      (12)   subitem可編輯,插入combobox,改變行顏色,subitem的tooltip提示
                                http://www.codeproject.com/listctrl/reusablelistcontrol.asp
             
                      (13)   header 中允許多行字符串
                                http://www.codeproject.com/listctrl/headerctrlex.asp
             
                      (14)   插入combobox
                                http://www.codeguru.com/Cpp/controls/listview/editingitemsandsubitem/article.php/c979/
             
                      (15)   添加背景圖片
                                http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c4173/
                                http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c983/
                                http://www.vchelp.net/vchelp/archive.asp?type_id=9&class_id=1&cata_id=1&article_id=1088&search_term=
               
                      (16)  自適應(yīng)寬度的listctrl
                                http://www.codeproject.com/useritems/AutosizeListCtrl.asp

                      (17)  改變ListCtrl高亮?xí)r的顏色(默認(rèn)為藍(lán)色)
                               處理 NM_CUSTOMDRAW
                       http://www.codeproject.com/listctrl/lvcustomdraw.asp

            posted @ 2008-09-16 16:24 深邃者 閱讀(394) | 評(píng)論 (0)編輯 收藏

            僅列出標(biāo)題
            共5頁(yè): 1 2 3 4 5 
            综合人妻久久一区二区精品| 久久99精品久久久久久不卡 | 午夜精品久久久久9999高清| 精品久久久久久久国产潘金莲 | 国产午夜福利精品久久| 日韩美女18网站久久精品| 亚洲av成人无码久久精品| 91精品国产91久久久久久蜜臀| 亚洲国产天堂久久久久久 | 囯产精品久久久久久久久蜜桃| 国产精品美女久久久久| 久久九九久精品国产| 久久精品中文字幕无码绿巨人| 久久99精品久久久久久秒播| 亚洲欧美日韩中文久久| 久久婷婷五月综合97色直播| 久久亚洲AV成人出白浆无码国产 | 久久亚洲AV成人无码| 色噜噜狠狠先锋影音久久| 久久精品国产亚洲AV不卡| 久久久久四虎国产精品| 国产精品久久久久久五月尺| 99久久免费只有精品国产| 亚洲国产精品无码久久98| 久久久久国产日韩精品网站| 国产91久久精品一区二区| 久久99热这里只频精品6| 爱做久久久久久| 久久偷看各类wc女厕嘘嘘| 伊人久久大香线蕉综合热线| AV狠狠色丁香婷婷综合久久| 久久国产亚洲精品| 九九久久精品国产| 久久综合九色综合97_久久久| 亚洲精品高清国产一线久久| 亚洲伊人久久成综合人影院 | 日韩中文久久| 精品久久久久久无码免费| 久久国产精品99精品国产987| 伊人久久综合无码成人网| 色播久久人人爽人人爽人人片aV|