这里讲解的是针对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*)的构造函数有区别。
- 前者(const char*)会直接将传入的字符串,逐一地,原封不动的,放到utf32(即unsigned int)缓冲区中。也就是一个简单的容量扩充操作。这对于ASCII字符集中的字符时没有问题的,因为utf32编码的ASCII字符集,与原来的ASCII码的值在数值上是相等的。但是如果是非ASCII字符集的字符,采用这种方式得到的将是一个错误的utf32编码。
- 但是如果传入是const utf8*,那么构造函数将会将此传入的缓冲区,看待成utf8编码的字符缓冲区,并进行utf8转到到utf32的编码操作。
很多时候我们想通过CEGUI::String::c_str()函数,让CEGUI::String返回c风格字符串,但是我要告诉你,CEGUI::String::c_str()是个文不达意的函数,其真正功能是将保存的utf32字符串转换成utf8编码的字符串。这对于ascii字符集中的字符没有什么问题,但是对非ASCII字符集的字符,你调用CEGUI::String::c_str()将会返回乱码。
- 假如你有以下代码:
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调试器写一个小插件(听着插件,似乎很麻烦,但实际上很简单,主要就牵扯到几个函数)。以下是具体的步骤:
- 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()函数。 - 现在我们开始真正去完成插件,首先要做的是创建一个dll工程,这个dll工程将会是我们的插件。
- 【打开vs】-》【创建工程】-》【选择win32程序】-》【创建时选择空的dll工程】
- 创建一个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。 - 这样我们就完成了插件的编写(具体插件里面怎么个原理一会再讲)。然后我们将编译出来的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显示出来。
- 好了,我们插件做完了,我们重新调试便能看到结果。只需要重新调试即能看到新的格式化后的提示信息,如需重启vs。
这是在CEGUI 0.7.9的版本中,实现的效果。 - main.cpp中的原理讲解:
- 首先我们拷贝出了需要显示提示信息的对象的数据。
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; }
- 然后我们判断,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(); }
- 再然后,我们将假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正常工作。所以一般人都会使用前者,也是最常见的构造方法。 - 然后最重要的,也是我耗费一下午时间才找到的解决方案。网上的例子都是老外,老外都用英文,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就可以了。 - 最后一个非常重要的地方:
//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