【问题标题】:How to serialize a VARIANT or BSTR?如何序列化 VARIANT 或 BSTR?
【发布时间】:2019-10-24 10:07:04
【问题描述】:

我正在尝试找出一些 COM 编组功能。最终,我想将一个深度嵌套的变量数组保存到文件中,但我首先尝试使用一个简单的字符串。我意识到这些 API 用于远程过程调用,但我希望这种序列化也适用于文件持久性。

我使用 Excel VBA 进行了大量工作,发现这个序列化 API 让我大开眼界。

下面的代码将 BSTR 序列化为缓冲区,复制缓冲区将用作保存到文件和从文件加载的替代品。

当前的问题是 BSTR_UserUnmarshal 正在抛出异常Unhandled exception at 0x7631C762 (KernelBase.dll) in RPCMarshalling.exe: 0x00000057: The parameter is incorrect. occurred

我可能有语法错误,因为我正在处理一些示例代码。目标是通过 RPC 序列化 API 调用将字符串从 srctest 获取到 desttest。

// RPCMarshalling.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include "pch.h"
#include <iostream>

//https://searchcode.com/file/140723732/dlls/oleaut32/tmarshal.c#l-818

typedef struct _marshal_state {
    LPBYTE  base;
    int     size;
    int     curoff;
} marshal_state;


int main()
{
    ::CoInitialize(0);

    CComBSTR srctest("Hello");
    marshal_state srcbuf;
    memset(&srcbuf, 0, sizeof(srcbuf));
    ULONG flags = MAKELONG(MSHCTX_DIFFERENTMACHINE, NDR_LOCAL_DATA_REPRESENTATION);

    ULONG size = ::BSTR_UserSize(&flags, 0, &srctest);

    DWORD newsize = max(size, 256);
    (&srcbuf)->base = (LPBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newsize);
    if (!(&srcbuf)->base)
        return E_OUTOFMEMORY;

    ::BSTR_UserMarshal(&flags, (&srcbuf)->base + (&srcbuf)->curoff, &srctest);
    (&srcbuf)->curoff = size;


    std::cout << "Hello World!\n" << size << "\n";


    marshal_state destbuf;
    memset(&destbuf, 0, sizeof(destbuf));

    (&destbuf)->base = (LPBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newsize);
    if (!(&destbuf)->base)
        return E_OUTOFMEMORY;

    /* pretend we are loading from file saved by src buffer */
    RtlCopyMemory((&destbuf)->base, (&srcbuf)->base, newsize);


    CComBSTR desttest("");
    BSTR deststring;

    try
    {
        unsigned char *buffer;
        buffer = ::BSTR_UserUnmarshal(&flags, (&destbuf)->base, &deststring);
    }
    catch (int e)
    {
        std::cout << "Error:" << e << "\n" << size << "\n";
    }



    ::CoUninitialize();

}

【问题讨论】:

  • 虽然我认为它曾经可以工作(很久以前),但它不再起作用了,因为 [type]_UserUnmarshal 的第一个参数实际上是指向 USER_MARSHAL_CB 结构的指针(第一个成员是确实是旗帜)。是的,没有这样记录,但这是真的(尝试使用 VARIANT 而不是 BSTR,它只会崩溃)。此结构由 RPC 代码填充(以及当您构建 RPC 接口时 MIDL 构建的内容)。几乎不可能在 RPC 上下文之外使用。如果你想看看它喜欢什么,只需在方法中构建一个包含 BSTR 的简单 idl。
  • PS:序列化一个BSTR,你不需要,只写长度和字节:-)
  • @Simon:下一步是序列化嵌套的 Variant 数组。这只是第一步。
  • 您可以尝试 StgSerializePropVariant docs.microsoft.com/en-us/windows/desktop/api/propvarutil/…(将您的 VARIANT* 转换为 PROPVARIANT*)。我知道它适用于相对复杂的东西。
  • @SimonMourier :我根据您的建议创建了一个答案。

标签: c++ com marshalling


【解决方案1】:

使用 Simon Mourier 关于 StgSerializePropVariant 的建议,实现序列化 Variant 目标的替代方法(根据 OP 的问题文本)工作正在进行中

int main()
{
    ::CoInitialize(0);

    CComVariant srctest("Hello");

    SERIALIZEDPROPERTYVALUE* serialized;
    ULONG cb;
    ::StgSerializePropVariant((PROPVARIANT*)&srctest, &serialized, &cb);

    CComVariant pvDest;
    ::StgDeserializePropVariant(serialized, cb, (PROPVARIANT*)&pvDest);

    CoTaskMemFree(serialized);

    CComBSTR strDest(pvDest.bstrVal);
    std::cout << "This got serialized:\n" << LPCSTR(_bstr_t(strDest, true)) << "\n";

    ::CoUninitialize();
}

【讨论】:

  • 大内存泄漏:您需要释放 PROPVARIANT 以及分配的序列化缓冲区。这就是我所说的更容易转换为 PROPVARIANT* 并继续使用 ATL 的智能包装类。基本上,PROPVARIANT 是 VARIANT(反向为假),因此由于您一直只使用 VARIANT 类型,因此您不应该遇到任何特定问题。这是一个有效且不会泄漏的代码:pastebin.com/raw/Udhr3RM6 否则在需要时使用 PropVariantClear。
  • @SimonMourier :好的,我在这个答案中采用了你的代码。而且,在探索 CComVariant 的方法时,我发现了 WriteToStream/ReadFromStream ,所以我添加了另一个答案。也许您也可以对它进行代码审查。干杯。
  • 内存泄漏现在消失了。不知道你为什么使用 _bstr_t。要么使用 ATL 附带的 CComXXX 类,要么使用 Visual Studio C++ 编译附带的 _bstr_t 和 _variant_t。两者都可以,但恕我直言,你不应该混合它们。事实上,ATL 有更多的特性,比如 Read/Write Stream 方法。就此而言,源是可用的,因此您可以查看它们的作用。我敢打赌,它们支持的组合少于 proputil.dll。另外,它们不支持 PROPVARIANTs 类型,如果您有一天需要它。
  • 那 _bstr_t 只是因为我想不出将 CComBSTR 写入 std:cout 的方法。
  • 只需将 CComBSTR 推送到 cout,它将转换为所需的内容。如果你真的想要底层指针,你可以使用m_str 成员。
【解决方案2】:

实际上,如果序列化是目标,那么为什么不序列化为 IStream

int main()
{
    ::CoInitialize(0);

    CComVariant srctest("Hello");

    CComPtr<IStream> pStream;
    HRESULT hr;
    hr = CreateStreamOnHGlobal(NULL, TRUE,(LPSTREAM *) &pStream.p);
    if (hr != S_OK) return hr;

    hr = srctest.WriteToStream(pStream, VT_BSTR);
    if (hr != S_OK) return hr;

    {
        // stream needs resetting to the start before we 
        // attempt to read it
        LARGE_INTEGER  dlibMove;
        dlibMove.HighPart = 0;
        dlibMove.LowPart = 0;
        DWORD dwOrigin;
        dwOrigin = 0;
        ULARGE_INTEGER  libNewPosition;
        hr = pStream->Seek(dlibMove, dwOrigin, &libNewPosition);
        if (hr != S_OK) return hr;
    }

    CComVariant pvDest;
    hr = pvDest.ReadFromStream(pStream, VT_BSTR);
    if (hr != S_OK) return hr;

    CComBSTR strDest(pvDest.bstrVal);
    std::cout << "This got serialized:\n" << LPCSTR(_bstr_t(strDest, true)) << "\n";
    //std::cout << "This got serialized:\n" << strDest.m_str << "\n";

    ::CoUninitialize();
}

【讨论】:

  • 你又出现了内存泄漏 :-) 你必须释放流(你要求 CreateStreamOnHGlobal 在释放流时释放内存),或者只是将它声明为 CComPtr ,这样它就会巧妙地发布。
【解决方案3】:

StgSerializePropVariant 解决方案与 VT_BSTR 存在内存边界问题,如果 wchar_t 不是按 Variant 顺序分配的;

CComVariant::WriteToStream 解决方案不适用于 VT_ARRAY。

请参考Convert VARIANT to bytes and vise versa?,它为我提供了更好的解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-20
    • 2012-01-23
    • 2015-12-29
    • 2019-04-26
    • 1970-01-01
    • 2011-02-09
    • 2018-05-17
    • 2010-11-14
    相关资源
    最近更新 更多