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

5
从书籍《ATL Internals》中,我了解到BSTR与OLECHAR*不同,并且有CComBSTR和CString可用于BSTR。
根据MSDN的为BSTR分配和释放内存,我知道调用者/被调用者需要负责内存管理。
以MSDN上的这行代码为例: HRESULT CMyWebBrowser::put_StatusText(BSTR bstr) 我仍然不知道如何在我的实现中正确处理bstr。由于我对BSTR仍有一个基本问题——在COM接口边界上,我们应该将bstr视为值(例如int)还是引用(例如int *)。
我想尽快将BSTR转换为CString/CComBSTR。对于转换来说,值语义和引用语义是完全不同的情况。我已经深入研究了CComBSTR.Attach、CComBSTR.AssignBSTR等函数,但这些代码都不能解决我的困惑。
MSDN上的CComBSTR.Attach代码片段让我感觉不对,因为它没有遵循为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 ???
}
2个回答

12

CComBSTR只是一个RAII包装器,用于封装原始的BSTR。因此,可以放心地使用CComBSTR代替原始的BSTR来编写异常安全的代码,使其更难泄漏资源(即原始的BSTR),等等。

如果BSTR是一个输入参数,它就像一个带有长度前缀和可能包含一些NUL字符的const wchar_t*。如果BSTR内部没有嵌入NUL,则可以将其直接传递给CString构造函数,这将对其进行深度复制,并且可以在本地使用CString。对该CString的修改不会影响原始的BSTR。也可以使用std::wstring(注意,std::wstring也可以处理嵌入的NUL)。

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类中),并使用std::wstringCString进行本地工作。
作为“额外阅读”,请考虑Eric Lippert's guide to BSTR semantics

我的一个重要问题是:我们应该复制还是引用bstr?小问题是如何使用CString引用bstr?如何复制带有嵌入NULL的bstr? - Raymond
你说的复制或引用是什么意思?如果它是一个输入参数,通过值传递 BSTR,否则通过指针传递 BSTR*。如果你有嵌入的 NUL,你可以将它复制到 std::wstring 中,而不是 CString - Mr.C64
也许你从错误的角度观察了这个问题... 把 BSTR 看作是使用特殊 COM 分配器分配的 const wchar_t*,并且带有长度前缀(因此它可以嵌入 NUL)。 - Mr.C64

4

如果你有一个 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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接