_com_util::ConvertStringToBSTR 和 _com_util::ConvertBSTRToString 中的错误





5.00/5 (16投票s)
2002 年 3 月 16 日
2分钟阅读

347507

842
MS 如何实现它们以及为什么他们不想让你看到这些函数的源代码。
引言
你是否曾经想过为什么在使用 _bstr_t 和 _variant_t COM 实用程序类时会收到堆栈溢出异常? 如果你从未遇到过这个问题,请尝试以下代码
#include "stdafx.h" #include <stdio.h> #include <eh.h> #include <comdef.h> #define BUG_ARR_MAX 2000000 class Exception { private: unsigned int m_n; public: Exception( unsigned int n ) : m_n( n ) {} ~Exception() {} unsigned int GetNumber() { return m_n; } }; void trans_func( unsigned int, EXCEPTION_POINTERS* pe) { throw Exception(pe->ExceptionRecord->ExceptionCode); }; int main(int argc, char* argv[]) { char *szBuf = new char[BUG_ARR_MAX]; memset(szBuf, 'X', BUG_ARR_MAX); szBuf[BUG_ARR_MAX - 1] = '\0'; //set translation function to //translate structured exceptions _set_se_translator( trans_func ); try { _bstr_t bstrString(szBuf); }catch(Exception e) { if(EXCEPTION_STACK_OVERFLOW == e.GetNumber()) printf("Stack overflow exception!\n"); else printf("Exception: %d\n",e.GetNumber()); } _bstr_t bstrString("Some string"); char *szResult = (char*)bstrString; return 0; }
我在这里做的是设置异常处理程序来处理结构化异常。为什么在为 bstrString 调用构造函数时会引发堆栈溢出异常? 让我们进入 _bstr_t 实现,我们会看到 MS 在构造函数中使用了 _com_util::ConvertStringToBSTR(s) 函数,并且该函数引发了异常。
inline _bstr_t::Data_t::Data_t(const char* s) throw(_com_error) : m_str(NULL), m_RefCount(1) { m_wstr = _com_util::ConvertStringToBSTR(s); if (m_wstr == NULL && s != NULL) { _com_issue_error(E_OUTOFMEMORY); } }
为了调查这里的问题,让我们进入该函数的汇编代码
_com_util::ConvertStringToBSTR:
0040F9D8   push        ebp
0040F9D9   mov         ebp,esp
0040F9DB   cmp         dword ptr [ebp+8],0
0040F9DF   push        esi
0040F9E0   push        edi
0040F9E1   jne         _com_util::ConvertStringToBSTR+0Fh (0040f9e7)
0040F9E3   xor         eax,eax
0040F9E5   jmp         _com_util::ConvertStringToBSTR+6Ch (0040fa44)
0040F9E7   push        dword ptr [pSrc]
0040F9EA   call        dword ptr [__imp__lstrlenA@4 (0042e28c)]
0040F9F0   mov         esi,eax
0040F9F2   inc         esi
0040F9F3   lea         eax,[esi+esi]
0040F9F6   add         eax,3
0040F9F9   and         al,0FCh
// this is the call to _alloca_probe function.
// What is the reason to use stack??
// try to get the memory from
// the stack! not SysAllocString()!
// but at the end they call SysAllocString()
// to reallocate the memory!!!
//exception is here, which is never caught
0040F9FB   call        $$$00001 (0040b980)
0040FA00   mov         edi,esp
0040FA02   push        esi
0040FA03   push        edi
0040FA04   push        0FFh
0040FA06   push        dword ptr [pSrc]
0040FA09   and         word ptr [edi],0
0040FA0D   push        0
0040FA0F   push        0
0040FA11   call        dword ptr [__imp__MultiByteToWideChar@24 (0042e24c)]
0040FA17   test        eax,eax
0040FA19   jne         _com_util::ConvertStringToBSTR+65h (0040fa3d)
0040FA1B   mov         esi,dword ptr [__imp__GetLastError@0 (0042e270)]
0040FA21   call        esi
0040FA23   test        eax,eax
0040FA25   je          _com_util::ConvertStringToBSTR+5Dh (0040fa35)
0040FA27   call        esi
0040FA29   and         eax,0FFFFh
0040FA2E   or          eax,80070000h
0040FA33   jmp         _com_util::ConvertStringToBSTR+5Fh (0040fa37)
0040FA35   xor         eax,eax
0040FA37   push        eax
0040FA38   call        _com_issue_error (0040f2e2)
0040FA3D   push        edi
0040FA3E   call        dword ptr [__imp__SysAllocString@4 (0042e320)]
0040FA44   lea         esp,[ebp-8]
0040FA47   pop         edi
0040FA48   pop         esi
0040FA49   pop         ebp
0040FA4A   ret         4
现在我们可以看到该函数内部发生了什么。
- 获取 char 字符串的长度。
- 使用 alloca()函数在堆栈上分配内存。 如果char字符串对于堆栈来说太长,则这里会失败。 并且 MS 编码人员并不关心堆栈上是否有足够的内存。 最后,为什么完全要在这里使用堆栈?
- 字符串使用 MultiByteToWideChar()进行转换
- 如果转换失败,则引发 COM 异常。
- 现在调用 SysAllocString()。
我希望 MS 编码人员不会将该函数用于他们的产品开发,否则根据这个 _com_util::ConvertStringToBSTR() 实现,我们可以让他们的几乎所有产品崩溃。
现在让我们谈谈 _com_util::ConvertBSTRToString()。 尝试将 _bstr_t 对象强制转换为 char 指针
_bstr_t bstrString("Some string"); char *szResult = (char*)bstrString;
将调用此函数
inline const char* _bstr_t::Data_t::GetString() const throw(_com_error) { if (m_str == NULL) { m_str = _com_util::ConvertBSTRToString(m_wstr); if (m_str == NULL && m_wstr != NULL) { _com_issue_error(E_OUTOFMEMORY); } } return m_str; }
正如你所看到的,在此代码中,MS 编码人员使用 _com_util::ConvertBSTRToString() 将 char 字符串转换为 BSTR 字符串。 另一个“魔鬼”。 至少该函数不使用堆栈。 但在内部,它分配的内存是我们需要的两倍。 这是该函数的汇编代码
_com_util::ConvertBSTRToString:
0040FA4D   push        ebx
0040FA4E   push        ebp
0040FA4F   mov         ebp,dword ptr [esp+0Ch]
0040FA53   xor         ebx,ebx
0040FA55   cmp         ebp,ebx
0040FA57   jne         _com_util::ConvertBSTRToString+10h (0040fa5d)
0040FA59   xor         eax,eax
0040FA5B   jmp         _com_util::ConvertBSTRToString+6Fh (0040fabc)
0040FA5D   push        esi
0040FA5E   push        edi
0040FA5F   push        ebp
0040FA60   call        wcslen (0040fe80)
// allocate size of the memory as much
// as twice of what we need !!
// What is the reason ??
// wcslen returned number of characters in eax register
0040FA65   lea         edi,[eax+eax+2]
0040FA69   push        edi
0040FA6A   call        operator new (00401fb0)
0040FA6F   mov         esi,eax
0040FA71   pop         ecx
0040FA72   cmp         esi,ebx
0040FA74   pop         ecx
0040FA75   jne         _com_util::ConvertBSTRToString+34h (0040fa81)
0040FA77   push        8007000Eh
0040FA7C   call        _com_issue_error (0040f2e2)
0040FA81   push        ebx
0040FA82   push        ebx
0040FA83   push        edi
0040FA84   push        esi
0040FA85   push        0FFh
0040FA87   push        ebp
0040FA88   push        ebx
0040FA89   push        ebx
0040FA8A   mov         byte ptr [esi],bl
0040FA8C   call        dword ptr [__imp__WideCharToMultiByte@32 (0042e234)]
0040FA92   test        eax,eax
0040FA94   jne         _com_util::ConvertBSTRToString+6Bh (0040fab8)
0040FA96   mov         edi,dword ptr [__imp__GetLastError@0 (0042e270)]
0040FA9C   call        edi
0040FA9E   test        eax,eax
0040FAA0   je          _com_util::ConvertBSTRToString+63h (0040fab0)
0040FAA2   call        edi
0040FAA4   and         eax,0FFFFh
0040FAA9   or          eax,80070000h
0040FAAE   jmp         _com_util::ConvertBSTRToString+65h (0040fab2)
0040FAB0   xor         eax,eax
0040FAB2   push        eax
0040FAB3   call        _com_issue_error (0040f2e2)
0040FAB8   mov         eax,esi
0040FABA   pop         edi
0040FABB   pop         esi
0040FABC   pop         ebp
0040FABD   pop         ebx
0040FABE   ret         4
从这个汇编清单中,我们可以看到 MS 编码人员并不关心内存。 他们遵循奇怪的规则:“分配的内存是我们需要的两倍。”
这是我对该问题的修复。 我重新实现了这两个函数。 将所有 _com_util::ConvertBSTRToString 替换为 ConvertBSTRToString,并将 _com_util::ConvertStringToBSTR 替换为 ConvertStringToBSTR。 最后,你必须从生成菜单中“全部重新生成”来重建你的项目。
新转换函数的源代码
//implement our own conversion functions //------------------------// // Convert char * to BSTR // //------------------------// inline BSTR ConvertStringToBSTR(const char* pSrc) { if(!pSrc) return NULL; DWORD cwch; BSTR wsOut(NULL); if(cwch = ::MultiByteToWideChar(CP_ACP, 0, pSrc, -1, NULL, 0))//get size minus NULL terminator { cwch--; wsOut = ::SysAllocStringLen(NULL, cwch); if(wsOut) { if(!::MultiByteToWideChar(CP_ACP, 0, pSrc, -1, wsOut, cwch)) { if(ERROR_INSUFFICIENT_BUFFER == ::GetLastError()) return wsOut; ::SysFreeString(wsOut);//must clean up wsOut = NULL; } } }; return wsOut; }; //------------------------// // Convert BSTR to char * // //------------------------// inline char* ConvertBSTRToString(BSTR pSrc) { if(!pSrc) return NULL; //convert even embeded NULL DWORD cb,cwch = ::SysStringLen(pSrc); char *szOut = NULL; if(cb = ::WideCharToMultiByte(CP_ACP, 0, pSrc, cwch + 1, NULL, 0, 0, 0)) { szOut = new char[cb]; if(szOut) { szOut[cb - 1] = '\0'; if(!::WideCharToMultiByte(CP_ACP, 0, pSrc, cwch + 1, szOut, cb, 0, 0)) { delete []szOut;//clean up if failed; szOut = NULL; } } } return szOut; };
为了你的方便,我将所有这些更改反映在comutil.h 头文件中,该文件已在此处发布。
执行速度测试
这是我的测试程序的结果。 该程序在循环中运行这些函数 100000 次并打印出结果。 为了获得正确的结果,你必须独立运行每个测试。
#include "stdio.h" #include "comdef.h" int main(int argc, char* argv[]) { char *szIn = "This is the test string to convert to BSTR"; BSTR wsOut = ::_com_util_fix::ConvertStringToBSTR(szIn); int i,imax = 100000; DWORD dwTime; switch((int)*argv[1]) { case '0': dwTime = ::GetTickCount(); for(i = 0 ; i < imax; ++i) { wsOut = _com_util::ConvertStringToBSTR(szIn); } dwTime = ::GetTickCount() - dwTime; printf("%d times of _com_util::ConvertStringToBSTR - %d msec\n", imax, dwTime); break; case '1': dwTime = ::GetTickCount(); for(i = 0 ; i < imax; ++i) { wsOut = _com_util_fix::ConvertStringToBSTR(szIn); } dwTime = ::GetTickCount() - dwTime; printf("%d times of /*fixed*/ ConvertStringToBSTR - %d msec\n", imax, dwTime); break; case '2': dwTime = ::GetTickCount(); for(i = 0 ; i < imax; ++i) { szIn = ::_com_util::ConvertBSTRToString(wsOut); } dwTime = ::GetTickCount() - dwTime; printf("%d times of _com_util::ConvertBSTRToString - %d msec\n", imax, dwTime); break; case '3': dwTime = ::GetTickCount(); for(i = 0 ; i < imax; ++i) { szIn = _com_util_fix::ConvertBSTRToString(wsOut); } dwTime = ::GetTickCount() - dwTime; printf("%d times of /*fixed*/ ConvertBSTRToString - %d msec\n", imax, dwTime); break; default: printf("command: temp.exe num\n\n\tnum == 0 - tests _com_util::ConvertStringToBSTR\n\ \r\tnum == 1 - tests /*fixed*/ ConvertStringToBSTR\n\ \r\tnum == 2 - tests _com_util::ConvertBSTRToString\n\ \r\tnum == 3 - tests /*fixed*/ ConvertBSTRToString\n\n Example: temp.exe 2\n\n"); } return 0; }

