十五 统一数据传输 数据格式 数据对象和IDataObject接口 通报连接机制 通过剪贴板传输数据 MFC对数据对象的支持 十五 统一数据传输 数据格式 数据对象和IDataObject接口 通报连接机制 数据变化通报机制. 数据变化通知的实现方式 通过剪贴板传输数据 MFC对数据对象的支持
1 数据格式 COM提供了UDT(Uniform Data Transfer)作为应用系统之间的数据交换方案。 1 数据格式 COM提供了UDT(Uniform Data Transfer)作为应用系统之间的数据交换方案。 COM之前的数据传输:剪贴板, DDE 局限性: 1。数据格式限制。只能使用预定义的几种格式 2。被传输的数据只能位于全局内存中。 3。没有双向通讯机制。 4。数据格式与传输协议相关。 COM使用数据对象作为数据实体,数据对象通过IDataObject接口暴露其内部信息。数据的传递变成了数据对象的创建和接口指针的传递。 (永久对象,数据对象,都是COM对象,都有状态.) UDT 定义了两个数据结构FORMATETC 和STGMEDIUM分别描述数据格式和存储介质以适应广泛的数据类型和存储介质。且提供了双向通讯机制。 数据对象可以称为应用之间交换信息的标准。数据对象的IDataObject接口指针。统一的方式访问、操作。 数据对象的操作过程频繁地使用了如下的两个数据结构:
typedef struct tagFORMATETC { CLIPFORMAT cfFormat; DVTARGETDEVICE *ptd; DWORD dwAspect; LONG lindex; DWORD tymed; }FORMATETC 1。CLIPFORMAT cfFormat; 数据格式。预定义的数据格式. UINT RegisterClipboardFormat( LPCTSTR lpszFormat // name of new format ); 可以创建新的数据格式。 2。ptd指向DVTARGETDEVICE数据结构 typedef struct tagDVTARGETDEVICE { DWORD tdSize; //结构长度 WORD tdDriverNameOffset; // 设备驱动名 WORD tdDeviceNameOffset;// 设备名 WORD tdPortNameOffset; // 端口名 WORD tdExtDevmodeOffset; // DEVMODE结构 BYTE tdData[ 1 ]; } DVTARGETDEVICE;
3。dwAspect 是一个枚举,指定图形数据的显示方式 enum tagDVASPECT { DVASPECT_CONTENT = 1, DVASPECT_THUMBNAIL = 2, DVASPECT_ICON = 4, DVASPECT_DOCPRINT = 8 } DVASPECT; 4。lindex为dwAspect的补充。 5。tymed指定存储介质。可以指定全局内存、磁盘文件、或者存储对象等。 另一个常用的结构如下: typedef struct tagSTGMEDIUM { DWORD tymed; union { HBITMAP hBitmap; HMETAFILEPICT hMetaFilePict; HENHMETAFILE hEnhMetaFile; HGLOBAL hGlobal; LPOLESTR lpszFileName; IStream *pstm; IStorage *pstg; } u; IUnknown *pUnkForRelease; }STGMEDIUM;
1。其中tymed指明介质的类型。 typedef enum tagTYMED { TYMED_HGLOBAL = 1, TYMED_FILE = 2, TYMED_ISTREAM = 4, TYMED_ISTORAGE = 8, TYMED_GDI = 16, TYMED_MFPICT = 32, TYMED_ENHMF = 64, TYMED_NULL = 0 } TYMED; 2。联合成员u指明数据的真正位置,根据tymed的值使用相应的联合成员。 3。pUnkownForRelease用来控制数据的释放过程。 应用程序可以选择合适的存储介质来传输数据。 这两个数据结构用于数据对象的各种操作之中.
2 数据对象和IDataObject接口 数据对象是一个COM对象,它实现了IDataObject接口。一旦应用程序创建了一个数据对象,此对象可以通过任一种传输协议传递到另一个应用。通过协议传送的实际上是IDataObject接口指针。(此传输协议使用了接口的列集,散集过程) .发送、接受双方都使用IDataObject接口来操作数据。 接口的定义如下: class IDataObject : public IUnknown { public: virtual HRESULT GetData( FORMATETC *pformatetcIn, [in] STGMEDIUM *pmedium [out] ) = 0; /*接受程序通过传输协议得到数据对象的接口指针后,在 pFormatetc中指定提取数据所使用的格式,并把数据放在pmedium 指针中。如果数据对象有我们所指定的格式,GetData会填充pmedium,否则出错返回。*/ virtual HRESULT GetDataHere( FORMATETC *pformatetc, [in] STGMEDIUM *pmedium [in, out] ) = 0; /*同GetData,但是,接受方也要负责预先分配存储空间pmedium*/
virtual HRESULT QueryGetData( FORMATETC *pformatetc [in]) = 0; /*询问数据对象是否支持pFormatetc数据格式,如果支持的话,再调用GetData会更好。*/ virtual HRESULT GetCanonicalFormatEtc( FORMATETC *pformatectIn, [in] FORMATETC *pformatetcOut [out]) = 0; /*提供一个标准的等价的FORMATETC结构。据此判断不同的FORMATETC结构能否得到同样的数据,以免重复调用GetData。*/ virtual HRESULT SetData( FORMATETC *pformatetc, [in] STGMEDIUM *pmedium, [in] BOOL fRelease [in]) = 0; /*接收方客户程序为数据对象提供数据。 */ virtual HRESULT EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC * *ppenumFormatEtc) = 0; /*返回一个数据格式枚举器,可以枚举该数据对象所支持的数据格式。*/
virtual HRESULT DAdvise( FORMATETC. pformatetc,DWORD advf,IAdviseSink virtual HRESULT DAdvise( FORMATETC *pformatetc,DWORD advf,IAdviseSink *pAdvSink, [in] DWORD *pdwConnection [out]) = 0; /*在客户的通报接收器(advisory sink)和数据对象之间建立一个通报连接(advisory connection),当数据对象中的数据发生变化时,它可以通知客户程序的通报连接器。 pdwConnection保留连接标志,以便客户解除连接使用。*/ virtual HRESULT DUnadvise( DWORD dwConnection [in] ) = 0; /*利用DAdvise函数返回的连接标志解除连接。*/ virtual HRESULT EnumDAdvise( IEnumSTATDATA **ppenumAdvise) = 0; /*返回一个STATDATA结构的枚举器。 STATDATA结构正好与DAdvise函数的参数一一对应*/ };
3 通报连接机制 3.1 数据变化通报机制. 客户得到IDataObject指针,可以读取数据,然而客户希望在数据改变的时候能及时得到通知,而不是一遍遍地轮询。要求数据对象在数据变化时能主动通知客户,客户应提供一个接收器以接受这些通知。 COM提供了连接点机制以实现双向通讯。 在UDT中,客户程序只需实现IAdviseSink接口: class IAdviseSink :public IUnkown { pulbic: virtual void OnDataChange(FORMATETC*pformatetc,STGMEDIUM *pmedium)=0; ......} 而数据对象作为源对象并没有实现IConnectionPointContainer接口,而是直接使用IDataObject的后三个成员函数 代替IConnectionPoint接口的Advise,UAdvise,和EnumConnections。 之所以可以这样简化,是因为在数据对象和客户的交互过程中,出接口以及其成员函数可以事先定下来,而且功能也很清楚。比较 IConnectionPoint的Advise的连接函数:
virtual HRESULT Advise( IUnknown *pUnkSink, DWORD *pdwCookie) = 0; 处理通用的连接。 IDataObject的DAdvise函数除了直接指定IAdviseSink接口以外(而不是含糊的IUnkown接口),同时指定了客户方感兴趣的数据格式pformtec,以及通报的方式advf。(见p284)。 使用方式: 数据对象方:可以使用诸如MFC的实现,见后. 接收方:客户程序实现IAdviseSink接口,当然指定OnDataChanged等函数, 然后在得到数据对象指针后,通过IDataObject调用DAdvise函数,将刚才的IAdviseSink接口传入,以响应数据变化通知. 客户 数据对象 通报连接接收器 客户把接收器的 接口指针传给对象 数据改变时,数据对象调用 OnDataChange成员 IDataObject IAdviseSink
3.2 数据变化通知的实现方式: 数据对象对连接的实现方式可以按照连接点机制中类似的方法来进行. COM提供了“数据通报控制器” (data advise holder)这个内部对象(没有类厂,没有CLSID)以简化这个工作. WINOLEAPI CreateDataAdviseHolder( IDataAdviseHolder **ppDAHolder ); 其中IDataAdviseHolder接口的定义 class IDataAdviseHolder : public IUnknown { public: virtual HRESULT Advise( IDataObject *pDataObject,FORMATETC *pFetc, DWORD advf,IAdviseSink *pAdvise, DWORD *pdwConnection) = 0; virtual HRESULT Unadvise(DWORD dwConnection) = 0; virtual HRESULT EnumAdvise( EnumSTATDATA * *ppenumAdvise) = 0; virtual HRESULT SendOnDataChange( IDataObject *pDataObject, DWORD dwReserved,DWORD advf) = 0; };
数据对象利用CreateDataAdviseHolder创建一个数据通报控制器对象,并返回一个IDataAdviseHolder 接口。 HRESULT MyDataObject::DAdvise(FORMATETC *pformatetc,DWORD advf,IAdviseSink *pAdvSink,DWORD *pdwConnection) { return pDAHolder->Advise((IDataObject*)this, FORMATETC *pformatetc,DWORD advf,IAdviseSink *pAdvSink,DWORD *pdwConnection ); } 其他两个函数的参数都一一对应。 当数据对象要调用它所连接的接收器sink的OnDataChange函数时,就把自己的IDataObject接口指针和通知方式标志advf一起传给IDataAdviseHolde::SendOnDataChange函数。在数据通报控制器内部,记录了所有通过Advise函数建立的sink的接口指针。数据通报控制器接收到调用后,检查连接标志,找到相应的advise sink,然后调用IAdviseSink::OnDataChange函数。
4 通过剪贴板传输数据 我们需要能传输数据对象接口指针的传输机制。 4 通过剪贴板传输数据 我们需要能传输数据对象接口指针的传输机制。 剪贴板机制是Windows上一种IPC机制。它是全局共享的资源。WIN32有一组API进行剪贴板操作。 p287列出了常用的几个。 OLE对剪贴板进行了扩充,称为“OLE剪贴板”,使之可以作为数据对象的传输通道。 WINOLEAPI OleSetClipboard(IDataObject * pDataObj ); 数据提供方向ole剪贴板上放数据对象 WINOLEAPI OleGetClipboard(IDataObject ** ppDataObj ); 数据接收方从ole剪贴板上得到数据对象接口指针 WINOLEAPI OleFlushClipboard( ); 清理剪贴板 WINOLEAPI OleIsCurrentClipboard(IDataObject * pDataObject ); 判断某数据对象是否在剪贴板上。 其他的数据传输机制也可以传输数据对象.
5 MFC对数据对象的支持 MFC提供了两个类COleDataSource (使用于数据提供方)和COleDataObject(使用于数据接收方) 实现了IDataObject接口。 COleDataSource 常用的成员函数 P290 CacheData 在数据对象的缓冲区中插入数据 SetCLipboard 调用OleSetCLipboard函数把数据对象放到剪贴板上。 DoDragDrop 对拖放的支持。 COleDataObject常用的成员函数。 AttachClipboard 把当前剪贴板上的数据对象与COleDataObject的数据成员m_lpDataObject联系起来。 Detach 解除联系 GetData 指定FORMATETC格式,准备STGMEDIUM指针后调用IDataObject::GetData,取回的数据存放在STGMEDIUM指针中。
源程序 客户程序 剪贴板 IDataObject 格式1 格式2 MFC应用处理剪贴板 原始数据 目标数据 COleDataSource 对象 格式1 格式2 客户程序 目标数据 m_lpDataObject IDataObject 剪贴板 复制 SetClipboard 粘贴 AttachClipboard 调用CacheGlobalData 或CacheData 调用GetGlobalData 或GetData MFC应用处理剪贴板
例子: 发送方: COleDataSource* pSource = new COleDataSource();//创建数据对象 ... HGLOBAL hHeader = ::GlobalAlloc(GMEM_SHARE, nHeaderSize + nImageSize); pSource->CacheGlobalData(CF_DIB, hHeader); //缓冲数据 pSource->SetClipboard(); //数据对象放到剪贴板上 接收方: ...... COleDataObject dataObject; //创建数据对象 dataObject.AttachClipboard();//数据对象与剪贴板相连 HGLOBAl hDib=dataObject.GetGlobalData(CF_DIB); //读取数据