_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; }