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

            可視化graph的工具: GraphViz

            這套工具可以把有向圖(digraph)和無向圖(graph)在平面內展現出來,方便觀察。GraphViz使用DOT(一種圖形描述語言)描述圖,然后有解釋工具dot生成圖像文件。dot支持多種圖像文件,包括非矢量的gif、矢量的ps、svg等約20多種格式。DOT語言也非常簡單易學。舉個例子:

            digraph G {
            ?size = "4,4"
            ?main [shape=box];?/* this is a comment */
            ?main -> parse [weight=8];
            ?parse -> execute;
            ?main -> init [style=dotted];
            ?main -> cleanup;
            ?execute -> { make_string; printf}
            ?init -> make_string;
            ?edge [color=red]; // so is this
            ?main -> printf [style=bold, label="100 times"];
            ?node [shape=box, style=filled, color=".7.3 1.0"];
            ?execute -> compare;
            }

            存為test.dot,然后執行
            ? > dot test.dot -Tpng -o test.png
            就生成了graph的圖像文件。很方便哦。
            ?
            實際上GraphViz還可以畫出很多漂亮的“圖”。比如ER圖,hash table示意圖。更多請參考:http://www.graphviz.org/Gallery.php。以后可以考慮用GraphViz畫一些示意圖,既方便有專業,還很容易轉成ps(eps)。繼續研究研究。
            ?
            posted @ 2007-03-06 21:35 dyh 閱讀(1976) | 評論 (0)編輯 收藏
             
            [轉載]Visual C++ MFC 中常用宏的含義

            文章來源: http://www.vccode.com/
            文章作者:不詳

            AND_CATCHAND_CATCH
            AND_CATCH(exception_class,exception _object_point_name)
            說明:

            定義一個代碼塊,它用于獲取廢除當前TRY塊中的附加異常類型。使用CATCH宏以獲得一個異常類型,然后使用AND_CATCH宏獲得隨后的異常處理代碼可以訪問異常對象(若合適的話)已得到關于異常的特別原因的更多消息。在AND_CATCH塊中調用THROW_LAST宏以便把處理過程移到下個外部異??蚣堋ND_CATCH可標記CATCH或AND_CATCH塊的末尾。

            注釋:
            AND_CATCH塊被定義成為一個C++作用域(由花括號來描述)。若用戶在此作用域定義變量,那么記住他們只在此作用域中可以訪問。他也用于exception_object_pointer_name變量。
            ?
            ASSERT
            ASSERT(booleanExpression)
            說明:
            計算變量的值。如果結構的值為0,那么此宏便打印一個診斷消息并且成訊運行失敗。如果條件為非0,那么什么也不做。 診斷消息的形式為: assertion failed in file in line 其中name是元文件名,num是源文件中運行失敗的中斷號。 在Release版中,ASSERT不計算表達式的值也就不中斷程序。如果必須計算此表達式的值且不管環境如何那么用VERIFY代替ASSERT。
            注釋:
            ASSERT只能在Debug版中用

            ASSERT_VAILD
            ASSERT_VAILD(pObject)
            說明:
            用于檢測關于對象的內部狀態的有效性。ASSERT_VALID調用此對象的AssertValid成員函數(把它們作為自己的變量來傳遞)。在Release版中ASSERT_VALID什么也不做。在DEBUG版中,他檢查指針,以不同于NULL的方式進行檢查,并調用對象自己的AssertValid成員函數。如果這些檢測中有任何一個失敗的話,那么他會以與ASSERT相同的方法顯示一個警告的消息。
            注釋:
            此函數只在DEBUG版中有效。

            BEGIN_MESSAGE_MAP
            BEGIN_MESSAGE_MAP(the class,baseclass)
            說明:
            使用BEGIN_MESSAGE_MAP開始用戶消息映射的定義。在定義用戶類函數的工具(.cpp)文件中,以BEGIN_MESSAGE_MAP宏開始消息映射,然后為每個消息處理函數增加宏項,接著以END_MESSAGE_MAP宏完成消息映射。

            CATCH
            CATCH(exception_class,exception_object_pointer_name)
            說明:
            使用此用定義一個代碼塊,此代碼用來獲取當前TRY塊中都一個異常類型。異常處理代碼可以訪問異常對象,如何合適的話,就會得到關于異常的特殊原因的更多消息。調用THROW_LAST宏以把處理過程一下一個外部異常框架,如果exception-class是類CExceptioon,那么會獲取所有異常類型。用戶可以使用CObject::IsKindOf成員函數以確定那個特別異常被排除。一種獲取異常的最好方式是使用順序的AND_CATCH語句,每個帶一個不同的異常類型。此異常類型的指針由宏定義,用戶不必定義。
            注釋:
            此CATCH塊被定義作一個C++范圍(由花括號描述)。如用戶在此范圍定義變量,那么它們只在吃范圍內可以訪問。他還可以用于異常對象的指針名。

            DEBUG_NEW
            #define new DEBUG_NEW
            說明:
            幫助查找內存錯誤。用戶在程序中使用DEBUG_NEW,用戶通常使用new運算符來從堆上分配。在Debug模式下(但定義了一個DEBUG符號),DEBUG_NEW為它分配的每個對象記錄文件名和行號。然后,在用戶使用CMemoryState::DumpAllObjectSince成員函數時,每個以DEBUG_NEW分配的對象分配的地方顯示出文件名和行號。 為了使用DEBUG_NEW,應在用戶的資源文件中插入以下指令: #define new DEBUG_NEW 一旦用戶插入本指令,預處理程序將在使用new的地方插入DEBUG_NEW,而MFC作其余的工作。但用戶編譯自己的程序的一個發行版時,DEBUG_NEW便進行簡單的new操作,而且不產生文件名和行號消息。

            DECLARE_DYNAMIC
            DECLARE_DYNAMIC(class_name)
            說明:
            但從CObject派生一個類時,此宏增加關于一個對象類的訪問運行時間功能。把DECLARE_DYNAMIC宏加入類的頭文件中,然后在全部需要訪問詞類對象的.CPP文件中都包含此模塊。如果像所描述那樣使用DELCARE_DYNAMIC和IMPLEMENT_DYNAMIC宏,那么用戶便可使用RUNTIME_CLASS宏和CObject::IsKindOf函數以在運行時間決定對象類。如果DECLARE_DYNAMIC包含在類定義中,那么IMPLEMETN_DYNAMIC必須包含在類工具中。

            DECLARE_DYNCREATE
            DECLARE_DYNCREATE(class_name)
            說明:
            使用DECLARE_DYNCRETE宏以便允許CObject派生類的對象在運行時刻自動建立。主機使用此功能自動建立新對象,例如,但它在串行化過程中從磁盤讀一個對象時,文件及視圖和框架窗應該支持動態建立,因為框架需要自動建立它。把DECLARE_DYNCREATE宏加入類的.H文件中,然后在全部需要訪問此類對象的.CPP文件中包含這一模式。如果DECLARE_DYNCREATE包含在類定義中,那么IMPLEMENT_DYNCREATE必須包含在類工具中。

            DECLARE_MESSAGE_MAP
            DECLARE_MESSAGE_MAP()
            說明:
            用戶程序中的每個CCmdTarget派生類必須提供消息映射以處理消息。在類定義的末尾使用DECLARE_MESSAGE_MAP宏。接著,在定義類成員函數的.CPP文件中,使用BEGIN_MESSAGE_MAP宏,每個用戶消息處理函數的宏項下面的列表以及END_MESSAGE_MAP宏。
            注釋:
            如果在DECLARE_MESSAGE_MAP之后定義任何一個成員,那么必須為他們指定一個新存取類型(公共的,私有的,保護的)。

            DECLARE_SERIAL
            DECLARE_SERIAL(class_name)
            說明:
            DECLARE_SERIAL為一個可以串行化的CObject派生類產生必要的C++標題代碼。串行化是把某個對象的內容從一個文件讀出和寫入一文件。在.H文件中使用DECLARE_SERIAL宏,接著在需要訪問此類對象的全部.CPP文件中包含此文件。如果DECLARE_SERIAL包含在類定義中,那么IMPLEMENT_SERIAL必須包含在類工具中。DECLARE_SERIAL宏包含全部DECLARE_DYNAMIC,IMPLEMENT_DYCREATE的功能。

            END_CATCH
            END_CATCH
            說明:
            標識最后的CATCH或AND_CATCH塊的末尾。

            END_MESSAGE_MAP
            END_MESSAGE_MAP
            說明:
            使用END_MESSAGE_MAP宏結束用戶的消息映射定義
            IMPLEMENT_DYNAMIC
            IMPLEMENT_DYNAMIC(class_name,base_class_name)
            說明:
            通過運行時在串行結構中為動態CObject派生類訪問類名和位置來產生必要的C++代碼。在.CPP文件中使用IMPLEMENT_DYNAMIC宏,接著一次鏈接結果對象代碼

            IMPLEMENT_DYNCREATE
            IMPLEMENT_DYNCREATE(class_name,base_class_name)
            說明:
            通過DECLARE_DYNCREATE宏來使用IMPLEMENT_DYNCREATE宏,以允許CObject派生類對象在運行時自動建立。主機使用此功能自動建立對象,例如,但它在串行化過程中從磁盤讀去一個對象時,他在類工具里加入IMPLEMENT_DYNCREATE宏。若用戶使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏,那么接著使用RUNTIME_CLASS宏和CObject::IsKindOf成員函數以在運行時確定對象類。若declare_dyncreate包含在定義中,那么IMPLEMENT_DYNCREATE必須包含在類工具中。

            IMPLEMENT_SERIAL
            IMPLEMENT_SERIAL(class_name,base_class_name,wSchema)
            說明:
            通過運行時在串行結構中動態CObject派生類訪問類名和位置來建立必要的C++代碼。在.CPP文件中使用IMPLEMENT_SERIAL宏,然后一次鏈接結果對象代碼。

            ON_COMMAND
            ON_COMMAND(id,memberFxn)
            說明:
            此宏通過ClassWizard或手工插入一個消息映射。它表明那個函數將從一個命令用戶接口(例如一個菜單項或toolbar按鈕)處理一個命令消息。當一個命令對象通過指定的ID接受到一個Windows WM_COMMAND消息時,ON_COMMAND將調用成員函數memberFxn處理此消息。在用戶的消息映射中,對于每個菜單或加速器命令(必須被映射到一個消息處理函數)應該確實有一個ON_COMMAND宏語句。

            ON_CONTROL
            ON_CONTROL(wNotifyCode,id,memberFxn)
            說明:
            表明哪個函數將處理一個常規控制表示消息。控制標識消息是那些從一個控制夫發送到母窗口的消息。

            ON_MESSAGE
            ON_MESSAGE(message,memberFxn)
            說明:
            指明哪個函數將處理一用戶定義消息。用戶定義消息通常定義在WM_USER到0x7FF范圍內。用戶定義消息是那些不是標準Windows WM_MESSAGE消息的任何消息。在用戶的消息映射中,每個必須被映射到一個消息處理函數。用戶定義消息應該有一個ON_MESSAGE宏語句。

            ON_REGISTERED_MESSAGE
            ON_REGISTERED_MESSAGE(nmessageVarible,memberFxn)
            說明:
            Windows的RegisterWindowsMesage函數用于定義一個新窗口消息,此消息保證在整個系統中是唯一的。此宏表明哪個函數處理已注冊消息。變量nMessageViable應以NEAR修飾符來定義。

            ON_UPDATE_COMMAND_UI
            ON_UPDATE_COMMAND_UI(id,memberFxn)
            說明:
            此宏通常通過ClassWizard被插入一個消息映射,以指明哪個函數將處理一個用戶接口個更改命令消息。在用戶的消息映射中,每個用戶接口更改命令(比訊被映射到一個消息處理函數)應該有一個ON_UPDATE_COMMAND_UI宏語句。

            ON_VBXEVENT
            ON_VBXEVENT(wNotifyCode,memberFxn)
            說明:
            此宏通常通過ClassWizard被插入一個消息映射,以指明哪個函數將處理一個來自VBX控制的消息。在用戶的消息映射中每個被映射到一消息處理函數的VBX控制消息應該有一個宏語句。

            RUNTIME_CLASS
            RUNTIME_CLASS(class_name)
            說明:
            使用此宏從c++類民眾獲取運行時類結構。RUNTIME_CLASS為由class_name指定的類返回一個指針到CRuntimeClass結構。只有以DECLARE_DYNAMIC,DECLARE_DYNCREATE或DECLARE_SERIAL定義的CObject派生類才返回到一個CRuntimeClass結構的指針。

            THROW
            THROW(exception_object_pointer)
            說明:
            派出指定的異常。THROW中斷程序的運行,把控制傳遞給用戶程序中的相關的CATCH塊。如果用戶沒有提供CATCH塊,那么控制被傳遞到一個MFC模塊,他打印出一個錯誤并終止運行。

            THROW_LAST
            THROW_LAST()
            說明:
            此宏允許用戶派出一個局部建立的異常。如果用戶試圖排除一個剛發現的異常,那么一般此異常將溢出并被刪除。使用THROW_LAST,此異常被直接傳送到下一個CATCH處理程序。

            TRACE
            TRACE(exp)
            說明:
            把一個格式化字符串送到轉儲設備,例如,文件或調試監視器,而提供與printf相似的功能。同MS_DOS下C程序的printf一樣,TRACE宏是一個在程序運行時跟蹤變量值的方便形式。在DEBUG環境中,TRACE宏輸出到afxDump。在Release版中他不做任何工作。
            注釋:
            此宏只在MFC的DEBUG版中有效。

            TRACE0
            TRACE0(exp)
            說明:
            與TRACE相似,但他把跟蹤字符串放在代碼段中,而不是DGROUP,因此使用少的DGROUP空間。TRACE0是一組跟蹤宏的一個變體,這些宏可用于調試輸出。這一組包括TRACE0,TRACE1,TRACE2和TRACE3,這些宏不同在于所取參數的數目不同。TRACE0只取一個格式化字符串并可用于簡單文本消息。TRACE1取一格式化字符串加上一個變量——一個將轉儲的變量。同樣,TRACE2,TRACE3分別取2個或3個參數(在格式化字符串之后)。如果用戶以便以了應用程序的發行版,那么它只把數據轉儲到afxDump。
            注釋:
            此宏只在MFC的DEBUG中有效。


            TRACE1
            TRACE1(exp,param1)
            說明:
            參見TRACE0

            TRACE2
            TRACE2(exp,param1,param2)
            說明:
            參見TRACE0

            TRACE3
            TRACE3(exp,param1,param2,param3)
            說明:

            TRY
            TRY
            說明:
            使用此宏建立一TRY塊。一個TRY識別一個可排除異常的代碼塊。這些異常在隨后的CATCH和AND_CATCH塊處理。傳遞是允許的:異??梢詡鬟f一個外部TRY塊,或者忽略它們或者使用THROW_LAST宏。

            VERIFY
            VERIFY(booleanExpression)
            說明:
            在MFC的DEBUG版中,VERIFY宏計算它的變量值。 如果結果為0,那么宏打印一個診斷消息并中止程序。如果條件不為0,那么什么工作也不作。 診斷有如下形式: assertion failed in file in line 其中name是源文件的名字,num是在源文件中失敗的中止行號。在MFC的Release版中,VERIFY計算表達式值但不打印或中止程序。例如:如果表達式是個函數調用,那么調用成功。
            posted @ 2007-01-15 19:20 dyh 閱讀(367) | 評論 (0)編輯 收藏
             
                 摘要: [轉載]VC中的一些常用方法(20條) 文章來源: www.csdn.net 作者blog: http://blog.csdn.net/oury/ VC中的一些常用方法 //一、打開CD-ROMmciSendStrin...  閱讀全文
            posted @ 2007-01-15 19:14 dyh 閱讀(406) | 評論 (0)編輯 收藏
             
            寫這個文章完全是因為想要搞清楚 vc 怎么布局每個 c++ 對象,以及怎樣完成指針的轉換的過程.
              先問一個問題,兩個不同類型的指針相互轉換以后,他們在數值上是一樣的嗎?比如:

                int nValue = 10;
                int *pInt = &nValue;
                void *pVoid = pInt;
                char *pChar = (char*)pInt;


              這些指針的值(不是說指針指向的內存的內容)是一樣的嗎? 如果你的回答是 yes,那如果是一個類的繼承體系呢?在繼承類向基類轉換的過程中,指針的數值還是不變化的么?如果你的回答是"不一定會變化,要看類的體系是怎么設計的"的話,那恭喜你,不用看下去了.如果你還不確定究竟變還是不變,究竟哪些變,哪些不變,究竟為什么要變為什么不變的話,接著看下來.

              c++ 標準不規定 c++ 實現的時候的對象的具體的內存布局,除了在某些方面有小的限制以外,c++ 對象在內存里面的布局完全是由編譯器自行決定,這里我也只是討論 vc++ .net 2003 build 7.1.3091 的實現方式,我并沒有在 vc5 vc6 vc.net 2002 以及其他的 2003 build 上面做過測試,結論也許不適合那些編譯平臺.這些屬于編譯器具體實現,ms 保留有在不通知你我的情況下作出更改的權利.廢話這么多,馬上開始.

              對于 c 的內建指針的轉換,結果是不用多討論的,我們只是討論 c++ 的對象.從最簡單的開始.

                class CBase
                {
                public:
                  int m_nBaseValue;
                };


              這樣的一個類在內存里放置是非常簡單的,他占有4個 bytes 的空間,不用多說,我們從他派生一個類出來.

                class CDerive1 : public CBase
                {
                public:
                  int m_nDerive1Value;
                };


              CDerive1 的對象在內存里面是怎么放的呢? 也很簡單,占有8個 bytes 的空間,前4個 bytes 屬于 CBase 類,后四個 bytes 屬于自己.一個CDerive1 的指針轉換成一個 CBase 的指針,結果是一樣的.下面我們加上多重繼承看看.

                class CFinal : public CDerive,public CBase // 這里的 CDerive 是一個和 CBase 差不多的基類
                {
                public:
                  int m_nFinalValue;
                };


              CFinal 的對象在內存里面的布局稍微復雜一點,但是也很容易想象,他占有 12 個 bytes 的空間,前4個屬于 CDerive,中間4個屬于 CBase,后面4個才是自己的.那一個 CFinal 的指針轉換成一個 CDerive 指針,數值會變么? 轉換成一個 CBase 指針呢?又會變化么?答案是,前一個不變,后一個要變化,道理非常的明顯,CFinal 對象的開頭剛好是一個 CDerive 對象,而 CBase 對象卻在 CFinal 對象的中間,自然是要變化的了,具體怎么變化呢? 加 4 就 ok(自然要檢查是否是空指針).

                CBase *pBase = pFinal ? (CBase*)((char*)pFinal + sizeof(CDerive)) : 0;// 當你寫下 pBase = pFinal 的時候,其實是這樣的

              這種不帶 virtual 的繼承就這么簡單,只是加上一個 offset 而已.下面我們看看如果加上 virtual function 的時候是什么樣子的呢?
            還是從簡單類開始.

                class CBase
                {
                public:
                  virtual void VirtualBaseFunction(){}
                  int m_nBaseValue;
                };


              這里刻意沒有使用 virtual destructor,因為這個函數稍微有些不同.還是同樣的問題,CBase 類在內存上占多大的空間?還是 4 bytes 么? 答案是 no, 在我的編譯器上面是 8 bytes,多出來的 4 bytes 是 __vfptr(watch 窗口看見的名字),他是一個指針,指向了類的 vtable,那什么是 vtable 呢,他是用來干什么的呢? vtable 是用來支援 virtual function 機制的,他其實是一個函數指針數組(并不等同于c/c++語言里面的指針數組,因為他們的類型并不一定是一樣的.)他的每一個元素都指向了一個你定義的 virtual function,這樣通過一個中間層來到達動態連編的效果,這些指針是在程序運行的時候準備妥當的,而不是在編譯的時候準備妥當的,這個就是動態聯編的目的,具體是由誰來設置這些指針的呢?constructor/destructor/copy constructor/assignment operator他們完成的,不用奇怪,編譯器會在你寫的這些函數里面安插些必要的代碼用來設置 vtable 的值,如果你沒有寫這些函數,編譯器會在適當的時候幫你生成這些函數.明白一點, vtable 是用來支持 virtual function 機制的,而需要 virtual 機制的類基本上都會由一個 __vfptr 指向他自己的 vtable.在調用 virtual function的時候,編譯器這樣完成:

               pBase->VirtualBaseFunction(); => pBase->__vfptr[0]();// 0 是你的virtual function 在 vtable 中的 slot number,編譯器決定

              現在應該很想象 CBase 的大小了吧,那這個 __vfptr 是放到什么位置的呢? 在 m_nBaseValue 之前還是之后呢? 在我的編譯器上看來,是在之前,為什么要放到之前,是因為在通過 指向類成員函數的指針調用 virtual function 的時候能少些代碼(指匯編代碼),這個原因這里就不深入討論了,有興趣的同學可以看看 inside the c++ object model 一書.
              接下來,我們加上繼承來看看.

                class CDerive1 : public CBase
                {
                public:
                  virtual void VirtualDerive1Function();
                };


              這個時候你也許要說,內存布局跟沒有 virtual 是一樣的,只不過每個類多了一個 __vfptr 而已,呃...這個是不對的,在我的編譯器上面 兩個類共享同一個 __vfptr, vtable 里面放有兩個指針,一個是兩個類共享的,一個只屬于 CDerive1 類,調用的時候如何呢?

               pDerive1->VirtualDerive1Function() => pDerive1->__vfptr[1]();
               pDerive1->VirtualBaseFunction() => pDerive1->__vfptr[0]();


              至于指針的相互轉換,數值還是沒有變化的(也正是追求這種效果,所以把 __vfptr 放到類的開頭,因為調整 this 指針也是要占有運行時的時間的).

              現在加上多重繼承瞧瞧,代碼我不寫上來了,就跟上面的 CFinal, CDerive, CBase 體系一樣,只是每個類多一個VirtualxxxFunction出來,這個時候的指針調整還是沒有什么變化,所以我們只是看看 vtable 的情況,你會說 CDerive 和 CFinal 共享一個 __vfptr,而 CBase 有一個自己的 __vfptr,而 CFinal 的 __vfptr 有 2 個slot,這個結論是正確的. 同時你也會說 通過 CFinal 類調用 CBase 的函數是要進行指針調整的,yes you'r right,不僅僅是 this 指針調整(呃,this 指針會成為 function 的一個參數),還要調整 vtable 的值:

               pFinal->VirtualBaseFunction() => (CBase*)((char*)pFinal + sizeof(CDerive))->__vfptr[0]();

               轉換成 asm 的代碼大約是這樣的:

               mov eax,[pFinal] ; pFinal is a local object,pFinal will be epb - xx
               add eax,8 ; 8 = sizeof(CDerive)
               mov ecx,eax ; ecx is this pointer
               mov edx,[eax] ; edx = vtable address
               call [edx] ; call vtable[0]


              寫到這里也就明白this指針是怎么調整的.帶 virtual function 的繼承也不復雜,this指針調整也是很簡單的,下面看最復雜的部分 virtual inheritance.

              我的編譯器支持虛擬繼承的方式和虛函數的方式差不多,都是通過一個 table 完成,只是這個就看不到 vc 賦予的名字了,我們叫他 vbtable 吧,編譯器同樣在類里面加入一個指向 vbtable 的指針,我們叫他 __vbptr 吧,這個指針指向了 vbtable ,而 vbtable 里面的每一項對應了一個基類,vbtable 記錄了每個基類的某一個偏移量,通過這個偏移量就能計算出具體類的指針的位置.看個簡單的例子:

               class CBase
               {
               public:
                 virtual ~CBase(){}
               };

               class CMid1 : public virtual CBase
               {
               public:
                 virtual ~CMid1(){}
                 int m_nMid1;
               };

               class CMid2 : public virtual CBase
               {
               public:
                 virtual ~CMid2(){}
                 int m_nMid2;
               };

               class CFinal : public CMid1,public CMid2
               {
               public:
                 virtual ~CFinal(){}
                 int m_nFinal;
               };

               CFinal final;
               CFinal *pFinal = &final;??? // pFinal = 0x0012feb4;
               CBase *pBase = pFinal; // pBase = 0x0012fec8 = pFinal + 0x14;
               CMid1 *pMid1 = pFinal; // pMid1 = 0x0012feb4 = pFinal;
               CMid2 *pMid2 = pFinal; // pMid2 = 0x004210b4 = pFinal;


              結果讓你吃驚嗎? 最奇怪的地方居然是 CMid2 和 CMid1 的地址居然是一樣的,這個是因為 vc 把 vbtable 放到了 CFinal 類的開頭的原因,而CMid1 和 CMid2 也同樣要使用這個 vbtable, 所以 這個三個的地址也就必須相同了.那 CBase 的地址是怎么出來的呢? 呃...剛剛我們說了 vbtable 放到了CFinal 的開頭(vc 一定會放在開頭嗎?答案是不一定,這個稍后解釋).在我的機器上面 final 對應內存的第一個 dword 是 0x00426030,查看這個地址,第一個dword 是 0 ,第二個就是 0x14,剛好和 pBase 的偏移相同,這個只是巧合,也許你換個類的繼承體系就完全不同了,但是我只是想說明一點,基類的偏移計算是和 vbtable 的值相關聯的.下面我們就來看看 vc 是怎么計算這些偏移的.
              vc 在分析我們的代碼的時候,生成了一份類的繼承體系信息,其中有一個叫 thisDisplacement 的_PMD結構:

                struct _PMD // total undocumented
                {
                  int mdisp; // i think the meaning is Multiinheritance DISPlacement
                  int pdisp; // Pointer to vbtable DISPlacement
                  int vdisp; // Vbtable DISPlacement
                };


              結構的名字和成員變量的名字確確實實是 vc 的名字(在 watch 窗口輸入 (_PMD*)0 就能看到這個結構的詳細信息),每個字段的含義卻是我自己猜測出來的.mdisp 大概用來表示多重繼承(包括單一繼承)的時候的偏移量,pdisp 表示 vbtable 的偏移量,而 vdisp 表示類在 vbtable 里面的下標.那么有了這個結構怎樣才能完成指針的轉換呢?假如我們有一個派生類指針 pFinal,要轉換成一個特定的基礎類,我們首先要知道和這個基類對應的 _PMD 結構的信息(這個信息的獲取,我暫時沒有找到一個非常方便的方法,現在我使用的方法下面會有描述),有了這個信息以后,轉換就方便了.首先找到 vbtabel 的地址 *(pFinal + pdisp),然后找到基類的偏移 *(*(pFinal + pdisp) + vdisp) 這個偏移值是相對vbtable的,所以還要加上 vbtable的偏移,最后加上 mdisp的偏移,如下:

              char *pFinal = xxx; // need a init value
              char *pBase; // we must calc
              pBase = pFinal + mdisp + *(int *)(*(int *)(pFinal + pdisp) + vdisp) + pdisp;


              注意: 當 pdisp < 0 的時候就表示這個類沒有 vbtable 直接使用 pFinal + mdisp 就得到結果了.
              所以這個結構是一個通用的結構,專門用作類型轉換,不管是有無虛繼承都能使用這個結構進行類型轉換.

              通過這個結構,我們也能看到 vc 是怎樣布局這個 object 的.

              看到這里,也許你要大呼一口氣,媽媽呀,一個類型轉換要這么的麻煩嗎?我直接寫 pBase = pFinal 不就可以了嗎? 恭喜你還沒有被我忽悠得暈頭轉向,哈哈.其實你寫下那行語句的時候,編譯器在幫你做這個轉換,大約生成下面的代碼

                mov eax,[pFinal] ;final address
                mov ecx,[eax] ; vbtable address *(int *)(pFinal + pdisp)
                mov edx,eax ; save to edx
                add edx,[ecx + 4] ; ecx + 4 is (*(int *)(pFinal + pdisp) + vdisp)
                mov [pBase],edx ; edx = pFinal + mdisp + *(int *)(*(int *)(pFinal + pdisp) + vdisp) + pdisp;
                ; here mdisp = 0, pdisp = 0, vdisp = 4


              也許你要說了,我要這些東西來干什么?要轉換的時候直接轉換就好了,編譯器會幫做,的確,大多數的時候確實是這樣,但是,在某些時候卻并不如此,現在你要實現一個功能,輸入一個指針,輸入一個 _PMD 結構,你要實現一個AdjustPointer 的函數來生成另一個指針.這個時候你也只能這樣完成了,因為我沒有給你兩個指針的名字,就算給了你字符串形式的名字也沒有用,呃....你也許會說,辦法是有的,的確是有,模板就能實現這種功能,呵..這個我們暫時不討論具體的實現細節.也許你要問了,究竟什么時候會去實現這種聽都沒有聽過的功能,其實這個函數是真正存在的,只不過不是由你來實現的,而是 ms 的人實現的,你只用寫一個 帶有 c++ 異常的程序,使用 ida 反匯編,然后查找函數,就能找到這個函數了,他用來在異常處理時創建 catch 所需要的 object.至于這個詳細的信息,請期待.我會最快速度寫出關于 vc 是怎樣實現 c++ 異常的文章來.

              最后了,說說那個 _PMD 結構的獲取方式.看的時候不要吃驚,方法比較的麻煩,比如我想知道和 CFinal 類相關的 _PMD 信息,先新建工作,寫下 throw pFinal 這樣的語句,編譯,在這個語句的地方設置斷點,運行,轉到反匯編,進入 __CxxThrowException@8 函數,這個時候不出意外你能看到一個叫 pThrowInfo 的東西(如果看不到,請打開"顯示符號名"選項),在 watch 窗口里面輸入pThrowInfo,展開他,看到一個pCatchableTypeArray,記錄下他的 nCacthableTypes的值,然后在 watch 里面輸入
            pThrowInfo->pCatchableTypeArray->arrayOfCatchableTypes[0] 到 pThrowInfo->pCatchableTypeArray->arrayOfCatchableTypes[n], n 就是你剛剛記錄的值減1,再展開他們,你就能看到一個 thisDisplacement 的數據,繼續展開就是 mdisp 等等了,很是麻煩吧.哈..你已經猜到了,這個是和異常有關系的.

              后記: 這段時間,我一直在讀些反匯編之后的代碼,也頗有些心得,所以才有想法寫一些文章,探討 vc 編譯器鮮為人知(太過狂妄了)的秘密,這個方面的文章也有人寫過,那些文章也給我不少的啟發,我不認為自己是第一個發現這些秘密的人,但是至少我自己知道的,我是第一個把這些東西寫出來的人.文章里面作墨多的部分都是自己發現的.就這個文章里面的內容來說,inside the c++ object model 是有比較詳細的描寫,但是他并不是轉換針對 vc 這個編譯器的實現,而 _PMD 這個結構我也沒有在什么地方見有人描述過,只是在 windows develop network 的2002年12月的雜志上看有人提到過這個結構,可惜他卻沒有了解(至少他在他發表文章的時候是如是說的)這個結構的用處(正是因為這個原因,我才有寫這個文章以及后續文章的沖動).所以,這個文章也算是我自己的原創吧.這個文件雖然和游戲制造沒有太大的關系,但是小 T 自視清高,不愿意自己的文章被一幫不懂的人評價來評價去的,所以也沒有發到那些著名的 xxx 網站,只發 goldpoint.轉載請注明出處(小 T 對自己的第一個原創文章比較珍惜,比較重視,謝謝).
            posted @ 2007-01-08 21:17 dyh 閱讀(1158) | 評論 (1)編輯 收藏
             
            安裝Visual Studio.net 2003時,系統提示"安裝程序檢測到另一個程序要求計算機重新啟動。必須重新啟動計算機后才能安裝 Visual Studio .NET 系統必備。系統重新啟動后,您需要重新啟動安裝程序。單擊“確定”重新啟動。單擊“取消”退出安裝程序,以后再安裝。"系統重新啟動后,問題依舊。
            解決方法:
            打開注冊表,找到下面主鍵并刪除
            HKEY_LOCAL_MACHINE\SYSTEM\Control001\Contrl\Session Manager\PendingFileRenameOperations,刪除后的效果是立桿見影的,馬上就可以安裝了。
            posted @ 2006-12-13 21:16 dyh 閱讀(2433) | 評論 (5)編輯 收藏
             

            一、在程序中包含commctrl.h頭文件,鏈接到ComCtl32.lib,并調用InitCommon Controls.

            #if ?(_WIN32_IE?>=?0x0300)
            ????INITCOMMONCONTROLSEX?iccx;
            ????iccx.dwSize?
            = ? sizeof (iccx);
            ????iccx.dwICC?
            = ?ICC_COOL_CLASSES? | ?ICC_BAR_CLASSES;
            ????BOOL?bRet?
            = ?::InitCommonControlsEx( & iccx);
            ????bRet;
            ????ATLASSERT(bRet);
            #else
            ????::InitCommonControls();
            #endif


            二、向項目中添加一個名為YourApp.exe.manifest的文件,其中的YourApp就寫你的可執行文件的名稱,這個文件具有XML格式:

            ?? <? xml?version="1.0"?encoding="UTF-8"?standalone="yes"? ?> ?
            -?
            < assembly? xmlns ="urn:schemas-microsoft-com:asm.v1" ?manifestVersion ="1.0" >
            ??
            < assemblyIdentity? version ="1.0.0.0" ?processorArchitecture ="X86" ?name ="CompanyName.ProductName.YourApp" ?type ="win32" ? /> ?
            ??
            < description > Your?application?description?here. </ description > ?
            -?
            < dependency >
            -?
            < dependentAssembly >
            ??
            < assemblyIdentity? type ="win32" ?name ="Microsoft.Windows.Common-Controls" ?version ="6.0.0.0" ?processorArchitecture ="X86" ?publicKeyToken ="6595b64144ccf1df" ?language ="*" ? /> ?
            ??
            </ dependentAssembly >
            ??
            </ dependency >
            ??
            </ assembly >

            三、在應用程序的資源文件(YourApp.rc)的頂部中添加這樣的一行語句:

            CREATEPROCESS_MANIFEST_RESOURCE_ID?RT_MANIFEST? "YourApp .exe.manifest "

            我在程序中沒有做第一步也能成功,可能是因為沒有用API創建的窗口或動態窗口,在程序中如果調用了CreateWindow函數去創建窗口,則需要執行第一步。

            posted @ 2006-06-22 13:04 dyh 閱讀(608) | 評論 (0)編輯 收藏
             
            大家在使用C++寫操作文件的程序時,一定使用過eof()這個函數,用它來判別文件結束,但有不少也用來判別文件是否為空的.但是,這樣操作的結果不是我們所想的.看下面程序:

            #include?<string>

            using?namespace?std;

            int?main()
            {
            ????
            char?c?=?'c';
            ????ifstream?FILE(
            "test.txt");
            ????
            if?(FILE.eof())
            ????????cout?
            <<?"文件是空的."<<endl;exit(1);?

            ????
            while?(!FILE.eof())
            ????
            {
            ????????FILE.
            get(c);
            ????????cout?
            <<?c;
            ????}
            ????
            ????system(
            "pause");
            ????
            return?0;
            }


            當test.txt為空文件時,它輸出的是:c
            奇怪!應該輸出是:文件是空的. while里面的操作也應該不用到的.但是結果偏偏和我們所想的相反.
            好,那操作二進制文件又是怎樣的呢?修改下為:
            ???

            ?ifstream?FILE("test.txt",ios::in|ios::binary);
            ????
            if?(FILE.eof())
            ????????cout?
            <<?"文件是空的."<????while?(!FILE.eof())
            ????
            {
            ????????FILE.read(
            &c,1);
            ????????cout?
            <<?c;
            ????}
            //代碼其他部分相同

            結果輸出還是c..噢!!怎么會這樣的.分明是騙人的東西嘛!!到底是什么原因呢?
            ???? 經過一段研究后,原來eof()返回true的條件是"讀到文件結束符",而不是文件內容的最后一個字符。
            要清楚"文件結束符"(0xff).就是說我們文件最后的字符不是文件結束符,而最后的字符的下一位才是.所以操作再讀多一次.就為什么上面if (FILE.eof())總是false的. 在一些編譯器中(dev c++),它讀到最后一個字符后文件位置的指針會定在那兒,所以就會重復最后一個字符.?????
            ??? 在一個外國的CPP論壇見到一位同志的代碼剛好有這解決方法.現在把上面的代碼改為下面的:

            #include?<iostream.h>
            #include?
            <stdlib.h>
            #include?
            <fstream.h>

            int?main()
            {
            ????
            char?c?=?'c';
            ????ifstream?FILE(
            "test.txt");
            ????
            if?(FILE.peek()?==?EOF)//修改?
            ????{
            ????????cout?
            <<?"文件是空的."<<?endl;?
            ????????exit(
            1);
            ????}
            ???????
            ????
            while?(FILE.peek()?!=?EOF)//修改
            ????{
            ????????FILE.
            get(c);
            ????????cout?
            <<?c;
            ????}
            ????
            ????system(
            "pause");
            ????
            return?0;
            }

            主要的是把eof()改為peek() == EOF來判別,其中peek()是取文件當前指針,EOF是文件尾標符,它的值為-1.所以采用這種方法就解決上面eof()的問題了..這種方法也可以用在讀寫二進制文件中.

            posted @ 2006-06-21 21:10 dyh 閱讀(4875) | 評論 (6)編輯 收藏
             

            ??????? 由于項目的需要,需要用C++去連Oracle數據庫,Oracle版本為10g,在經過n次錯誤后終于成功連接。
            ?????? ?最開始用ADO連,裝上客戶端以后,在Oracle Net Manager中設置服務命名,測試連接成功,然后
            設置ODBC數據源,測試也成功,ADO連接串寫上"DSN=xxx"就能連上了,但是在釋放連接時總是出錯,
            經過多次嘗試后放棄,在網上看到Oracle專門為C++提供了連接的接口OCCI,于是嘗試采用這種方法。
            ??????? 一、安裝Oracle客戶端
            ??????????????? 安裝方式選擇為管理員。安裝完以后設置服務命名。在%ORACLIENTHOME%\NETWORK\ADMIN目錄下tnsnames.ora文件記錄了服務命名的設置。我的設置如下:
            ???????????????

            ORCL_192.168.0.3?=
            ??(DESCRIPTION?=
            ????(ADDRESS_LIST?=
            ??????(ADDRESS?=?(PROTOCOL?=?TCP)(HOST?=?192.168.0.3)(PORT?=?1521))
            ????)
            ????(CONNECT_DATA?=
            ??????(SID?=?orcl)
            ??????(SERVER?=?DEDICATED)
            ????)
            ??)

            ORCL?=
            ??(DESCRIPTION?=
            ????(ADDRESS_LIST?=
            ??????(ADDRESS?=?(PROTOCOL?=?TCP)(HOST?=?192.168.0.3)(PORT?=?1521))
            ????)
            ????(CONNECT_DATA?=
            ??????(SERVICE_NAME?=?ORCL_192.168.0.3)
            ????)
            ??)

            其中192.168.0.3為遠程Oracle服務器IP地址
            設置完以后在Net Manager中測試連接,成功就OK了。
            ????????二、設置VC++.net環境
            ??????? 我用的編譯器是VS2003,首先在工具---選項中Project添加包含文件%OracleClientHome%\Oci\include
            添加庫文件%OracleClientHome%\Oci\lib\msvc\vc71和%OracleClientHome%\Oci\lib\msvc如果是VC6.0的話就
            添加%OracleClientHome%\Oci\lib\msvc\vc6。
            然后設置項目屬性,在編譯器---鏈接中添加lib文件,oraocci10d.lib,之后把oraocci10d.dll文件復制到system32目錄下,這個文件在VC71目錄下。
            第三步在C++----代碼生成中設置運行時庫為多線程DLL或是多線程調試DLL,注意這一步設置很重要,
            沒有設置的話會造成getString函數出錯。
            ??????? 三、代碼
            ???????

            #include? " stdafx.h "
            #include?
            < occi.h >
            using ? namespace ?std;
            using ? namespace
            ?oracle::occi?;
            int
            ?main()?
            {
            ????Environment
            * ?env = Environment::createEnvironment( " ZHS16GBK " ,? " UTF8 "
            );
            ????
            // Environment?*env=Environment::createEnvironment(Environment::DEFAULT);


            ????
            string ?mc;
            ????
            {
            ????????Connection?
            * conn? = ?env -> createConnection( " system " ,? " system " ,? " orcl_192.168.0.3 "
            );
            ????????
            ????????
            try

            ????????
            {
            ????????????Statement?
            * stmt? = ?conn -> createStatement( " select?*?from?test1 "
            );

            ????????????ResultSet?
            * rs? = ?stmt ->
            executeQuery();
            ????????????
            while (rs -> next()? == ? true
            )
            ????????????
            {
            ????????????????mc
            = rs -> getString( 1
            );
            ????????????????cout?
            << ?mc? <<
            ?endl;
            ????????????}

            ????????????stmt
            -> closeResultSet(rs);
            ????????????conn
            ->
            terminateStatement(stmt);
            ????????}

            ????????
            catch ?(SQLException?e)
            ????????
            {
            ????????????cout
            <<
            e.what();
            ????????}

            ????????env
            -> terminateConnection(conn);
            ????}

            ????Environment::terminateEnvironment(env);
            ????system(
            " pause " );
            ????
            return ? 0
            ;
            }

            orcl_192.168.0.3就是前面設置的連接串。
            測試成功。以上就是所有的設置過程,唉,還是jdbc好連阿。。。

            注意,項目屬性中運行時庫一定要設置為多線程DLL或是多線程調試DLL,否則getString函數就會出錯,我在這個問題上也卡了很長時間,單步調試發現是在string對象析購時出錯,在網上看到一篇文章得到答案。
            ??????? 原因是由于程序中使用的內存管理多來源于crt提供的例程,而非直接使用操作系統的接口,這些例程都需要維護一些module全局數據(例如維護池、維護空閑塊、或者標記已申請的塊等等,不同的實現中有不同的作用),當他們被靜態連編時,實際上這些“全局數據”就不“全局”了,不同的module各自為政,每份module都有自己的“全局數據”,自身的內存信息不為他人所知,module A的合法內存快自然不可能通得過module B的合法性驗證

            解決問題的方法有:
            1、不要跨module傳遞c++對象,或者避免釋放跨module申請的內存

            2、將參與合作的module統統以multithreaded dll方式鏈入crt庫,讓他們的“全局”數據真正全局,注意,所有有交互的module都需要動態鏈入crt。

            posted @ 2006-06-20 13:07 dyh 閱讀(6029) | 評論 (12)編輯 收藏
             

            1)運行時庫就是 C run-time library,是 C 而非 C++ 語言世界的概念:取這個名字就是因為你的 C 程序運行時需要這些庫中的函數.

            2)C 語言是所謂的“小內核”語言,就其語言本身來說很小(不多的關鍵字,程序流程控制,數據類型等);所以,C 語言內核開發出來之后,Dennis Ritchie 和 Brian Kernighan 就用 C 本身重寫了 90% 以上的 UNIX 系統函數,并且把其中最常用的部分獨立出來,形成頭文件和對應的 LIBRARY,C run-time library 就是這樣形成的。

            3)隨后,隨著 C 語言的流行,各個 C 編譯器的生產商/個體/團體都遵循老的傳統,在不同平臺上都有相對應的 Standard Library,但大部分實現都是與各個平臺有關的。由于各個 C 編譯器對 C 的支持和理解有很多分歧和微妙的差別,所以就有了 ANSI C;ANSI C (主觀意圖上)詳細的規定了 C 語言各個要素的具體含義和編譯器實現要求,引進了新的函數聲明方式,同時訂立了 Standard Library 的標準形式。所以C運行時庫由編譯器生產商提供。至于由其他廠商/個人/團體提供的頭文件和庫函數,應當稱為第三方 C 運行庫(Third party C run-time libraries)。

            4)C run-time library里面含有初始化代碼,還有錯誤處理代碼(例如divide by zero處理)。你寫的程序可以沒有math庫,程序照樣運行,只是不能處理復雜的數學運算,不過如果沒有了C run-time庫,main()就不會被調用,exit()也不能被響應。因為C run-time library包含了C程序運行的最基本和最常用的函數。


            5)到了 C++ 世界里,有另外一個概念:Standard C++ Library,它包括了上面所說的 C run-time library 和 STL。包含 C run-time library 的原因很明顯,C++ 是 C 的超集,沒有理由再重新來一個 C++ run-time library. VC針對C++ 加入的Standard C++ Library主要包括:LIBCP.LIB, LIBCPMT.LIB和 MSVCPRT.LIB

            6)Windows環境下,VC提供的 C run-time library又分為動態運行時庫和靜態運行時庫。
            動態運行時庫主要是DLL庫文件msvcrt.dll(or MSVCRTD.DLL for debug build),對應的Import library文件是MSVCRT.LIB(MSVCRTD.LIB for debug build)
            靜態運行時庫(release版)對應的主要文件是:
            LIBC.LIB (Single thread static library, retail version)
            LIBCMT.LIB (Multithread static library, retail version)

            msvcrt.dll提供幾千個C函數,即使是像printf這么低級的函數都在msvcrt.dll里。其實你的程序運行時,很大一部分時間時在這些運行庫里運行。在你的程序(release版)被編譯時,VC會根據你的編譯選項(單線程、多線程或DLL)自動將相應的運行時庫文件(libc.lib,libcmt.lib或Import library msvcrt.lib)鏈接進來。

            編譯時到底哪個C run-time library聯入你的程序取決于編譯選項:
            /MD, /ML, /MT, /LD?? (Use Run-Time Library)
            你可以VC中通過以下方法設置選擇哪個C run-time library聯入你的程序:
            To find these options in the development environment, click Settings on the Project menu. Then click the C/C++ tab, and click Code Generation in the Category box. See the Use Run-Time Library drop-down box.

            從程序可移植性考慮,如果兩函數都可完成一種功能,選運行時庫函數好,因為各個 C 編譯器的生產商對標準C Run-time library提供了統一的支持.

            posted @ 2006-06-20 12:11 dyh 閱讀(585) | 評論 (0)編輯 收藏
            僅列出標題
            共2頁: 1 2 
             
            国产A级毛片久久久精品毛片| 国产91色综合久久免费| 一级女性全黄久久生活片免费| 久久久久国产一区二区三区| 2021国产精品久久精品| 国产亚洲综合久久系列| 91性高湖久久久久| 久久精品国产日本波多野结衣| 久久AV高清无码| 精品久久久久成人码免费动漫| 久久精品国产亚洲77777| 久久综合伊人77777麻豆| 久久丫精品国产亚洲av不卡| 久久久久国产亚洲AV麻豆| 精品久久久久久无码中文字幕一区 | 久久久无码精品亚洲日韩蜜臀浪潮 | 无码人妻少妇久久中文字幕蜜桃| 久久国产精品99精品国产987| 理论片午午伦夜理片久久| 久久这里只有精品首页| 日韩人妻无码精品久久久不卡 | 无码超乳爆乳中文字幕久久| 国产一区二区三精品久久久无广告 | 狠狠色综合网站久久久久久久高清 | 99久久国产热无码精品免费 | 久久精品国产99久久久古代| 久久99精品久久久久久噜噜 | 久久综合久久美利坚合众国| 国产国产成人久久精品| 色综合色天天久久婷婷基地| 99精品国产在热久久| 久久99国产综合精品| 久久精品中文无码资源站| 色欲久久久天天天综合网| 亚洲精品无码久久一线| 无码国内精品久久人妻蜜桃| 波多野结衣AV无码久久一区| 久久91精品国产91久| 97精品依人久久久大香线蕉97| 午夜精品久久久内射近拍高清| 久久综合亚洲色HEZYO国产|