COM 通过指针将C++结构从C++客户端传递到C++服务器

COM Passing C++ struct from C++ client to to C++ server by pointer

本文关键字:C++ 客户端 服务器 结构 指针 COM      更新时间:2023-10-16

我一直在研究使用 COM 将结构作为参数从 C++ 客户端传递到C++服务器。我找到了很多例子,但没有一个像我五岁那样真正向我解释它,也没有一个真正提供关于如何做我想做的事情的坚定理解,即简单地通过 COM 接口传递一个C++结构,其中双方都是C++。应该很容易,对吧?

我已经在服务器端的 IDL 文件中建立了如下结构:

[
    uuid(7F0C9A48-3C41-425B-B4E6-8156B61D5355),
    version(1.0)
]
typedef struct xxxData
{
    int iWidth;
    int iHeight;
    SafeArray(short) pxxxData;
} xxxData;
// Fix for UUID DECLARATION FOR _uuidof() functionality
// From http://go4answers.webhost4life.com/Example/error-c2787-no-guid-been-associated-158947.aspx
cpp_quote("struct __declspec(uuid("{7F0C9A48-3C41-425B-B4E6-8156B61D5355}")) xxxData;")

据我所知,这有效。

现在我的客户端调用GetImageData,如下所示:

[id(16)] HRESULT GetImageData([in,out] VARIANT* pData);

现在我的客户端调用使用此函数如下所示:

VARIANT* pData = new VARIANT;
VariantInit( pData );
xxxData* data = new xxxxData;
HRESULT hr = mpCOMEvents->GetImageData(pData);
data = (FBIS_ImageData*)(pData->pvRecord);
int length = data->iWidth * data->iHeight;

但是,长度给了我一个不正确的地址位置。这让我想知道我对pvRecord的使用是否不正确,我是否真的可以对其进行类型转换?

这是我的 COM 服务器端:

xxxData data;
//SAFEARRAY *psa;
IRecordInfo *pRI;
HRESULT hr;
/* Pass in Structure Information */
data.iHeight = 100;
data.iWidth = 100;
// Used http://vcfaq.mvps.org/com/4.htm as reference
hr = GetRecordInfoFromGuids(LIBID_xxxLib, 1, 0, 0x409, _uuidof(xxxData), &pRI);
VariantInit(pData);
pData->vt = VT_RECORD;
pData->pvRecord = &data;
pData->pRecInfo = pRI;
pRI = NULL;

这里有一些混乱。

如果您的目标不是自动化友好,请将 IDL 更改为:

[size_is=iWidth*iHeight] unsigned short* pxxxData;

并且不要在此上使用 SAFEARRAY API。对于封送处理,必须编译代理/存根 DLL 并注册它。

如果您的目标是自动化友好,请将 IDL 更改为:

SAFEARRAY(short) pxxxData;

为此使用 SAFEARRAY API。对于编组,您必须编译一个类型库(可选地嵌入它)并注册它。这也支持早期绑定(例如VB6,tlbimp)。

这将适用于支持用户定义类型的语言/环境。对于没有的(例如脚本语言),您必须使用基于oleautomation/dual/IDispatch的接口而不是结构(并在服务器中实现)。


编辑:基于您对问题所做的更改。

您应该仅将pData参数声明为outGetImageData填充它,而不是使用它,并可能替换它。它也只需要在返回时封送,而不是在呼叫时封送。这里有一个建议:

[id(16)] HRESULT GetImageData([out] VARIANT* pData);

您的客户端代码存在内存泄漏,它总是创建一个 xxxData。这里有一个建议:

// If pData is in-out, this is not safe, use CoTaskMemAlloc(sizeof(VARIANT)) instead.
// The callee may override the buffer by assuming it was CoTaskMemAlloc'ed, thus
// assuming it can CoTaskMemFree the original location and set the pointer to a new
// CoTaskMemAlloc'ed location.
// The callee may be a proxy.
// Assuming it's out only, we can provide any location with enough space for a VARIANT.
VARIANT vData; 
VariantInit( &vData );
xxxData* data; // remove memory leak
HRESULT hr = mpCOMEvents->GetImageData(&vData);
// error handling removed for clarity (I hope)
data = (xxxData*)(vData.pvRecord);
int length = data->iWidth * data->iHeight;
// ... use data ...
// Don't forget to clear the variant, or there'll be a memory leak
// It implies:
//   vData.pRecInfo->RecordDestroy(vData.pvRecord);
//     This should recursively release memory allocated in each field
//     and finally release the memory allocated for the struct itself.
//   vData.pRecInfo->Release();
VariantClear( &vData );
// don't use data past this point

服务器代码pData->pvRecord设置为指向堆栈,这意味着它可能会被调用方或其他调用的函数覆盖。这里有一个建议:

xxxData* data; // Changed to pointer
IRecordInfo *pRI;
HRESULT hr;
// data.iHeight = 100; // removed
// data.iWidth = 100;  // removed
hr = GetRecordInfoFromGuids(LIBID_xxxLib, 1, 0, 0x409, _uuidof(xxxData), &pRI);
// error handling removed for clarity (I hope)
VariantInit(pData);
// This will allocate memory for the struct itself
// For fields that require memory allocation, follow "normal" COM rules,
// such as using CoTaskMemAlloc for buffers, SysAllocString or similar for BSTRs,
// etc.
// For each inner (pointed to) structure, you should call RecordCreate on the
// respective IRecordInfo instance for that type.
data = (xxxData*)pRI->RecordCreate();
data->iHeight = 100; // new
data->iWidth = 100;  // new
// If pData is in-out, this will leak, use VariantClear instead.
// Assuming it's out only, use VariantInit as it points to (allocated) garbage.
VariantInit(pData);
pData->vt = VT_RECORD;
pData->pvRecord = data; // data is already a pointer
pData->pRecInfo = pRI;
pRI = NULL;
// This won't (normally) leak, the caller must call VariantClear on the out VARIANT.
// The caller may be a stub.