You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

返回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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 21:48:23