讓VS調試器幫你格式化顯示自訂資料

來源:互聯網
上載者:User

這裡講解的是針對vs2010之前的版本的(即vs2005,vs2008。因為vs2010對於這方面有了一些改動),並以CEGUI 0.7.9版本(因為這個版本的CEGUI的String對象採用統一utf32編碼,調試時很難查看字串資訊)中的CEGUI::String類型為例講解,

首先介紹一點此版本的CEGUI::String類需要注意的地方。

有一個很重要的地方需要注意,0.7.9的版本中CEGUI::String對於const char*,以及對於const utf8*(即const unsigned char*)的建構函式有區別。

  1. 前者(const char*)會直接將傳入的字串,逐一地,原封不動的,放到utf32(即unsigned int)緩衝區中。也就是一個簡單的容量擴充操作。這對於ASCII字元集中的字元時沒有問題的,因為utf32編碼的ASCII字元集,與原來的ASCII碼的值在數值上是相等的。但是如果是非ASCII字元集的字元,採用這種方式得到的將是一個錯誤的utf32編碼。
  2. 但是如果傳入是const utf8*,那麼建構函式將會將此傳入的緩衝區,看待成utf8編碼的字元緩衝區,並進行utf8轉到到utf32的編碼操作。

很多時候我們想通過CEGUI::String::c_str()函數,讓CEGUI::String返回c風格字串,但是我要告訴你,CEGUI::String::c_str()是個文不達意的函數,其真正功能是將儲存的utf32字串轉換成utf8編碼的字串。這對於ascii字元集中的字元沒有什麼問題,但是對非ASCII字元集的字元,你調用CEGUI::String::c_str()將會返回亂碼。

  1. 假如你有以下代碼:
        CEGUI::String strTest = "中國";    std::cout << strTest.c_str() << std::endl;

    你將得不到“中國”這樣的輸出。

這是為什麼呢?這正是前面第一點提到的,因為CEGUI::String::String( const char* )建構函式,對於非ASCII字元集字串的構造根本就是錯誤的。這點在CEGUI::String::Assign(const utf8*)中的注釋中CEGUI已經考慮到了。但是未做過多處理。

然後我們來看一下如果讓vs調試器幫你格式化顯示CEGUI::String類型。

用過CEGUI.0.7.9的開發人員都知道,CEGUI::String類中直接將字串全部儲存到utf32(即一個字元為4個位元組)的緩衝區中!這將意味著vs調試器不能直接查看CEGUI::String裡面的字元,因為這個緩衝區裡面到處都有c風格字串的結尾符(即位元組的值為0)。所以你很難查看到一個CEGUI::String對象的字元含義。當然如果你的CEGUI::String裡面只儲存的是ascii字元,那麼有個簡陋的方法是可以看到字串。那就是使用VS的Memory查看器,我們將字串頭地址傳給Memory查看器,Memory查看器會自動將能顯示的ascii字元顯示出來。這樣能勉強能滿足你的願望。

但是,如果你的CEGUI::String對象,儲存的是中文,那麼沒有任何簡單的方法能讓你再次看到其字元含義。要想讓其格式化顯示中文,我們必須給vs調試器寫一個小外掛程式(聽著外掛程式,似乎很麻煩,但實際上很簡單,主要就牽扯到幾個函數)。以下是具體的步驟:

  1. vs調試器給了我們一個介面,可以為每個類型提供一個格式化其顯示資訊的機會。這個介面就是:
    HRESULT WINAPI CustomViewer(   DWORD dwAddress,       // low 32-bits of address   DEBUGHELPER *pHelper,  // callback pointer to access helper functions   int nBase,             // decimal or hex   BOOL bIgnore,          // not used   char *pResult,         // where the result needs to go   size_t max,            // how large the above buffer is   DWORD dwReserved       // always pass zero)

    只要函數類型符合就可以,函數名字隨便。只要我們完成這個函數,然後調試器每次顯示你的資料類型的對象的時候,就會調用這個介面,你所需要做的就是將想要顯示的資訊填充到pResult所指向的字元緩衝區中。這是我們的中心思想,但是為了完成這個任務,我們有不少困難需要克服。後面會一一列舉。

    其中DEBUGHELPER定義如下:

    typedef struct tagDEBUGHELPER{    DWORD dwVersion;    HRESULT (WINAPI *ReadDebuggeeMemory)(     struct tagDEBUGHELPER *pThis, //DEBUGHELPER pointer        DWORD dwAddr,//the address of object you want to show formatted prompt information        DWORD nWant, //the object size in byte.        VOID* pWhere, //the dest buffer for storing the object        DWORD *nGot );//number bytes are transferred.    // from here only when dwVersion >= 0x20000    DWORDLONG (WINAPI *GetRealAddress)( struct tagDEBUGHELPER *pThis );    //use for 64-bit system.    HRESULT (WINAPI *ReadDebuggeeMemoryEx)( struct tagDEBUGHELPER *pThis, DWORDLONG qwAddr,        DWORD nWant, VOID* pWhere, DWORD *nGot );    int (WINAPI *GetProcessorType)( struct tagDEBUGHELPER *pThis );} DEBUGHELPER;

    重要的函數我已經提供的注釋。需要注意的是,這個類型並不存在於window.h中,我們需要手動添加其聲明。我們將只用到ReadBuggeeMemory()函數。

  2. 現在我們開始真正去完成外掛程式,首先要做的是建立一個dll工程,這個dll工程將會是我們的外掛程式。
    1. 【開啟vs】-》【建立工程】-》【選擇win32程式】-》【建立時選擇空的dll工程】
    2. 建立一個main.cpp。然後把下面代碼粘貼上!
      // CEGUIDbg.cpp : Defines the exported functions for the DLL application.//#include "stdafx.h"#include <Windows.h>#include "tchar.h"#include <string>#include <sstream>#include <vector>#include "ceguistring.h"#define ADDIN_API    __declspec(dllexport)typedef struct tagDEBUGHELPER{    DWORD dwVersion;    HRESULT (WINAPI *ReadDebuggeeMemory)(     struct tagDEBUGHELPER *pThis, //DEBUGHELPER pointer        DWORD dwAddr,//the address of object you want to show formatted prompt information        DWORD nWant, //the object size in byte.        VOID* pWhere, //the dest buffer for storing the object        DWORD *nGot );//number bytes are transferred.    // from here only when dwVersion >= 0x20000    DWORDLONG (WINAPI *GetRealAddress)( struct tagDEBUGHELPER *pThis );    //use for 64-bit system.    HRESULT (WINAPI *ReadDebuggeeMemoryEx)( struct tagDEBUGHELPER *pThis, DWORDLONG qwAddr,        DWORD nWant, VOID* pWhere, DWORD *nGot );    int (WINAPI *GetProcessorType)( struct tagDEBUGHELPER *pThis );} DEBUGHELPER;// 多位元組編碼轉為UTF8編碼  bool MultiByteToUtf8( char* pszDestUtf8, int iDestUtf8Size, const char* pszMultiByte, int iMultiByteSize = -1 )  {      if( NULL == pszDestUtf8 || NULL == pszMultiByte )    {        return false;    }    // convert an MBCS string to widechar       int iWideCharSize = MultiByteToWideChar( CP_ACP, 0, pszMultiByte, iMultiByteSize, NULL, 0 );      std::vector< WCHAR > vctWideChar( iWideCharSize );    int iNumWritten = MultiByteToWideChar( CP_ACP, 0, pszMultiByte, iMultiByteSize, &vctWideChar.front(), iWideCharSize );      if( iNumWritten != iWideCharSize )      {          return false;      }      // convert an widechar string to utf8      int iUtf8Size = WideCharToMultiByte(CP_UTF8, 0, &vctWideChar.front(), -1, NULL, 0, NULL, NULL);      if ( iUtf8Size <= 0)      {          return false;      }          if( iUtf8Size > iDestUtf8Size )    {        iUtf8Size = iDestUtf8Size;    }    iNumWritten = WideCharToMultiByte( CP_UTF8, 0, &vctWideChar.front(), -1, pszDestUtf8, iUtf8Size, NULL, NULL );      if ( iNumWritten != iUtf8Size )      {          return false;      }      return true;  } ADDIN_API HRESULT WINAPI CEGUIDbg_String(DWORD dwAddress, DEBUGHELPER *pHelper,                                         int nBase, BOOL bUniStrings, char *pResult,                                         size_t max, DWORD reserved ){    CEGUI::String strDebug;    DWORD nGot;    //get CEGUI::String data member.    if (pHelper->ReadDebuggeeMemory(pHelper,dwAddress,sizeof( strDebug),&strDebug,&nGot) != S_OK)    {        return E_FAIL;    }    if( nGot != sizeof( strDebug ) )    {        return E_FAIL;    }    const CEGUI::utf32* pszUtf32 = strDebug.ptr();    int iLength = strDebug.length();    std::vector< CEGUI::utf32 > vctBuffer;    //if the string data is stored in a memory allocated by new(), we have to copy the data to out memory block.    if( iLength > STR_QUICKBUFF_SIZE )    {        vctBuffer.resize( iLength );        if( S_OK != pHelper->ReadDebuggeeMemory( pHelper, ( DWORD )pszUtf32, iLength * sizeof( CEGUI::utf32 ), &vctBuffer.front(), &nGot ) )        {            return E_FAIL;        }        if( nGot != vctBuffer.size() * sizeof( CEGUI::utf32 ) )        {            return E_FAIL;        }        pszUtf32 = &vctBuffer.front();    }    //get ascii character.    //although the data pointer is utf32*, but the data isn't encoded by utf32 if you pass const char* to CEGUI::String constructor. In contrary, it only store each ascii character     //in a utf32-type element.    int iSize = iLength + 1;    if( iSize > max )    {        iSize = max;        iLength = iSize - 1;    }    std::vector< char > vctAscii( iSize );        for( int i = 0; i < iLength; ++i )    {        vctAscii[ i ] = ( char )( unsigned char )pszUtf32[ i ];    }    vctAscii[ iLength ] = 0;    //convert ascii character set to utf8 character set.    //Because debugger accepts utf8 character set.     //If you pass ascii string to pResult, chinese character can't be shown.     if( false == MultiByteToUtf8( pResult, max, &vctAscii.front() ) )    {        return E_FAIL;    }    //set all data to 0, then CEGUI::String::~String won't delete anything should't be deleted.    memset( &strDebug, 0, sizeof( strDebug ) );    return S_OK;}

      可以看到我們需要包含“CEGUIString.h"這樣的標頭檔,我們的做法是直接拷貝CEGUIString.h,CEGUIString.cpp到工程來,因為我們需要CEGUI::String這個類的聲明和實現(因為我們需要對這種類型進行一些解析操作)。
      但是CEGUI::String.h包含了CEGUIBase.h。所以我們需要添加CEGUI標頭檔的搜尋目錄。做法是【項目屬性】-》【C/C++】-》【General】-》【Addtional include direstories】,向其中添加CEGUI SDK中的CEGUI/Include檔案路徑。
      同時為了能夠靜態編譯CEGUIString.h,CEGUIString.cpp,我們在【C/C++】-》【Preprocessor】中添加CEGUI_STATIC宏,這表明使用靜態庫形式編譯CEGUI。

    3. 這樣我們就完成了外掛程式的編寫(具體外掛程式裡面怎麼個原理一會再講)。然後我們將編譯出來的dll放到devenv.exe所在的目錄下,我這是【D:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE】,你的很有可能是在c盤。這是vs調試器搜尋外掛程式的目錄(我猜的)。
      然後我們需要改一個檔案,叫做【autoexp.dat】,它在【D:\Program Files\Microsoft Visual Studio 9.0\Common7\Packages\Debugger】,這個是調試器啟動是讀取的關於自動延伸類型的檔案。我們開啟檔案在其【[AutoExpand]】的欄位後添加如下語句:
      [AutoExpand]CEGUI::String=$ADDIN(ceguidbg.dll,?CEGUIDbg_String@@YGJKPAUtagDEBUGHELPER@@HHPADIK@Z)

      CEGUI::String是需要格式化顯示資訊的資料類型,其中$ADDIN()是:

      ; $ADDIN allows external DLLs to be added to display even more complex; types via the EE Add-in API. The first argument is the DLL name, the; second argument is the name of the export from the DLL to use. For; further information on this API see the sample called EEAddIn.

      ceguidbg.dll是外掛程式名稱,?CEGUIDbg_String@@YGJKPAUtagDEBUGHELPER@@HHPADIK@Z是dll中匯出函數經過名稱修飾的函數名稱。可以通過dumpbin /exports ceguibdg.dll查看匯出函數名。通過添加這一行,調試器才知道碰到CEGUI::String這種類型的對象,去調用ceguidbg.dll中的對應函數,然後將此函數返回的pResult顯示出來。

  3. 好了,我們外掛程式做完了,我們重新調試便能看到結果。只需要重新調試即能看到新的格式化後的提示資訊,如需重啟vs。

    這是在CEGUI 0.7.9的版本中,實現的效果。

  4. main.cpp中的原理講解:
    1. 首先我們拷貝出了需要顯示提示資訊的對象的資料。
          CEGUI::String strDebug;    DWORD nGot;    //get CEGUI::String data member.    if (pHelper->ReadDebuggeeMemory(pHelper,dwAddress,sizeof( strDebug),&strDebug,&nGot) != S_OK)    {        return E_FAIL;    }    if( nGot != sizeof( strDebug ) )    {        return E_FAIL;    }
    2. 然後我們判斷,CEGUI::String對象是否動態分配了一塊字元緩衝區,因為我們的dll(外掛程式)是在調試器進程中的,所以我們不能訪問其他程式動態申請的記憶體,因為每個進程都有自己的虛擬記憶體地址空間。你訪問的成員變數所指向的記憶體在你的進程中根本就沒有分配。所以我們需要自己建立一塊記憶體,通過ReadDebuggeeMemory函數讀取。即用ReadDebuggeeMemory讀取時可以的,這是系統保證的。由於我不太喜歡處理動態記憶體申請這類的問題,我使用了vector來協助了我(確實有點難看)。
          const CEGUI::utf32* pszUtf32 = strDebug.ptr();    int iLength = strDebug.length();    std::vector< CEGUI::utf32 > vctBuffer;    //if the string data is stored in a memory allocated by new(), we have to copy the data to out memory block.    if( iLength > STR_QUICKBUFF_SIZE )    {        vctBuffer.resize( iLength );        if( S_OK != pHelper->ReadDebuggeeMemory( pHelper, ( DWORD )pszUtf32, iLength * sizeof( CEGUI::utf32 ), &vctBuffer.front(), &nGot ) )        {            return E_FAIL;        }        if( nGot != vctBuffer.size() * sizeof( CEGUI::utf32 ) )        {            return E_FAIL;        }        pszUtf32 = &vctBuffer.front();    }
    3. 再然後,我們將假utf32編碼格式,轉換成多位元組編碼。
          //get ascii character.    //although the data pointer is utf32*, but the data isn't encoded by utf32 if you pass const char* to CEGUI::String constructor. In contrary, it only store each ascii character     //in a utf32-type element.    int iSize = iLength + 1;    if( iSize > max )    {        iSize = max;        iLength = iSize - 1;    }    std::vector< char > vctAscii( iSize );        for( int i = 0; i < iLength; ++i )    {        vctAscii[ i ] = ( char )( unsigned char )pszUtf32[ i ];    }    vctAscii[ iLength ] = 0;

      可以看到,我直接將32位的utf32編碼給了char變數。所以我基於這樣的前提,程式中我們都使用CEGUI::String::String( const char* )建構函式構造,而不使用CEGUI::String::String( const utf8* ),因為要使用後者,我們還需要將我們字串轉換成utf8編碼格式,才能讓CEGUI::String正常工作。所以一般人都會使用前者,也是最常見的構造方法。

    4. 然後最重要的,也是我耗費一下午時間才找到的解決方案。網上的例子都是老外,老外都用英文,ASCII字元就夠了,所以直接將多位元組編碼的字串給pResult。結果我發現,多位元組編碼的漢子是無法顯示的,調試器根本不識別,而且從網上找各種例子,搜集資料也沒找到解決方案。偶然情況下,我想是不是調試器識別Unicode編碼啊,於是將多位元組編碼轉換成utf8編碼,果真成功了!真是皇天不負有心人啊,耗了我好多精力啊!
      //convert ascii character set to utf8 character set.    //Because debugger accepts utf8 character set.     //If you pass ascii string to pResult, chinese character can't be shown.     if( false == MultiByteToUtf8( pResult, max, &vctAscii.front() ) )    {        return E_FAIL;    }

      具體如何轉換成,直接看MultiByteToUtf8的函數實現,裡面不懂的函數直接看msdn就可以了。

    5. 最後一個非常重要的地方:
      //set all data to 0, then CEGUI::String::~String won't delete anything should't be deleted.    memset( &strDebug, 0, sizeof( strDebug ) );

      既然你聲明了一個該類型的對象,並填充了其中的資料成員,那麼這個對象析構的時候必然會走解構函式,而解構函式一定會將申請的記憶體釋放。但此時的對象內的指標都是非空且無效的,那麼析構的時候一定會出問題。而且CEGUI::String類型不提供清空方法,我們只能來硬的了。幸虧對象有沒有虛函數表以及多重繼承的問題,否則很難搞。

終於搞定了,如果還有不明白的地方,請留言!

不想自己動手寫的,可以直接下載我上傳到csdn的資源:http://download.csdn.net/detail/xujiezhige/5740411

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.