• <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

            導航

            <2025年6月>
            25262728293031
            1234567
            891011121314
            15161718192021
            22232425262728
            293012345

            統計

            常用鏈接

            留言簿(2)

            隨筆檔案

            文章檔案

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            #

            進程通信

            Win32應用程序中進程間通信方法分析與比較

            來源:Intetnet

            1 進程與進程通信

            進程是裝入內存并準備執行的程序,每個進程都有私有的虛擬地址空間,由代碼、數據以及它可利用的系統資源(如文件、管道等)組成。多進程/多線程是Windows操作系統的一個基本特征。Microsoft Win32應用編程接口(Application Programming Interface, API)提供了大量支持應用程序間數據共享和交換的機制,這些機制行使的活動稱為進程間通信(InterProcess Communication, IPC),進程通信就是指不同進程間進行數據共享和數據交換。
            正因為使用Win32 API進行進程通信方式有多種,如何選擇恰當的通信方式就成為應用開發中的一個重要問題,下面本文將對Win32中進程通信的幾種方法加以分析和比較。

            2 進程通信方法

            2.1

            文件映射


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

            共享內存


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

            匿名管道


            管道(Pipe)是一種具有兩個端點的通信通道:有一端句柄的進程可以和有另一端句柄的進程通信。管道可以是單向-一端是只讀的,另一端點是只寫的;也可以是雙向的一管道的兩端點既可讀也可寫。
            匿名管道(Anonymous Pipe)是在父進程和子進程之間,或同一父進程的兩個子進程之間傳輸數據的無名字的單向管道。通常由父進程創建管道,然后由要通信的子進程繼承通道的讀端點句柄或寫端點句柄,然后實現通信。父進程還可以建立兩個或更多個繼承匿名管道讀和寫句柄的子進程。這些子進程可以使用管道直接通信,不需要通過父進程。
            匿名管道是單機上實現子進程標準I/O重定向的有效方法,它不能在網上使用,也不能用于兩個不相關的進程之間。
            2.4

            命名管道


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

            郵件槽


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

            剪貼板


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

            Sockets


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

            3 結束語

            Win32 API為應用程序實現進程間通信提供了如此多種選擇方案,那么開發者如何進行選擇呢?通常在決定使用哪種IPC方法之前應考慮下一些問題,如應用程序是在網絡環境下還是在單機環境下工作等。

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

            vs2005 調試

            1,斷點設置有技巧:

            1)設置條件斷點,比如i==10,變量改變時斷點;

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

            3)When Hit,這個選項可以讓我們在命中斷點后做一些事情,包括輸出一些內容,或者調用宏,比如輸出一個程序中變量的值;

            4)利用斷點的Filter功能,比如我希望斷點只有被機器名為yizhu的機器訪問才能觸發;

            具體參見:一篇介紹VS2005調試斷點技巧的文章

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

               調試時,打開Debug->Window->Modules,在窗口中顯示的就是當前進程加載的所有dll及其詳細信息,如果

            斷點無法擊中,可以檢查這里,看是否有匹配的pdb文件或者是加載了錯誤的dll

            3. 已經開始調試的工程加入另外的進程并且調試

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

            4. 同時啟動多進程進行調試

              在Solution的屬性中的Common Properties->Startup Project。選擇Multiple startup projects。這個選項是可以記憶的,下次打開可以直接調試,非常方便

            5.調試Windows Service

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

            private static void DebugRun(string[] args)

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

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

            3)加入2種調試代碼

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

             

            6,遠程調試技術


                顧名思義,就是要調試的程序和調試器本身并不在一臺機器上。由于虛擬機技術的盛行,在虛擬機里面運行待調試的程序,而在外面運行調試器,也是一種比較流行的做法。

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

            2 怎樣使用遠程調試
               使用vs2005進行遠程調試,詳細的介紹參考:http://support.microsoft.com/kb/910448

               簡單的來說,
               1)在被調試的機器上面運行Msvsmon.exe
               2)在調試機器上面運行vs2005,并attach到遠程機器的某個進程

            3 注意事項

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

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

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

            windbg調試器

            WinDbg調試器

            你可以從微軟網站上下載到的調試器:

            · KD-內核調試器。你可以用它來調試藍屏一類的系統問題。如果是開發設備驅動程序是少不了它的。

            · CDB-命令行調試器。這是一個命令行程序

            · NTSD-NT調試器。這是一個用戶模式調試器,可以用來調試用戶模式應用程序。它實際上是一個CDB的windows UI增強。

            · WinDbg-用一個漂亮的UI包裝了KD和NTSD。WinDbg即可以調試內核模式,也可以調試用戶模式程序。

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

             

            WinDbg實際上包裝了NTSD和KD并且提供了一個更好用的用戶界面。它也提供了命令行開關,比如最小化啟動(-m),附加到一PID指定的進程(-p)以及自動打開崩潰文件(-z)。它支持三種類型的命令。

            ·  Regular commands(比如: k) 用來調試進程

            ·  Dot commands(比如:.sympath)用來控制調試器

            ·  Extension commands(比如: !handle)-這些命令屬于可以用來添加到WinDbg的自定義命令;它們用擴展DLL的輸出函數來實現。

             

            PDB文件

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

            配置WinDbg


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

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

             

            WINDBG命令

            調試前的必備工作

            在開始調試前首先要做的工作是設置好符號(Symbols)路徑。沒有符號,你看到的調用堆棧基本上毫無意義。Microsoft的操作系統符號文件(PDB)是對外公開的。另外請注意在編譯你自己的程序選擇生成PDB文件的選項。如果設置好符號路徑后,調用堆棧看起來還是不對。可以使用lm, !sym noisy, !reload 等命令來驗證符號路徑是否正確。

            Windbg也支持源碼級的調試。在開始源碼調試前,你需要用.srcpath設置源代碼路徑。如果你是在生成所執行代碼的機器上進行調試,符號文件中的源碼路徑會指向正確的位置,所以不需要設置源代碼路徑。如果所執行代碼是在另一臺機器上生成的,你可以將所用的源碼拷貝(保持原有的目錄結構)的一個可以訪問的文件夾(可以是網絡路徑)并將源代碼路徑設為該文件夾的路徑。注意如果是遠程調試,你需要使用.lsrcpath來設置源碼路徑。

             

            2)啟動Debugger

            Windbg可以用于如下三種調試:

            (1)遠程調試:你可以從機器A上調試在機器B上執行的程序。具體步驟如下:
              在機器B上啟動一個調試窗口(Debug Session)。你可以直接在Windbg下運行一個程序或者將Windbg附加(Attach)到一個進程。
              在機器B的Windbg命令窗口上啟動一個遠程調試接口(remote):

                .server npipe:pipe=PIPE_NAME

                 PIPE_NAME是該接口的名字。
            在機器A上運行:

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

            SERVER_NAME是機器B的名字。

            Dump文件調試:如果在你的客戶的機器上出現問題,你可能不能使用遠程調試來解決問題。你可以要求你的用戶將Windbg附加到出現問題的進程上,然后在命令窗口中輸入:

            .dump /ma File Name

            創建一個Dump文件。在得到Dump文件后,使用如下的命令來打開它:

            windbg –z DUMP_FILE_NAME

            (2)本地進程調試:你可以在Windbg下直接運行一個程序:

            Windbg “path to executable” arguments     

                也可以將Windbg附加到一個正在運行的程序:

                Windbg –p “process id”  

            Windbg –pn “process name”

                注意有一種非侵入(Noninvasive)模式可以用來檢查一個進程的狀態并不進程的執行。當然在這種模式下無法控制被調試程序的執行。這種模式也可以用于查看一個已經在Debugger控制下運行的進程。具體命令如下:

                Windbg –pv –p “process id” 

            Windbg –pv –pn “process name” 

            (3)調試多個進程和線程

            如果你想控制一個進程以及它的子進程的執行,在Windbg的命令行上加上-o選項。Windbg中還有一個新的命令.childdbg 可以用來控制子進程的調試。如果你同時調試幾個進程,可以使用 | 命令來顯示并切換到不同的進程。

            在同一個進程中可能有多個線程。~命令可以用來顯示和切換線程。

            .hh keyword  如何得到幫助

            靜態命令:

            顯示調用堆棧:在連接到一個調試窗口后,首先要知道的就是程序當前的執行情況k* 命令顯示當前線程的堆棧。~*kb會顯示所有線程的調用堆棧。如果堆棧太長,Windbg只會顯示堆棧的一部分。.kframes可以用來設置缺省顯示框架數。

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

            顯示類和鏈表: dt可以顯示數據結構。比如dt PEB 會顯示操作系統進程結構。在后面跟上一個進程結構的地址會顯示該結構的詳細信息:dt PEB 7ffdf000。

            Dl命令可以顯示一些特定的鏈表結構。

            顯示當前線程的錯誤值:!gle會顯示當前線程的上一個錯誤值和狀態值。!error命令可以解碼HRESULT。

            搜索或修改內存:使用s 命令來搜索字節,字或雙字,QWORD或字符串。使用e命令來修改內存。

            計算表達式:?命令可以用來進行計算。關于表達式的格式請參照幫助文檔。使用n命令來切換輸入數字的進制。

            顯示當前線程,進程和模塊信息:!teb顯示當前線程的環境信息。最常見的用途是查看當前線程堆棧的起始地址,然后在堆棧中搜索值。!peb顯示當前進程的環境信息,比如執行文件的路徑等等。lm顯示進程中加載的模塊信息。

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

            顯示最相近的符號:ln Address。如果你有一個C++對象的指針,可以用來ln來查看該對象類型。

            查找符號:x命令可以用來查找全局變量的地址或過程的地址。x命令支持匹配符號。x kernel32!*顯示Kernel32.dll中的所有可見變量,數據結構和過程。

            查看lock:!locks顯示各線程的鎖資源使用情況。對調試死鎖很有用。

            查看handle:!handle顯示句柄信息。如果一段代碼導致句柄泄漏,你只需要在代碼執行前后使用!handle命令并比較兩次輸出的區別。有一個命令!htrace對調試與句柄有關的Bug非常有用。在開始調試前輸入:

            !htrace –enable

            然后在調試過程中使用!htrace handle_value 來顯示所有與該句柄有關的調用堆棧。

            顯示匯編代碼:u。

             

            程序執行控制命令:

            設置代碼斷點:bp/bu/bm 可以用來設置代碼斷點。你可以指定斷點被跳過的次數。假設一段代碼KERNEL32!SetLastError在運行很多次后會出錯,你可以設置如下斷點:

                bp KERNEL32!SetLastError 0x100.

            在出錯后使用bl 來顯示斷點信息(注意粗體顯示的值):

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

            重新啟動調試(.restart命令)并設置如下的斷點:

            bp Kernel32!SetLastError 0x100-0x4f

            Debugger會停在出錯前最后一次調用該過程的地方。

            你可以指定斷點被激活時Debugger應當執行的命令串。在該命令串中使用J命令可以用來設置條件斷點:

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

            上面的斷點只在MyVar的值大于32時被激活(g命令

            條件斷點的用途極為廣泛。你可以指定一個斷點只在特殊的情況下被激活,比如傳入的參數滿足一定的條件,調用者是某個特殊的過程,某個全局變量被設為特殊的值等等。

            設置內存斷點:ba可以用來設置內存斷點。調試過程中一個常見的問題是跟蹤某些數據的變化。如下的斷點:

            ba w4 0x40000000 "kb; g"

            可以打印出所有修改0x40000000的調用堆棧。

            控制程序執行:p, pa,t, ta等命令可以用來控制程序的執行。

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

                sxe clr

            可以控制Debugger在托管異常發生時中斷程序的執行。常用的Debugger事件有:

                av    訪問異常 

                eh    C++異常

                clr   托管異常

                ld    模塊加載

            -c 選項可以用來指定在事件發生時執行的調試命令。

             

             

             

             

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

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

            堆棧顯示指令 

            k [b|p|P|v]

            在內核調試的時候,k命令用來顯示內核棧的內容

            先說說內核棧用來干嘛的 看了些資料個人理解是這樣的

            比如我們的代碼運行時,肯定會有函數函數然后還會調用函數 但是系統如何記錄是哪個父函數調用了這個子函數,在子函數調用之前整個狀態又是怎樣的,其實系統是利用了堆棧記錄的 棧這個東西好阿 先進后出 最近調用的函數記錄在最頂層 函數執行完后就從棧內彈出之前記錄的參數,如果調用函數 一樣的把函數壓進棧內就好了 這樣一來 一旦子函數執行完,從棧內彈出的第一個函數肯定是該子函數的老爹 我們可以看上層堆棧的狀態等等 功能大家慢慢去體會吧我也沒用過 呵呵 不好說什么 下面說些細節的東西

            b

            顯示傳給函數的前三個參數

            p

            顯示傳給函數的全部參數

            P( 大寫)

            跟上面那個一樣 只不過是顯示形式不同而已

            V

            外加顯示一些額外的信息

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

            u [f]

            反匯編指令,嘿嘿 超級有用的指令喲雖然說內核很多東西很復雜 認識偶爾小小反下也是可以的

            u

            反匯編當前寄存器指向的代碼

            uf 函數名(比如nt!ZwCreateFile)

            反匯編指定的函數

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

            t [r]

            單步跟蹤

            r 打開指顯示寄存器的詳細信息,狀態的開關(下面指令一樣有效,在用1次就會關閉哦~)

            ta 地址

            讓程序執行到指定地址

            tb

            讓程序運行到分支語句時停止

            tc

            讓程序運行到下一個函數調用停止

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

            p [r]

            單步執行一跳指令

            r 打開指顯示寄存器的詳細信息,狀態的開關(下面指令一樣有效,在用1次就會關閉哦~)

            pa

            讓程序執行到指定地址

            pc

            讓程序執行到函數調用就停止

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

            wt

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

            這個感覺用處不是很大.不細細研究了

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

            Ps: 很多人不清楚到底p指令和t指令有什么區別 其實很簡單 p指令執行到函數時把這個當做一個指令來執行也就是說不會進入函數執行,但是t指令會進入到函數里面執行 就這么簡單~~呵呵

             

             

            遠程調試

            使用WinDbg進行遠程調試是很容易的,而且有很多種可行的方法。在下文中,’調試服務器’指的是運行在你所要調試的遠程機器上的調試器。’調試客戶端’指的是控制當前會話的調試器。

            ·      使用調試器:你需要CDB, NTSD或者WinDbg已經安裝在遠程機器上。WinDbg客戶端可以連接到CDB, NTSD或者WinDbg中的任何一個作為服務器,反之亦然。在客戶端和服務器直接可以選擇TCP或者命名管道作為通訊協議。

            o   在服務器端的啟動過程:

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

            §  從WinDbg內部: .server npipe:pipe=pipename(注,連接單個客戶端)

            你可以用多種協議開啟不同的服務會話。并且可用密碼來保護一個會話。

            o   從客戶端連接:

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

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

            ·     使用Remote.exe: Remote.exe使用命名管道作為通訊的方式。如果你使用的是一個命令行接口的程序,比如KD,CDB或者NTSD。你可以使用remote.exe來遠程調試。注意:使用@q(不是q)來退出客戶端,不用關掉服務端。

            o   要啟動一個服務端:

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

            o   從客戶端連接:

            §  Remote.exe /c <machinename> test1

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

            服務端會顯示那個客戶端從那個服務器連接以及執行過的命令。你可以使用‘qq’命令來退出服務端;或者使用File->Exit來退出客戶端。另外,如果要進行遠程調試,你必須屬于遠程機器的”Debugger User”組并且服務器必須允許遠程連接。
            即時調試

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

            通過JIT的設置,當一個應用程序在不是調試的狀態下拋出了未處理的異常之時,WinDbg就會被啟動。
            64位調試

            所有這些調試器均支持在AMD64和IA64上的64位調試環境。


            托管應用程序的調試

            WinDbg 6.3以后的版本支持在Widbey(VS2005和.net 2.0的內部開發代號) .net CLR托管調試。在文檔中針對托管調試有很好的討論。需要注意的是,對于托管程序來說,沒有剛才所說的PDB(譯注:托管代碼實際上也是有PDB的,但是這個PDB實際上記錄了C#代碼和IL代碼的對應關系以及相關的一些信息)的概念,因為所有的程序都是編譯成為ILASM。調試器通過CLR來查詢所需的附加信息。

            有幾點需要注意:

            你只能在托段函數的代碼被執行過至少一次之后才能設置斷點。只有這樣它才能被編譯成匯編代碼。記住以下的幾點:

            ·         關于函數的地址的復雜化以及對應的斷點設置:

            o   CLR有可能丟棄已經編譯好的代碼,所以函數的入口地址有可能改變。

            o   同樣的代碼有可能被多次編譯,如果多個應用程序域沒有共享這段代碼的話。如果你設置了一個斷點,它就會被設置在當前線程(譯注:CLR的邏輯線程)所在的應用程序域內。

            o   泛型的特殊實例可能導致同一個函數有不同的地址。.

            ·         數據存儲布局的復雜化以及對應的數據檢查:
            CLR可能會在運行的時候任意改變數據的存儲布局,所以一個結構體成員的偏移量可能會被改變掉. (譯注:實際上是在一個類型被加載的時候決定的數據布局,之后是不會改變的。)
            一個類型的信息是在第一次使用的時候被加載,所以你可能不能夠查看一個數據成員如果它還沒有被使用過.

            ·         調試器命令的復雜化

            o   當跟蹤托管代碼的時候,你會需要穿越大段的CLR自己的代碼比如JIT編譯器的代碼,原因可能是你第一次進入一個函數,或者是你在托管和非托管代碼之間進行切換。
            調試Windows服務

            使用WinDbg,你可以像調試其它應用程序那樣調試Windows服務程序。即可以通過附加進程的方法啟動Windows服務,也可以把WinDbg當作一個即時調試器,并且在代碼中調用DbgBreakPoint 或者 DebugBreak,或者在x86機器上加入一條int 3匯編指令。
            調試異常

            一個調試器會得到兩次的異常通知-第一次在應用程序有機會處理異常之前(‘first chance exception’);如果應用程序沒有處理這個異常,這時候調試器就會有機會來處理異常(‘second-chance exception’)。如果調試器沒有處理二次機會的異常,應用程序就會退出。

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

            你也可以使用 .exr, .cxr以及 .ecxr命令來顯示異常和上下文記錄。同時需要注意的是,你也可以改變first-chance的處理選項。對應的命令就是: sxe, sxd, sxn和sxi。


            虛擬機調試驅動

             

            1)
            將 WinDbg 發送一個快捷方式,并修改在快捷方式上右鍵=>"屬性"
            將"目標"中的 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)
            打開虛擬機中的 c:\boot.ini 文件(之前去掉"只讀"屬性),復制一行
            multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise" /fastdetect
            即添加了一個啟動選項,并修改為:
            multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise[Debug]" /fastdetect /debug /debugport=com1 /baudrate=115200
            即添加了調試選項,調試端口以及串口的速率.
            保存.

            3)
            關閉虛擬機里的目標windows系統(必須,否則在"Settings..."里的"Add..."將為灰色,不可選狀態),
            選擇目標windows系統的"Settings..."選項,在"Hardware"選項中,點擊下面的"Add..."按鈕.
            選擇"Serial Port"點擊"Next",再選擇"Output to named pipe","Next",
            這一向導中,前兩項不修改,最后一項修改為"The other end is an application.",
            如果這里存在"高級"選項,則在其中選擇"Yield CPU on poll"[注:有些虛擬機在這里并沒有"高級"選項,則在"Finish"后,選擇"Serial Port",再勾選右下角的"Yield CPU on poll"],
            "Finish".

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

            4)
            打開虛擬機中的目標Windows系統,選擇"Windows Server 2003, Enterprise[Debug]"后,立即打開之前創建的WinDbg快捷方式(若先打開WinDbg,則會報找不到'com:port=....'等信息).

            5)
            連接上虛擬機中的目標windows系統后,立即發送一個WinDbg中的"Debug"=>"Break"來中斷它.

            6)
            設置"Symbol"路徑,"Source"路徑和"ImagePath"路徑(若有多個,則用";"號隔開).
            如:
            "File"=>"Symbol" 里設置:"G:\虛擬機共享文件\Win2003Symbols;G:\虛擬機共享文件\GiveIO",勾選"Reload","OK". (第一個是Windows的符號[這里要注意,符號應該是虛擬機內Windows系統對應的],第二個是要調試的驅動的符號)

            "File"=>"Source" 里設置:"G:\虛擬機共享文件\GiveIO", "OK". (要調試的驅動的源碼我放在了此目錄中)

            "File"=>"Image File Path" 里設置: "G:\虛擬機共享文件\GiveIO", "Reload", "OK". (我把要調試的驅動程序放在了此目錄中)

            "File"=>"Open Source File", 打開驅動源文件.

            7)
            在要調試的驅動程序上下斷點(這里我調試的驅動模塊名為"GiveIO",后面是在"DriverEntry"入口點下斷點[bu設置的是延遲斷點]):
            bu GiveIO!DriverEntry
            按"F5"(或在命令行中用"g"命令),即可讓虛擬機中的目標Windows順利啟動.

            8)
            在虛擬機中運行要調試的驅動程序,即可運行到斷點處.

            9)
            一些說明:
            在已運行的Windows目標系統中調試指定驅動:
            首先應使WinDbg產生一個斷點,使用WinDbg中的"Debug"=>"Break",此時目標Windows系統就會中斷.
            此時使用:
            bu GiveIO!DriverEntry
            命令,在指定驅動的模塊名的入口點下斷點(GiveIO為此處的驅動模塊名).
            運行驅動后,即會在驅動的DriverEntry處斷下來,此時就可以進行跟蹤調試了.

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

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

            #pragma 預處理指令詳解

            #pragma 預處理指令詳解

            在所有的預處理指令中,#pragma 指令的作用是設定編譯器的狀態或者是指示編譯器完成一些特定的動作。

            依據定義,編譯指示是機器或操作系統專有的,且對于每個編譯器都是不同的。 
                其格式一般為: #pragma  para 
                其中para為參數,下面來看一些常用的參數。 
             

            (1)message 信息參數

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

            (2)code_seg代碼段參數

                #pragma  code_seg( ["section-name" [, "section-class"] ] ) 
                它能夠設置程序中函數代碼存放的代碼段,當我們開發驅動程序的時候就會使用到它。 
             

            (3)#pragma once 

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

                  
            (4)#pragma  hdrstop

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

                  
            (5)#pragma  resource  "*.dfm"

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

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

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


            (7)#pragma  comment(...) 

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

            例如,對循環優化功能: 
            #pragma  loop_opt(on)     //  激活 
            #pragma  loop_opt(off)    //  終止 

            有時,程序中會有些函數會使編譯器發出你熟知而想忽略的警告,
            如“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 
            函數會產生一條有唯一特征碼100的警告信息,如此可暫時終止該警告。 

            每個編譯器對#pragma的實現不同,在一個編譯器中有效在別的編譯器中幾乎無效。可從編譯器的文檔中查看。


            補充 —— #pragma pack 與 內存對齊問題


                許多實際的計算機系統對基本類型數據在內存中存放的位置有限制,它們會要求這些數據的首地址的值是某個數k
            (通常它為4或8)的倍數,這就是所謂的內存對齊,而這個k則被稱為該數據類型的對齊模數(alignment modulus)。

                Win32平臺下的微軟C編譯器(cl.exe for 80x86)在默認情況下采用如下的對齊規則:
                任何基本數據類型T的對齊模數就是T的大小,即sizeof(T)。比如對于double類型(8字節),
            就要求該類型數據的地址總是8的倍數,而char類型數據(1字節)則可以從任何一個地址開始。

                Linux下的GCC奉行的是另外一套規則(在資料中查得,并未驗證,如錯誤請指正):
                任何2字節大小(包括單字節嗎?)的數據類型(比如short)的對齊模數是2,而其它所有超過2字節的數據類型
            (比如long,double)都以4為對齊模數。

                ANSI C規定一種結構類型的大小是它所有字段的大小以及字段之間或字段尾部的填充區大小之和。
            填充區就是為了使結構體字段滿足內存對齊要求而額外分配給結構體的空間。那么結構體本身有什么對齊要求嗎?
            有的,ANSI C標準規定結構體類型的對齊要求不能比它所有字段中要求最嚴格的那個寬松,可以更嚴格。


            如何使用c/c++中的對齊選項

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

                實際上,1字節邊界對齊也就表示了結構成員之間沒有空洞。
                /Zpn選項是應用于整個工程的,影響所有的參與編譯的結構。
                要使用這個選項,可以在vc6中打開工程屬性頁,c/c++頁,選擇Code Generation分類,在Struct member alignment可以選擇。

                要專門針對某些結構定義使用對齊選項,可以使用#pragma pack編譯指令:


            (1) #pragma  pack( [ n ] )

                該指令指定結構和聯合成員的緊湊對齊。而一個完整的轉換單元的結構和聯合的緊湊對齊由/Zp 選項設置。
            緊湊對齊用pack編譯指示在數據說明層設置。該編譯指示在其出現后的第一個結構或聯合說明處生效。
            該編譯指示對定義無效。
                當你使用#pragma  pack ( n ) 時, 這里n 為1、2、4、8 或16。
                第一個結構成員之后的每個結構成員都被存儲在更小的成員類型或n 字節界限內。
            如果你使用無參量的#pragma  pack, 結構成員被緊湊為以/Zp 指定的值。該缺省/Zp 緊湊值為/Zp8 。


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

                若不同的組件使用pack編譯指示指定不同的緊湊對齊, 這個語法允許你把程序組件組合為一個單獨的轉換單元。
            帶push參量的pack編譯指示的每次出現將當前的緊湊對齊存儲到一個內部編譯器堆棧中。
                編譯指示的參量表從左到右讀取。如果你使用push, 則當前緊湊值被存儲起來;
            如果你給出一個n 的值, 該值將成為新的緊湊值。若你指定一個標識符, 即你選定一個名稱,
            則該標識符將和這個新的的緊湊值聯系起來。

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

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


            (3) 棧內存對齊

                在vc6中棧的對齊方式不受結構成員對齊選項的影響。它總是保持對齊,而且對齊在4字節邊界上。

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

            c++內存布局

            0712月,我寫了一篇《C++虛函數表解析》的文章,引起了大家的興趣。有很多朋友對我的文章留了言,有鼓勵我的,有批評我的,還有很多問問題的。我在這里一并對大家的留言表示感謝。這也是我為什么再寫一篇續言的原因。因為,在上一篇文章中,我用了的示例都是非常簡單的,主要是為了說明一些機理上的問題,也是為了圖一些表達上方便和簡單。不想,這篇文章成為了打開C++對象模型內存布局的一個引子,引發了大家對C++對象的更深層次的討論。當然,我之前的文章還有很多方面沒有涉及,從我個人感覺下來,在談論虛函數表里,至少有以下這些內容沒有涉及:

            1)有成員變量的情況。

            2)有重復繼承的情況。

            3)有虛擬繼承的情況。

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

             

            這些都是我本篇文章需要向大家說明的東西。所以,這篇文章將會是《C++虛函數表解析》的一個續篇,也是一篇高級進階的文章。我希望大家在讀這篇文章之前對C++有一定的基礎和了解,并能先讀我的上一篇文章。因為這篇文章的深度可能會比較深,而且會比較雜亂,我希望你在讀本篇文章時不會有大腦思維紊亂導致大腦死機的情況。;-)

             

            對象的影響因素

             

            簡而言之,我們一個類可能會有如下的影響因素:

             

            1)成員變量

            2)虛函數(產生虛函數表)

            3)單一繼承(只繼承于一個類)

            4)多重繼承(繼承多個類)

            5)重復繼承(繼承的多個父類中其父類有相同的超類)

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

            上述的東西通常是C++這門語言在語義方面對對象內部的影響因素,當然,還會有編譯器的影響(比如優化),還有字節對齊的影響。在這里我們都不討論,我們只討論C++語言上的影響。

             

            本篇文章著重討論下述幾個情況下的C++對象的內存布局情況。

             

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

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

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

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

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

             

            我們的目標就是,讓事情越來越復雜。

             

            知識復習

             

            我們簡單地復習一下,我們可以通過對象的地址來取得虛函數表的地址,如:

             

                      typedef void(*Fun)(void);

             

                        Base b;

             

                        Fun pFun = NULL;

             

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

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

             

                        // Invoke the first virtual function 

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

                        pFun();

             

            我們同樣可以用這種方式來取得整個對象實例的內存布局。因為這些東西在內存中都是連續分布的,我們只需要使用適當的地址偏移量,我們就可以獲得整個內存對象的布局。

             

            本篇文章中的例程或內存布局主要使用如下編譯器和系統:

            1)Windows XP VC++ 2003

            2)Cygwin G++ 3.4.4

             

            單一的一般繼承

             

            下面,我們假設有如下所示的一個繼承關系:

             

             

            請注意,在這個繼承關系中,父類,子類,孫子類都有自己的一個成員變量。而了類覆蓋了父類的f()方法,孫子類覆蓋了子類的g_child()及其超類的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; }

            };

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

             

                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;

             

            其運行結果如下所示:(在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

             

            使用圖片表示如下:

             

             

             

            可見以下幾個方面:

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

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

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

             

             

             

             

             

            多重繼承

             

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

             

             

             

            我們的類繼承的源代碼如下所示:父類的成員初始為102030,子類的為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; }

            };

             

            我們通過下面的程序來查看子類實例的內存布局:下面程序中,注意我使用了一個s變量,其中用到了sizof(Base)來找下一個類的偏移量。(因為我聲明的是int成員,所以是4個字節,所以沒有對齊問題。關于內存的對齊問題,大家可以自行試驗,我在這里就不多說了)

             

                         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;

             

            其運行結果如下所示:(在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


            使用圖片表示是下面這個樣子:

             

             

            我們可以看到:

            1) 每個父類都有自己的虛表。

            2) 子類的成員函數被放到了第一個父類的表中。

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

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

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

            c++虛函數表解析

            前言

             

            C++中的虛函數的作用主要是實現了多態的機制。關于多態,簡而言之就是用父類型別的指針指向其子類的實例,然后通過父類的指針調用實際子類的成員函數。這種技術可以讓父類的指針有“多種形態”,這是一種泛型技術。所謂泛型技術,說白了就是試圖使用不變的代碼來實現可變的算法。比如:模板技術,RTTI技術,虛函數技術,要么是試圖做到在編譯時決議,要么試圖做到運行時決議。

             

             

            關于虛函數的使用方法,我在這里不做過多的闡述。大家可以看看相關的C++的書籍。在這篇文章中,我只想從虛函數的實現機制上面為大家 一個清晰的剖析。

             

            當然,相同的文章在網上也出現過一些了,但我總感覺這些文章不是很容易閱讀,大段大段的代碼,沒有圖片,沒有詳細的說明,沒有比較,沒有舉一反三。不利于學習和閱讀,所以這是我想寫下這篇文章的原因。也希望大家多給我提意見。

             

            言歸正傳,讓我們一起進入虛函數的世界。

             

             

            虛函數表

             

            C++ 了解的人都應該知道虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。簡稱為V-Table。在這個表中,主是要一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其容真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了這個實例的內存中,所以,當我們用父類的指針來操作一個子類的時候,這張虛函數表就顯得由為重要了,它就像一個地圖一樣,指明了實際所應該調用的函數。

             

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

             

            聽我扯了那么多,我可以感覺出來你現在可能比以前更加暈頭轉向了。 沒關系,下面就是實際的例子,相信聰明的你一看就明白了。

             

            假設我們有這樣的一個類:

             

            class Base {

                 public:

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

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

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

             

            };

             

            按照上面的說法,我們可以通過Base的實例來得到虛函數表。 下面是實際例程:

             

                      typedef void(*Fun)(void);

             

                        Base b;

             

                        Fun pFun = NULL;

             

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

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

             

                        // Invoke the first virtual function 

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

                        pFun();

             

            實際運行經果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

             

            虛函數表地址:0012FED4

            虛函數表第一個函數地址:0044F148

            Base::f

             

             

            通過這個示例,我們可以看到,我們可以通過強行把&b轉成int *,取得虛函數表的地址,然后,再次取址就可以得到第一個虛函數的地址了,也就是Base::f(),這在上面的程序中得到了驗證(把int* 強制轉成了函數指針)。通過這個示例,我們就可以知道如果要調用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()

             

            這個時候你應該懂了吧。什么?還是有點暈。也是,這樣的代碼看著太亂了。沒問題,讓我畫個圖解釋一下。如下所示:

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

             

             

            下面,我將分別說明“無覆蓋”和“有覆蓋”時的虛函數表的樣子。沒有覆蓋父類的虛函數是毫無意義的。我之所以要講述沒有覆蓋的情況,主要目的是為了給一個對比。在比較之下,我們可以更加清楚地知道其內部的具體實現。

             

            一般繼承(無虛函數覆蓋)

             

            下面,再讓我們來看看繼承時的虛函數表是什么樣的。假設有如下所示的一個繼承關系:

             

             

            請注意,在這個繼承關系中,子類沒有重載任何父類的函數。那么,在派生類的實例中,其虛函數表如下所示:

             

            對于實例:Derive d; 的虛函數表如下:

             

            我們可以看到下面幾點:

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

            2)父類的虛函數在子類的虛函數前面。

             

            我相信聰明的你一定可以參考前面的那個程序,來編寫一段程序來驗證。

             

             

             

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

             

            覆蓋父類的虛函數是很顯然的事情,不然,虛函數就變得毫無意義。下面,我們來看一下,如果子類中有虛函數重載了父類的虛函數,會是一個什么樣子?假設,我們有下面這樣的一個繼承關系。

             

             

             

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

             

             

            我們從表中可以看到下面幾點,

            1)覆蓋的f()函數被放到了虛表中原來父類虛函數的位置。

            2)沒有被覆蓋的函數依舊。

             

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

             

                        Base *b = new Derive();

             

                        b->f();

             

            b所指的內存中的虛函數表的f()的位置已經被Derive::f()函數地址所取代,于是在實際調用發生時,是Derive::f()被調用了。這就實現了多態。

             

             

             

            多重繼承(無虛函數覆蓋)

             

            下面,再讓我們來看看多重繼承中的情況,假設有下面這樣一個類的繼承關系。注意:子類并沒有覆蓋父類的函數。

             

             

             

            對于子類實例中的虛函數表,是下面這個樣子:

             

            我們可以看到:

            1) 每個父類都有自己的虛表。

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

             

            這樣做就是為了解決不同的父類類型的指針指向同一個子類實例,而能夠調用到實際的函數。

             

             

             

             

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

             

            下面我們再來看看,如果發生虛函數覆蓋的情況。

             

            下圖中,我們在子類中覆蓋了父類的f()函數。

             

             

             

            下面是對于子類實例中的虛函數表的圖:

             

             

            我們可以看見,三個父類虛函數表中的f()的位置被替換成了子類的函數指針。這樣,我們就可以任一靜態類型的父類來指向子類,并調用子類的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()

             

             

            安全性

             

            每次寫C++的文章,總免不了要批判一下C++。這篇文章也不例外。通過上面的講述,相信我們對虛函數表有一個比較細致的了解了。水可載舟,亦可覆舟。下面,讓我們來看看我們可以用虛函數表來干點什么壞事吧。

             

            一、通過父類型的指針訪問子類自己的虛函數

            我們知道,子類沒有重載父類的虛函數是一件毫無意義的事情。因為多態也是要基于函數重載的。雖然在上面的圖中我們可以看到Base1的虛表中有Derive的虛函數,但我們根本不可能使用下面的語句來調用子類的自有虛函數:

             

                      Base1 *b1 = new Derive();

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

             

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

             

             

            二、訪問non-public的虛函數

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

             

            如:

             

            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();

            }

             

             

            結束語

            C++這門語言是一門Magic的語言,對于程序員來說,我們似乎永遠摸不清楚這門語言背著我們在干了什么。需要熟悉這門語言,我們就必需要了解C++里面的那些東西,需要去了解C++中那些危險的東西。不然,這是一種搬起石頭砸自己腳的編程語言。

             

            在文章束之前還是介紹一下自己吧。我從事軟件研發有十個年頭了,目前是軟件開發技術主管,技術方面,主攻Unix/C/C++,比較喜歡網絡上的技術,比如分布式計算,網格計算,P2PAjax等一切和互聯網相關的東西。管理方面比較擅長于團隊建設,技術趨勢分析,項目管理。歡迎大家和我交流,我的MSNEmail是:haoel@hotmail.com 

             

            附錄一:VC中查看虛函數表

             

            我們可以在VCIDE環境中的Debug狀態下展開類的實例就可以看到虛函數表了(并不是很完整的)

            附錄 二:例程

            下面是一個關于多重繼承的虛函數表訪問的例程:

             

            #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 深邃者 閱讀(112) | 評論 (0)編輯 收藏

            如何用 Win32 APIs 枚舉應用程序窗口和進程

            如何用 Win32 APIs 枚舉應用程序窗口和進程

            編譯:NorthTibet

            下載源代碼

            摘要

              我們在編寫程序時,常常遇到的一件事情就是要準確列出系統中所有正在運行的程序或者進程。Windows 任務管理器就是這樣的一個程序。它既能列出運行的桌面應用程序,又能列出系統中所有運行的進程。那么,我們在程序中如何實現這樣的任務呢?本文下面將詳細討論這個問題。


            枚舉頂層(top-level)窗口

              枚舉桌面頂層窗口相對于枚舉進程來說可能要容易一些。枚舉桌面頂層窗口的方法是用 EnumWindows() 函數。不要用 GetWindow()來創建窗口列表,因為窗口之間復雜的父子及同胞關系(Z-Order)容易造成混亂而使得枚舉結果不準確。
              EnumWindows()有兩個參數,一個是指向回調函數的指針,一個是用戶定義的 LPARAM 值, 針對每個桌面窗口(或者頂層窗口)它調用回調函數一次。然后回調函數用該窗口句柄做一些處理,比如將它添加到列表中。這個方法保證枚舉結果不會被窗口復雜的層次關系搞亂,因此,一旦有了窗口句柄,我們就可以通過 GetWindowText() 得到窗口標題。


            枚舉進程

              建立系統進程列表比枚舉窗口稍微復雜一些。這主要是因為所用的 API 函數對于不同的 Win32 操作系統有依賴性。在 Windows 9x、Windows Me、Windows 2000 Professional 以及 Windows XP 中,我們可以用 ToolHelp32 庫中的 APIs 函數。但是在 Windows NT 里,我們必須用 PSAPI 庫中的 APIs 函數, PSAPI 庫是 SDK 的一部分。本文我們將討論上述所有平臺中的實現。附帶的例子程序將對上述庫中的 APIs 進行包裝,以便包裝后的函數能支持所有 Win32 操作系統。


            使用 ToolHelp32 庫枚舉進程

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


            使用 PSAPI 庫枚舉進程

              在 Windows NT 中,創建進程列表使用 PSAPI 函數,這些函數在 PSAPI.DLL 中。這個文件是隨 Platform SDK 一起分發的,最新版本的 Platform SDK 可以從這里下載

            使用這個庫所需的 PSAPI.h 和 PSAPI.lib 文件也在該 Platform SDK 中。
              為了使用 PSAPI 庫中的函數,需將 PSAPI.lib 添加到代碼項目中,同時在所有調用 PSAPI API 的模塊中包含 PSAPI.h 文件。記住一定要隨可執行文件一起分發 PSAPI.DLL,因為它不隨 Windows NT 一起分發。你可以點擊這里單獨下載 PSAPI.DLL 的可分發版本(不用完全下載 Platform SDK)。
              與 ToolHelp32 一樣,PSAPI 庫也包含各種各樣有用的函數。由于篇幅所限,本文只討論與枚舉進程有關函數:EnumProcesses()、 EnumProcessModules()、GetModuleFileNameEx()和 GetModuleBaseName()。
              創建進程列表的第一步是調用 EnumProcesses()。該函數的聲明如下:

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


            16位進程的處理方法

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

              此處 dwProcessId 是 NTVDM 中擬枚舉的16位任務進程標示符。參數 fp 是回調枚舉函數的指針。參數 lparam 是用戶定義的值,它被傳遞到枚舉函數。枚舉函數應該被定義成如下這樣:

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


            關于代碼

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

            使用該函數時,要象下面這樣聲明回調函數:

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

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

            如何“干凈地”終止 Win32 中的應用程序

            如何“干凈地”終止 Win32 中的應用程序

            編譯:Northtibet

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

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

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

              在 Win32 下,操作系統可保證在進程關閉時清除進程所擁有的資源。但是,這并不意味著進程本身將有機會對磁盤執行任何最后的信息刷新或通過遠程連接執行任何最后的通信,也不意味著進程的 DLL 將有機會執行其 PROCESS_DETACH 代碼。這就是通常最好避免在 Windows 95 和 Windows NT 下終止應用程序的原因。

            如果你必須關閉進程,請按照下列步驟操作:

            1. 向你打算關閉的進程所擁有的所有頂級窗口發送一條 WM_CLOSE 消息。許多 Windows 應用程序會通過關閉它自身來響應此消息。

              注意:控制臺應用程序對 WM_CLOSE 的響應取決于它是否安裝了控制處理程序。

              使用 EnumWindows() 找到目標窗口的句柄。在回調函數中,檢查該窗口的進程 ID 是否與要關閉的進程相匹配。你可以通過調用 GetWindowThreadProcessId() 來執行此操作。確定匹配項后,使用 PostMessage() 或 SendMessageTimeout() 向該窗口發送 WM_CLOSE 消息。
            2. 使用 WaitForSingleObject() 等待進程的句柄。確保你使用超時值等待,因為在很多情況下 WM_CLOSE 不會關閉應用程序。記住,應使超時值足夠長(通過 WaitForSingleObject() 或 SendMessageTimeout()),以便用戶可以響應為了 處理 WM_CLOSE 消息而創建的任何對話框。
            3. 如果返回值為 WAIT_OBJECT_0,則應用程序已干凈地將其自身關閉。如果返回值為 WAIT_TIMEOUT,則必須使用 TerminateProcess() 關閉應用程序。

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

            16 位問題(在 Windows NT 下)

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

              此方法允許你關閉 Windows NT 下 VDM 中的單個 16 位應用程序。不過,16 位 Windows 以及 VDM 中運行的 WOWExec 都不能有效地清除已終止任務的資源。如果你要尋找最有可能干凈地終止 Windows NT 下的 16 位應用程序的方法,應考慮終止整個 VDM 進程。注意:如果你要啟動以后可能會終止的 16 位應用程序,請將 CREATE_SEPARATE_WOW_VDM 與 CreateProcess() 結合使用。

            示例代碼

              下面的示例代碼使用以下兩個函數實現上述用于 16 位和 32 位應用程序的方法:TerminateApp() 和 Terminate16App()。TerminateApp() 采用一個 32 位進程 ID 和一個超時值(以毫秒為單位)。Terminate16App()。這兩個函數都使用 DLL 函數的顯式鏈接,以便它們的二進制文件與 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 );



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

            // 實現代碼 TermApp.cpp

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

            #include "TermApp.h"

            #include <vdmdbg.h>

            typedef struct

            {

            DWORD dwID ;

            DWORD dwThread ;

            } TERMINFO ;

            // 聲明回調枚舉函數.

            BOOL CALLBACK TerminateAppEnum( HWND hwnd, LPARAM lParam ) ;

            BOOL CALLBACK Terminate16AppEnum( HWND hwnd, LPARAM lParam ) ;

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

            DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout )

            功能:

            關閉 32-位進程(或 Windows 95 下的 16-位進程)

            參數:

            dwPID

            要關閉之進程的進程 ID.

            dwTimeout

            進程關閉前等待的毫秒時間.

            返回值:

            TA_FAILED —— 如果關閉失敗.

            TA_SUCCESS_CLEAN —— 如果使用 WM_CLOSE 關閉了進程.

            TA_SUCCESS_KILL —— 如果使用 TerminateProcess() 關閉了進程.

            返回值的定義參見頭文件.

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

            DWORD WINAPI TerminateApp( DWORD dwPID, DWORD dwTimeout )

            {

            HANDLE hProc ;

            DWORD dwRet ;

            // 如果無法用 PROCESS_TERMINATE 權限打開進程,那么立即放棄。

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

            if(hProc == NULL)

            {
            return TA_FAILED ;
            }


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

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


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

            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 )

            功能:

            關閉 Win16 應用程序.

            參數:

            dwPID

            16-位程序運行其中的 NTVDM 進程 ID.

            dwThread

            16-位程序中執行線程的線程 ID.

            w16Task

            應用程序的 16-位任務句柄.

            dwTimeout

            任務關閉前等待的毫秒時間.



            返回值:

            如果成功, 返回 TA_SUCCESS_16

            如果不成功, 返回 TA_FAILED.

            返回值的定義參見該函數的頭文件.

            注意:

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

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

            DWORD WINAPI Terminate16App( DWORD dwPID, DWORD dwThread,

            WORD w16Task, DWORD dwTimeout )

            {

            HINSTANCE hInstLib ;

            TERMINFO info ;

            // 你必須通過外部鏈接調用函數,以便代碼在所有 Win32 平臺上都兼容。

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

            hInstLib = LoadLibraryA( "VDMDBG.DLL" ) ;

            if( hInstLib == NULL )
            return TA_FAILED ;

            // 獲得函數過程地址.

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

            GetProcAddress( hInstLib, "VDMTerminateTaskWOW" ) ;

            if( lpfVDMTerminateTaskWOW == NULL )

            {

            FreeLibrary( hInstLib ) ;

            return TA_FAILED ;

            }

            // 向所有匹配進程 ID 和線程的窗口發送 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 深邃者 閱讀(223) | 評論 (0)編輯 收藏

            SQL

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

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

            LIST控件

            CListCtrl使用技巧

            以下未經說明,listctrl默認view 風格為report


            1. CListCtrl 風格

                  LVS_ICON: 為每個item顯示大圖標
                  LVS_SMALLICON: 為每個item顯示小圖標
                  LVS_LIST: 顯示一列帶有小圖標的item
                  LVS_REPORT: 顯示item詳細資料

                  直觀的理解:windows資源管理器,“查看”標簽下的“大圖標,小圖標,列表,詳細資料”



            2. 設置listctrl 風格及擴展風格

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

             


            3. 插入數據

                  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”);//設置數據

             


            4. 一直選中item

                選中style中的Show selection always,或者在上面第2點中設置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的狀態

                  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為選中狀態"), i);
                            AfxMessageBox(str);
                       }
                  }



            7. 得到listctrl中所有選中行的序號


                  方法一:
                  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);

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



            9. 得到listctrl的所有列的header字符串內容

                  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中一項可見,即滾動滾動條

                m_list.EnsureVisible(i, FALSE);


            11. 得到listctrl列數

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


            12. 刪除所有列

                  方法一:
                     while ( m_list.DeleteColumn (0))
                   因為你刪除了第一列后,后面的列會依次向上移動。

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



            13. 得到單擊的listctrl的行列號

                  添加listctrl控件的NM_CLICK消息相應函數
                  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. 判斷是否點擊在listctrl的checkbox上

                  添加listctrl控件的NM_CLICK消息相應函數
                  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);
                       //判斷是否點在checkbox上
                       if(nFlag == LVHT_ONITEMSTATEICON)
                       {
                            AfxMessageBox("點在listctrl的checkbox上");
                       }
                       *pResult = 0;
                  }



            15. 右鍵點擊listctrl的item彈出菜單

                  添加listctrl控件的NM_RCLICK消息相應函數
                  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切換焦點時(包括用鍵盤和鼠標切換item時),狀態的一些變化順序

                  添加listctrl控件的LVN_ITEMCHANGED消息相應函數
                  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. 得到另一個進程里的listctrl控件的item內容

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



            18. 選中listview中的item

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



            19. 如何在CListView中使用CListCtrl的派生類

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



            20. listctrl的subitem添加圖標

                  m_list.SetExtendedStyle(LVS_EX_SUBITEMIMAGES);
                  m_list.SetItem(..); //具體參數請參考msdn

             


            21. 在CListCtrl顯示文件,并根據文件類型來顯示圖標

                  網上找到的代碼,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內容進行大數據量更新時,避免閃爍

                  m_list.SetRedraw(FALSE);
                  //更新內容
                  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中選中某個item時動態改變其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后,再InsertColumn()后導致整列數據移動的問題

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



            26. 關于listctrl第一列始終居左的問題

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

             


            27. 鎖定column header的拖動

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



            28. 如何隱藏clistctrl的列

                把需隱藏的列的寬度設為0,然后檢測當該列為隱藏列時,用上面第27點的鎖定column 的拖動來實現


            29. listctrl進行大數據量操作時,使用virtual list   

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



            30. 關于item只能顯示259個字符的問題

            解決辦法:需要在item上放一個edit。



            31. 響應在listctrl的column header上的鼠標右鍵單擊

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



            32. 類似于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只響應兩次的問題

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


            34. 以下為一些為實現各種自定義功能的listctrl派生類

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

                               在CListCtrl和CTreeCtrl間拖放
                               http://support.microsoft.com/kb/148738/en-us
             
                      (2)    多功能listctrl
                               支持subitem可編輯,圖標,radiobutton,checkbox,字符串改變顏色的類
                               http://www.codeproject.com/listctrl/quicklist.asp
             
                               支持排序,subitem可編輯,subitem圖標,subitem改變顏色的類
                               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中顯示進度條   
                               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)    動態改變subitem的顏色和背景色
                                http://www.codeproject.com/listctrl/highlightlistctrl.asp
                                http://www.codeguru.com/Cpp/controls/listbox/colorlistboxes/article.php/c4757/
             
                      (7)    類vb屬性對話框
                                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)  自適應寬度的listctrl
                                http://www.codeproject.com/useritems/AutosizeListCtrl.asp

                      (17)  改變ListCtrl高亮時的顏色(默認為藍色)
                               處理 NM_CUSTOMDRAW
                       http://www.codeproject.com/listctrl/lvcustomdraw.asp

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

            僅列出標題
            共5頁: 1 2 3 4 5 
            2021最新久久久视精品爱| 久久国产免费直播| 久久超乳爆乳中文字幕| 97精品久久天干天天天按摩| 国产精品伊人久久伊人电影| 中文字幕精品无码久久久久久3D日动漫 | Xx性欧美肥妇精品久久久久久| 亚洲国产精品久久久久婷婷软件 | 无码人妻少妇久久中文字幕蜜桃| 久久久久99精品成人片试看| 久久av免费天堂小草播放| 狠狠色噜噜色狠狠狠综合久久| 精品午夜久久福利大片| 伊人久久大香线蕉综合5g| 国产精品久久久久久久| 99精品国产免费久久久久久下载| 色综合久久天天综合| 久久精品久久久久观看99水蜜桃 | 精品久久久久久无码人妻热| 久久久久免费精品国产| 久久精品国产精品亚洲人人| 久久777国产线看观看精品| 国产精品久久久久免费a∨| 久久国产高清一区二区三区| 久久99热精品| 久久亚洲国产中v天仙www| 国内精品久久久久影院日本| 午夜精品久久久久久毛片| 一本色道久久88—综合亚洲精品 | 久久91精品综合国产首页| 精品999久久久久久中文字幕| 欧美丰满熟妇BBB久久久| 99精品国产99久久久久久97 | 久久国产精品久久久| 潮喷大喷水系列无码久久精品| 无码国内精品久久人妻蜜桃| 婷婷五月深深久久精品| 久久精品国产亚洲av麻豆小说| 无码国内精品久久人妻蜜桃| 看久久久久久a级毛片| av国内精品久久久久影院|