类型:翻译
时间:2004-09-27
译者:王先生(MrWang2000)
Email : yuanzhaowang@sohu.com
实例下载地址Download source files - 116 Kb
几个星期以前,我拼命的寻找一个能够通过COM接口传递C++对象的例子,
但是,没有找到.这就是我发表这篇文章的原因
向ATL的DLL中传递一个C++对象参数并不是非常之难,
但是,当然也会有点难度,也很有趣.
在开始一个工程以前,确信你的客户机和服务器组件都是由适应C++的程序的,
第二,你必须知道怎样设置你的客户机和服务器
接口的局限性
COM技术要求客户机和服务器高度的分离,这是通过接口实现的,
但是问题出在:接口的方法中只提供了有限个参数数据类型,
如果这个接口是基于IDispatch的,参数类型的可选范围就更加受到限制了,
由于这些局限性,C++对象只有在满足以下条件时才能够传递:
1,客户机和服务器都是由VC++编写
2,它们必须共享对象的定义(比如 头文件)
3,传递应用程序设计的简单的对象
4,你的应用程序可能需要运行在一个分布式环境下。你希望COM的远程活动,本地/远程活动是透明的,安全的
我建议,在开始工作之前,先顺序的看一下各个标题
现在,我列出实例,并作以下事情:
1,创建一个ATL DLL服务器
2,添加一个MFC类,从CObject类派生
3,在类的头部使用 DECLARE_SERIAL 宏
4,在类的中间使用 IMPLEMENT_SERI 宏
5,覆盖Serialize() 方法
// 你的 CSimpleObj 类应该像这样:
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;// 内部字符串数据
};
// 把这个数据对象写入到文档中
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;
}
6,现在,下一步就是用一个CArchive对象来进行序列化和反序列化(载入和存储对象),
我用了一个叫CBlob的新类来实现的
class CBlob
{
public:
CBlob() {};
virtual ~CBlob() {};
// 从一个 CObject对象中提取数据并载入到一个 SAFEARRAY对象中.
SAFEARRAY* Load( CObject *pObj );
// 重新创建一个SAFEARRAY对象
BOOL Expand( CObject * &pObj, SAFEARRAY *pVar );
private:
};
// 从一个 CObject对象中提取数据并用它构建一个 SAFEARRAY对象.
SAFEARRAY* CBlob::Load( CObject *pObj)
{
CMemFile memfile; // 内存文件
// 定义一个用来标记档案文件是读取还是存储的标志
long lMode = CArchive::store | CArchive::bNoFlushOndelete;
// 用内存文件创建档案文件
CArchive ar(&memfile, lMode );
// m_pDocument 不使用
ar.m_pDocument = NULL;
// 序列化对象到档案文件中
ar.WriteObject(pObj);
// 关闭档案文件--现在,数据在内存文件中
ar.Close();
// 取得内存文件的长度(以字节为单位)
long llen = memfile.GetLength();
// 释放缓冲区 关闭文件
unsigned char *pMemData = memfile.Detach();
// 设定safearray
SAFEARRAY *psa;
// 创建safearray对象存取流数据
psa = SafeArrayCreateVector( VT_UI1, 0, llen );
// 指向字节数组的指针
unsigned char *pData = NULL;
// 取得一个 safe array的指针. 锁定数组.
SafeArrayAccessData( psa, (void**)&pData );
// 拷贝内存文件到 safearray
memcpy( pData, pMemData, llen );
// 清理缓冲区
delete pMemData;
// 锁定对 safearray的访问
SafeArrayUnaccessData(psa);
// 返回一个在这分配的SAFEARRAY的指针
return psa;
}
// 重新创建一个SAFEARRAY对象
BOOL CBlob::Expand(CObject * &rpObj, SAFEARRAY *psa)
{
CMemFile memfile; // 反序列化的内存文件
long lLength; // 字节数
char *pBuffer; // 缓冲区指针
// 锁定数组数据的访问
SafeArrayAccessData( psa, (void**)&pBuffer );
// 取得数组中元素个数. 是字节数
lLength = psa->rgsabound->cElements;
// 连接缓冲区到内存文件
memfile.Attach((unsigned char*)pBuffer, lLength);
// 从缓冲区头部开始
memfile.SeekToBegin();
// 创建一个连接到内存文件上的档案文件
CArchive ar(&memfile, CArchive::load | CArchive::bNoFlushOndelete);
// 不使用文档指针
ar.m_pDocument = NULL;
// 填充对象 取得指针
rpObj = ar.ReadObject(0);
// 关闭档案文件
ar.Close();
// 注意: 当SAFEARRAY被毁坏时 pBuffer 被释放
// 释放缓冲区 关闭文件
pBuffer = (char*) memfile.Detach();
// 释放safearray 缓冲区
SafeArrayUnaccessData( psa );
return TRUE;
}
在这里 ,我使用SAFEARRAY是因为它对我们来说是最好的选择,
它可以包含一些复杂的多维数组,但是,
这个例子我们只使用了非常简单的数组,SAFEARRAY数据
有一个问题:MIDL认不出这个数据类型,
在下一篇文章中我将讲述最简单的方法:使用 VARIANT数据类型
下一步如下:
1,创建一个COM接口
2,创建一个SAFEARRAY对象
3,在IDL文件中定义 [helpstring("method SetArray")]
HRESULT SetArray([in]SAFEARRAY (unsigned char) pData);[helpstring("method GetArray")]
HRESULT GetArray([out/*,retval*/]SAFEARRAY(unsigned char) *pData);
4,创建一个基于MFC的客户机来测试该应用程序
你的IDL文件应该象这样:
interface IBolbData : IUnknown
{
[helpstring("method SetArray")] HRESULT SetArray([in]SAFEARRAY
(unsigned char) pData);
[helpstring("method GetArray")] HRESULT GetArray([out/*,retval*/]
SAFEARRAY(unsigned char) *pData);
};
// 设定对象
STDMETHODIMP CBolbData::SetArray(SAFEARRAY *pData)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())
// 创建CSimpleObj的亚元指针
CSimpleObj *dummy=NULL;
// 创建 blob 对象 用来填充、反序列化
CBlob blob;
// 使用 safearray 创建亚元对象
blob.Expand( (CObject*&)dummy, pData );
dummy->Show(); // 调用显示函数测试对象
delete dummy; //删除指针
return S_OK;
}
// 创建对象 并发送给客户机.
STDMETHODIMP CBolbData::GetArray(SAFEARRAY **pData)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())
// 创建对象并发送给服务器
CSimpleObj *pMyOb = new CSimpleObj();
//设定字符串数据
pMyOb->SetString( "A SAFEARRAY from the server!" );
// 创建blob来序列化对象
CBlob blob;
// 将对象载入blob
*pData = blob.Load( pMyOb );
// 删除pMyOb指针
delete pMyOb;
return S_OK;
}
最后,写一个有两个按钮的基于对话框的 MFC 应用程序 并添加如下代码:
void CClientDlg::OnOK()
{
// 从CLSID串创建COM智能指针
try
{
IBolbDataPtr pI( "Server.BolbData.1" );
SAFEARRAY *psa ;
// 从服务器取得 safearray
pI->GetArray( &psa );
// 创建指针
CSimpleObj *dummy=NULL;
// blob 对象
CBlob blob;
//使用blob 扩展 safearray 到一个对象里
blob.Expand( (CObject *&)dummy, psa );
//通过调用一个对象的方法来测试它
dummy->Show();
// 删除对象
delete dummy;
}
// 通过智能指针处理任意 COM 异常
catch (_com_error e)
{
// 显示错误信息
AfxMessageBox( e.ErrorMessage() );
}
}
void CClientDlg::OnLoad()
{
try
{
// 从CLSID 串创建智能指针
IBolbDataPtr pI( "Server.BolbData.1" );
SAFEARRAY *psa ;
// 创建送给服务器的对象
CSimpleObj *pMyOb = new CSimpleObj();
// 设置字符串数据
pMyOb->SetString( "The client sent a SAFEARRAY!" );
// 创建 blob 用来序列化对象
CBlob blob;
// 将对象载入到 blob
psa = blob.Load( pMyOb );
//删除对象
delete pMyOb;
pI->SetArray( psa );
}
catch (_com_error e)
{
// 显示错误信息
AfxMessageBox( e.ErrorMessage() );
}
}
总结
这篇文章包含了很多的主题:例如 怎样使用序列化,
怎样使用 SAFEARRAY,和怎样通过接口传递C++对象。
我要感谢William Rubin,他的文章对我帮助很大,
我曾经计划把这个主题解释的更详细,但由于时间不足我无法完成,
然而我会不断的更新这篇文章,在这期间,请不用客气的跟我联系