Passing a reference to a COM object accross proces

2019-08-27 12:21发布

问题:

I have a reference to a COM object, and would like to pass that reference on to another process. So, I'd need some way to serialize information about this reference that would allow me to restore it again in the other process.

Is there any way to do this for COM objects of any arbitrary type?

I'm don't know too much about COM; from what I understand, there may be Monikers for some types of objects (for example, COM references such as Excel.Workbook might be restored via their .FullName property which seems to be used as the moniker), but so far I haven't found if something like this is possible for any kind of COM object

回答1:

Here's one way:

  1. In the server process, use CoMarshalInterface to marshal the interface to a stream. Convert bytes written to stream to a base-64 string (or any other encoding). Pass the encoded string to your client.
  2. In the client process, receive the string from your server. Decode the string into bytes. Wrap those bytes in a stream. Unmarshal from the stream.

Server side:

#include <windows.h>
#include <comdef.h>
#include <shlwapi.h>
#include <vector>
// link: crypt32.lib (for CryptBinaryToStringW)
// link: shlwapi.lib (for SHCreateMemStream)

/// <summary>
/// Gets a token that can be used to unmarshal an interface in another process.
/// </summary>
HRESULT GetInterfaceToken(LPUNKNOWN pUnk, REFIID riid, LPBSTR pbstrOut)
{
    // validate output parameters
    if (pbstrOut == nullptr)
        return E_POINTER;

    // set default values for output parameters
    *pbstrOut = nullptr;

    // validate input parameters
    if (pUnk == nullptr)
        return E_INVALIDARG;

    // create a stream
    IStreamPtr stream;
    stream.Attach(SHCreateMemStream(nullptr, 0));
    if (!stream)
        return E_FAIL;

    // marshal interface into stream
    auto hr = CoMarshalInterface(stream, riid, pUnk, MSHCTX_LOCAL, nullptr, MSHLFLAGS_NORMAL);
    if (FAILED(hr))
        return hr;

    // get stream length
    ULONG stream_length;
    {
        STATSTG stat;
        hr = stream->Stat(&stat, STATFLAG_NONAME);
        if (FAILED(hr))
            return hr;
        stream_length = static_cast<ULONG>(stat.cbSize.QuadPart);
    }

    // read data from stream
    std::vector<BYTE> raw_data;
    {
        hr = stream->Seek({ 0 }, STREAM_SEEK_SET, nullptr);
        if (FAILED(hr))
            return hr;
        raw_data.resize(stream_length);
        ULONG bytes_read;
        hr = stream->Read(&raw_data.front(), stream_length, &bytes_read);
        if (FAILED(hr))
            return hr;
        if (bytes_read != stream_length)
            return E_FAIL;
    }

    // encode bytes as base-64 string
    std::vector<WCHAR> encoded_bytes;
    {
        DWORD encoded_length_with_null = 0;
        if (!CryptBinaryToStringW(&raw_data.front(), stream_length, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &encoded_length_with_null))
            return E_FAIL;
        encoded_bytes.resize(encoded_length_with_null);
        auto encoded_length = encoded_length_with_null;
        if (!CryptBinaryToStringW(&raw_data.front(), stream_length, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, &encoded_bytes.front(), &encoded_length))
            return E_FAIL;
        if (encoded_length != encoded_length_with_null - 1)
            return E_FAIL;
    }

    // create result
    const auto result = SysAllocStringLen(&encoded_bytes.front(), encoded_bytes.size() - 1);
    if (result == nullptr)
        return E_OUTOFMEMORY;

    // set output parameters
    *pbstrOut = result;

    // success
    return S_OK;
}

Client side:

#include <windows.h>
#include <comdef.h>
#include <shlwapi.h>
#include <vector>
// link: crypt32.lib (for CryptStringToBinaryW)
// link: shlwapi.lib (for SHCreateMemStream)

/// <summary>
/// Unmarshals an interface from a token.
/// </summary>
HRESULT UnmarshalInterfaceFromToken(BSTR token, REFIID riid, LPVOID* ppv)
{
    // validate output parameters
    if (ppv == nullptr)
        return E_POINTER;

    // set default values for output parameters
    *ppv = nullptr;

    // validate input parameters
    if (token == nullptr)
        return E_INVALIDARG;

    // decode base-64 string as bytes
    std::vector<BYTE> decoded_bytes;
    {
        DWORD decoded_length = 0;
        if (!CryptStringToBinaryW(token, 0, CRYPT_STRING_BASE64, nullptr, &decoded_length, nullptr, nullptr))
            return E_FAIL;
        decoded_bytes.resize(decoded_length);
        if (!CryptStringToBinaryW(token, 0, CRYPT_STRING_BASE64, &decoded_bytes.front(), &decoded_length, nullptr, nullptr))
            return E_FAIL;
        if (decoded_length != decoded_bytes.size())
            return E_FAIL;
    }

    // wrap the bytes in an IStream
    IStreamPtr stream;
    stream.Attach(SHCreateMemStream(&decoded_bytes.front(), decoded_bytes.size()));
    if (!stream)
        return E_FAIL;

    // unmarshal interface from stream
    return CoUnmarshalInterface(stream, riid, ppv);
}


标签: c# com ipc