COM 介面嚴格限制參數類型 (僅支援 Automation 相容類型),導致無法直接跨 DLL 邊界傳遞自訂 C++ 類別物件。本文示範一種經典方案:利用 MFC 序列化機制將物件扁平化為位元組陣列,封裝於 SAFEARRAY 中跨介面傳遞,對端再反序列化還原物件。
CObject 並實作 DECLARE_SERIAL / IMPLEMENT_SERIAL 及 Serialize(CArchive&)。class CSimpleObj : public CObject
{
DECLARE_SERIAL(CSimpleObj)
public:
CSimpleObj(); virtual ~CSimpleObj();
void SetString(CString csData);
virtual void Serialize(CArchive& ar);
void Show(); // 測試用:彈窗顯示內部字串
private:
CString m_strData;
};
IMPLEMENT_SERIAL(CSimpleObj, CObject, 1)
void CSimpleObj::Serialize(CArchive& ar)
{
CObject::Serialize(ar);
if (ar.IsLoading()) ar >> m_strData;
else ar << m_strData;
}
void CSimpleObj::Show() { AfxMessageBox(m_strData); }
void CSimpleObj::SetString(CString csData) { m_strData = csData; }
封裝 CMemFile + CArchive 完成記憶體序列化,並與 SAFEARRAY 互轉:
class CBlob
{
public:
// 物件 → SAFEARRAY (序列化)
SAFEARRAY* Load(CObject* pObj)
{
CMemFile memFile;
CArchive ar(&memFile, CArchive::store | CArchive::bNoFlushOnDelete);
ar.m_pDocument = NULL;
ar.WriteObject(pObj);
ar.Close();
DWORD len = memFile.GetLength();
BYTE* pData = memFile.Detach();
SAFEARRAY* psa = SafeArrayCreateVector(VT_UI1, 0, len);
BYTE* pSafeData;
SafeArrayAccessData(psa, (void**)&pSafeData);
memcpy(pSafeData, pData, len);
delete[] pData;
SafeArrayUnaccessData(psa);
return psa;
}
// SAFEARRAY → 物件 (反序列化)
BOOL Expand(CObject*& rpObj, SAFEARRAY* psa)
{
CMemFile memFile;
BYTE* pData;
SafeArrayAccessData(psa, (void**)&pData);
LONG len = psa->rgsabound[0].cElements;
memFile.Attach(pData, len);
memFile.SeekToBegin();
CArchive ar(&memFile, CArchive::load | CArchive::bNoFlushOnDelete);
ar.m_pDocument = NULL;
rpObj = ar.ReadObject(RUNTIME_CLASS(CSimpleObj)); // 需指定 RUNTIME_CLASS
ar.Close();
pData = (BYTE*)memFile.Detach(); // 避免雙重釋放
SafeArrayUnaccessData(psa);
return TRUE;
}
};
interface IBlobData : IUnknown
{
[helpstring("method SetArray")] HRESULT SetArray([in] SAFEARRAY(unsigned char) pData);
[helpstring("method GetArray")] HRESULT GetArray([out] SAFEARRAY(unsigned char)* pData);
};
注意:MIDL 不直接支援 SAFEARRAY(unsigned char) 語法,實際 .idl 中需寫為 SAFEARRAY(BYTE) 或使用 VARIANT 封裝 (下文提及)。
STDMETHODIMP CBlobData::SetArray(SAFEARRAY* pData)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CSimpleObj* pObj = NULL;
CBlob blob;
blob.Expand(pObj, pData); // 還原物件
if (pObj) { pObj->Show(); delete pObj; }
return S_OK;
}
STDMETHODIMP CBlobData::GetArray(SAFEARRAY** ppData)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CSimpleObj* pObj = new CSimpleObj();
pObj->SetString(_T("來自 Server 的 SAFEARRAY!"));
CBlob blob;
*ppData = blob.Load(pObj); // 序列化為 SAFEARRAY
delete pObj;
return S_OK;
}
void CClientDlg::OnGetFromServer() // 取得物件
{
try {
IBlobDataPtr pSrv(_T("Server.BlobData.1"));
SAFEARRAY* psa = NULL;
pSrv->GetArray(&psa);
CSimpleObj* pObj = NULL;
CBlob blob;
blob.Expand(pObj, psa);
if (pObj) { pObj->Show(); delete pObj; }
} catch (_com_error& e) { AfxMessageBox(e.ErrorMessage()); }
}
void CClientDlg::OnSendToServer() // 送出物件
{
try {
IBlobDataPtr pSrv(_T("Server.BlobData.1"));
CSimpleObj* pObj = new CSimpleObj();
pObj->SetString(_T("Client 送出的 SAFEARRAY!"));
CBlob blob;
SAFEARRAY* psa = blob.Load(pObj);
delete pObj;
pSrv->SetArray(psa);
} catch (_com_error& e) { AfxMessageBox(e.ErrorMessage()); }
}
SAFEARRAY 在 MIDL 需特殊處理。VARIANT 封裝 SAFEARRAY (支援 VT_ARRAY | VT_UI1),提升 IDL 相容性;或採用自定義封送器。完整專案碼可下載參考:原文配套源碼。