我们应该将 COM 中的 BSTR 类型视为值还是引用?

Posted

技术标签:

【中文标题】我们应该将 COM 中的 BSTR 类型视为值还是引用?【英文标题】:Shall we treat BSTR type in COM as value or reference? 【发布时间】:2013-03-04 16:27:52 【问题描述】:

ATL Internals一书中,我知道BSTR与OLECHAR*不同,BSTR有CComBSTR和CString。

根据 MSDN Allocating and Releasing Memory for a BSTR,我知道调用者/被调用者的内存管理责任。

从 MSDN 取这条线,

HRESULT CMyWebBrowser::put_StatusText(BSTR bstr)

我仍然不知道如何在我的实现中正确处理bstr。因为我还有一个关于 BSTR 的基本问题——我们应该将bstr 视为一个值(如 int)还是作为一个引用(如 int*),至少在 COM 接口边界上。

我想在我的实现中尽快将 BSTR 转换为 CString/CComBSTR。对于转换,值或引用语义将完全不同。我已经深入研究了 CComBSTR.Attach、CComBSTR.AssignBSTR 等。但代码无法解决我的疑问。

MSDN CComBSTR.Attach 有一些代码片段,我觉得这是错误的,因为它不服从Allocating and Releasing Memory for a BSTR。 ATL Internals 说 SetSysString 将“释放传入的原始 BSTR”,如果我使用它,它将违反 BSTR 参数约定,就像 CComBSTR.Attach 一样。

总而言之,我想在实现中使用 CString 来处理原始 BSTR,但不知道正确的方法......我在我的项目中编写了一些只是工作代码,但我总是感到紧张,因为我没有'不知道我说的对不对。

让我谈谈编码语言

HRESULT CMyWebBrowser::put_StatusText(BSTR bstr)

// What I do NOT know
CString str1;  // 1. copy bstr (with embeded NUL)
CString str2;  // 2. ref bstr

// What I know
CComBSTR cbstr1;
cbstr1.AssignBSTR(bstr); // 3. copy bstr 
CComBSTR cbstr2;
cbstr2.Attach(bstr); // 4. ref bstr, do not copy

// What I do NOT know
// Should we copy or ref bstr ???

【问题讨论】:

【参考方案1】:

CComBSTR 只是 raw BSTR 周围的 RAII wrapper。因此,请随意使用 CComBSTR 而不是原始的 BSTR 来帮助编写异常安全的代码,从而更难泄漏资源(即原始 BSTR)等。

如果BSTR 是一个输入 参数,它就像一个const wchar_t*(以长度为前缀,并且可能包含一些NULs L'\0' 字符)。如果BSTR 没有嵌入NULs,您可以简单地将它传递给CString 构造函数,这将对其进行深层复制,您可以在本地使用您的CString。对 CString 的修改将不会在原始 BSTR 上可见。您也可以使用 std::wstring (注意std::wstring 也可以处理嵌入的NULs)。

void DoSomething(BSTR bstrInput)

    std::wstring myString(bstrInput);
    // ... work with std::wstring (or CString...) inside here

相反,如果BSTR 是一个输出 参数,那么它会使用另一层间接传递,即BSTR*。在这种情况下,您可以在方法中使用CComBSTR::Detach() 来释放安全地包装到CComBSTR 中的BSTR,并将其所有权转移给调用者:

HRESULT DoSomething( BSTR* pbstrOut )

    // Check parameter pointer
    if (pbstrOut == nullptr)
        return E_POINTER;

    // Guard code with try-catch, since exceptions can't cross COM module boundaries.
    try
    
        std::wstring someString;
        // ... work with std::wstring (or CString...) inside here

        // Build a BSTR from the ordinary string     
        CComBSTR bstr(someString.c_str());

        // Return to caller ("move semantics", i.e. transfer ownership
        // from current CComBSTR to the caller)
        *pbstrOut = bstr.Detach();

        // All right
        return S_OK;
    
    catch(const std::exception& e)
    
        // Log exception message...
        return E_FAIL;
    
    catch(const CAtlException& e)
    
        return e; // implicit cast to HRESULT
    

基本上,这个想法是使用BSTR(包装在类似CComBSTR 的RAII 类中)在边界处,并使用@ 进行本地工作987654348@或CString

作为“赏读”,请考虑Eric Lippert's guide to BSTR semantics

【讨论】:

我最大的问题是我们应该复制还是引用 bstr ?小问题是如何用 CString 引用 bstr ?如何复制带有嵌入 NULL 的 bstr? 复制或参考是什么意思?如果是输入参数,则按值传递BSTR,否则通过“按指针”传递BSTR*。如果您嵌入了NULs,您可以将其复制到std::wstring,而不是CString 也许你从错误的角度观察问题...想想BSTR 就像const wchar_t* 分配了一个特殊的 COM 分配器和长度前缀(所以它可以有嵌入NULs)。【参考方案2】:

输入有BSTR,您不负责释放它。转换为CString 很简单:

CString sValue(bstr);

或者,如果您希望在 MBCS 构建中保留 Unicode 字符:

CStringW sValue(bstr);

如果您需要在有[out] 参数时转换回来,您可以这样做(简单版):

VOID Foo(/*[out]*/ BSTR* psValue)

  CString sValue;
  *psValue = CComBSTR(sValue).Detach();

完整版是:

STDMETHODIMP Foo(/*[out]*/ BSTR* psValue)

    _ATLTRY
    
        ATLENSURE_THROW(psValue, E_POINTER); // Parameter validation
        *psValue = NULL; // We're responsible to initialize this no matter what
        CString sValue;
        // Doing our stuff to get the string value into variable
        *psValue = CComBSTR(sValue).Detach();
    
    _ATLCATCH(Exception)
    
        return Exception;
    
    return S_OK;

【讨论】:

以上是关于我们应该将 COM 中的 BSTR 类型视为值还是引用?的主要内容,如果未能解决你的问题,请参考以下文章

我应该将行业分类代码视为 K 均值聚类中的双重数据类型吗?

如何将_bstr_t转换为CString

ATL接口返回类型&&ATL接口返回字符串BSTR*

如何对 COM 中的方法进行返回类型?

_variant_t和_bstr_t

将 LPWST 转换或转换为 BSTR