返回BSTR**的COM服务枚举函数调用异常,非COM环境运行正常
问题根源与修复方案
核心问题:违反COM内存分配规范
COM要求跨组件传递的输出内存必须使用CoTaskMemAlloc/CoTaskMemFree完成分配与释放,你的代码中用malloc分配了输出的BSTR数组:
BSTR* pServiceNames = (BSTR*)malloc(*dwServicesReturned * sizeof(BSTR));
当客户端与COM对象不在同一模块(或使用不同堆空间)时,malloc分配的内存无法被客户端正确访问或释放,直接触发内存访问崩溃。
修复步骤
1. 修改COM端的内存分配方式
将输出数组的malloc替换为CoTaskMemAlloc,确保符合COM内存规则:
// 原代码 // BSTR* pServiceNames = (BSTR*)malloc(*dwServicesReturned * sizeof(BSTR)); // 替换为 BSTR* pServiceNames = (BSTR*)CoTaskMemAlloc(*dwServicesReturned * sizeof(BSTR)); if (!pServiceNames) { free(lpServices); CloseServiceHandle(hSCManager); return E_OUTOFMEMORY; }
2. 修正客户端初始化代码
客户端无需提前分配内存,GetServices会直接为pServiceNames赋值,初始化为NULL即可:
// 原代码 // BSTR* pServiceNames = (BSTR*)CoTaskMemAlloc(sizeof(BSTR));; // 替换为 BSTR* pServiceNames = NULL; DWORD dwServicesReturned = 0; hr = pIService->GetServices(&pServiceNames, &dwServicesReturned);
访问完成后,需正确释放资源:
for (DWORD i = 0; i < dwServicesReturned; i++) { SysFreeString(pServiceNames[i]); } CoTaskMemFree(pServiceNames);
3. 补充错误处理逻辑
增加SysAllocString的失败检查,避免内存泄漏:
for (DWORD i = 0; i < *dwServicesReturned; i++) { pServiceNames[i] = SysAllocString(lpServices[i].lpServiceName); if (!pServiceNames[i]) { // 释放已分配的BSTR for (DWORD j = 0; j < i; j++) { SysFreeString(pServiceNames[j]); } CoTaskMemFree(pServiceNames); free(lpServices); CloseServiceHandle(hSCManager); return E_OUTOFMEMORY; } }
优化方向
1. 用ATL工具类简化内存管理
使用ATL的CHandle、CAtlArray、CComBSTR等智能类,减少手动内存管理的出错概率:
STDMETHODIMP CServiceHandler::GetServices(BSTR** pOut, LPDWORD dwServicesReturned) { if (!pOut || !dwServicesReturned) return E_INVALIDARG; *pOut = NULL; *dwServicesReturned = 0; CHandle hSCManager(OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE)); if (!hSCManager) return HRESULT_FROM_WIN32(GetLastError()); DWORD dwBytesNeeded = 0; DWORD dwResumeHandle = 0; if (!EnumServicesStatus(hSCManager, SERVICE_WIN32, SERVICE_STATE_ALL, NULL, 0, &dwBytesNeeded, dwServicesReturned, &dwResumeHandle)) { if (GetLastError() != ERROR_MORE_DATA) return HRESULT_FROM_WIN32(GetLastError()); } CAtlArray<ENUM_SERVICE_STATUS> services; if (!services.SetCount(dwBytesNeeded / sizeof(ENUM_SERVICE_STATUS))) return E_OUTOFMEMORY; if (!EnumServicesStatus(hSCManager, SERVICE_WIN32, SERVICE_STATE_ALL, services.GetData(), dwBytesNeeded, &dwBytesNeeded, dwServicesReturned, &dwResumeHandle)) return HRESULT_FROM_WIN32(GetLastError()); CAtlArray<CComBSTR> serviceNames; if (!serviceNames.SetCount(*dwServicesReturned)) return E_OUTOFMEMORY; for (DWORD i = 0; i < *dwServicesReturned; i++) { serviceNames[i] = services[i].lpServiceName; if (!serviceNames[i]) return E_OUTOFMEMORY; } BSTR* pArray = (BSTR*)CoTaskMemAlloc(*dwServicesReturned * sizeof(BSTR)); if (!pArray) return E_OUTOFMEMORY; for (DWORD i = 0; i < *dwServicesReturned; i++) { pArray[i] = serviceNames[i].Detach(); // 转移BSTR所有权 } *pOut = pArray; return S_OK; }
2. 增加参数合法性检查
在函数开头校验输入参数,避免空指针访问:
if (!pOut || !dwServicesReturned) { return E_INVALIDARG; } // 初始化输出参数,避免客户端未初始化导致的异常 *pOut = NULL; *dwServicesReturned = 0;
3. 升级API函数
使用EnumServicesStatusEx替代EnumServicesStatus,它支持更多服务类型与详细信息,兼容性更强。
内容的提问来源于stack exchange,提问作者user20528109
相关产品推荐
相关产品推荐

