Marshalling a VARIANT back from COM to C++ client

2019-08-06 13:05发布

问题:

I'm attempting to marshal a safearray of BSTRs from a COM object, back to a C++ client application.

My IDL definition of the function involved:

[id(5), helpstring("method GetStreams")] 
    HRESULT GetStreams( [out,retval] VARIANT* pvarStreamNames );

Here's my implementation of the GetStreams() function:

STDMETHODIMP CArchiveManager::GetStreams(VARIANT* pvarStreamNames)
{   
CComSafeArray<BSTR, VT_BSTR>    saStreamNames;
CComVariant                     varOutNames;

Stream* pNext       = NULL;
int     nNumStreams = m_Streams.NumStreams();

if( nNumStreams == 0 )
    return S_OK;

for(int x = 0; x < nNumStreams; x++)
{
    pNext = m_Streams.GetAt(x); 
    if( pNext )             
        saStreamNames.Add( pNext->StreamName() );   
}

if( saStreamNames.GetCount() > 0 )
{
    varOutNames.vt      = VT_ARRAY;
    varOutNames.parray  = saStreamNames.m_psa;

    varOutNames.Detach(pvarStreamNames);
}

return S_OK;
}

Here's how the C++ client program calls the GetStreams() function:

VARIANT varStreamNames;
hr = spArchiveMgr->GetStreams( &varStreamNames );

I trace through the program using the interactive debugger and everything appears to work correctly (safearray populates correctly, etc) until the GetStreams() function returns. At that point, I get an "unhandled exception reading location" message.

advice on how to debug/solve this?

回答1:

There're two problems. The first one is

 VARIANT varStreamNames;

is unitialized, so when

varOutNames.Detach(pvarStreamNames);

runs it calls VariantClear() on an uninitialized variable and this leads to undefined behavior - your program crashes.

You have to call VariantInit() on varStreamNames before invoking the COM method or just use CComVariant type for varStreamNames.

The second one is:

CComSafeArray<BSTR, VT_BSTR>    saStreamNames;
CComVariant                     varOutNames;

varOutNames.vt      = VT_ARRAY;
varOutNames.parray  = saStreamNames.m_psa;

perfoms shallow copy of the safe array - now both saStreamNames and varOutNames own the safe array and so when saStreamNames gets destroyed at the end of the scope the safe array is released.

Since you've copied the same safe array address into pvarStreamNames you've now got a variant with a dangling safe array pointer. Trying to access that safe array is undefined behavior. You should use Detach() method of CComSafeArray to release ownership.