第三部分:IDataObject实现
2013-02-25 14:53
387 查看
第二部分我们介绍了OLE数据传输的相关知识,这一节主要讲怎么实现一个IDataObject接口。然后再给出一个例子。
首先我们要明白,IDataObject是一个COM接口,我们就必须得创建一个类,实现这个接口的每一个方法,包括它的基类的方法。
上面SdkDataObject.h定义了类SdkDataObject类,它实现了IDataObject接口,包括IUnknown接口。
几点说明如下:
1、SdkDataObject类里面也声明了拷贝构造、赋值操作符等,而且是私有的,就是不想让这个对象可以复制
2、IsDataAvailable:判断指定的格式是否支持。
3、GetGlobalData:得到全局的数据。
4、SetGlobalData:设置全局的数据。
5、CopyMedium:复制媒体数据。
6、上面列出的这些函数,几乎都是辅助函数,我设计时是根据我自己的业务需求来设计的,不同的需求可能不同,但最本质的都是实现一些COM接口。同时,我还定义了一个结构体DATASTORAGE_t,用来保存数据格式对,把设置的数据格式对存在一个vector中。
7、成员变量volatile LONG m_lRefCount,表示当前类的引用计数,构造函数一定要初始化为1,不能是0,其中volatile的意思就是说,告诉编译器不要其优化,每次要用访问这个值时,都是到它的地址上去取,而不是从寄存器中读取。
IDataObject::DAdvise、IDataObject::EnumDAdvise和IDataObject::DUnadivise函数简单的返回OLE_E_ADVISENOTSUPPORTED。
下面给出了利用这个Data object 住剪切板复制一些数据。
这里调用了SetGlobalData函数来设置数据,上面没有给出这个实现,现在记住就行了,它内部是调用SetData来实现。设置的数据格式是CF_UNICODETEXT,因为目前这个DataObject只支持CF_UNICODETEXT格式,这个从EnumFormatEtc函数的实现就可以看出。你可以写一个控制台程序,加如下面两个方法,运行后,在记事本里按Ctrl +
V,看看是不是把Hello World.粘贴了。
这一节,我们给出了SdkDataObject的实现,有些实现还是很简单,关键是要明白它的本质。
首先我们要明白,IDataObject是一个COM接口,我们就必须得创建一个类,实现这个接口的每一个方法,包括它的基类的方法。
1. SdkDataObject.h 头文件:
#ifdef __cplusplus #ifndef _SDKDATAOBJECT_H_ #define _SDKDATAOBJECT_H_ #include "SdkCommon.h" #include "SdkDropSource.h" typedef struct _DATASTORAGE { FORMATETC *m_formatEtc; STGMEDIUM *m_stgMedium; } DATASTORAGE_t, *LPDATASTORAGE_t; class CLASS_DECLSPEC SdkDataObject : public IDataObject { public: SdkDataObject(SdkDropSource *pDropSource = NULL); BOOL IsDataAvailable(CLIPFORMAT cfFormat); BOOL GetGlobalData(CLIPFORMAT cfFormat, void **ppData); BOOL GetGlobalDataArray(CLIPFORMAT cfFormat, HGLOBAL *pDataArray, DWORD dwCount); BOOL SetGlobalData(CLIPFORMAT cfFormat, void *pData, BOOL fRelease = TRUE); BOOL SetGlobalDataArray(CLIPFORMAT cfFormat, HGLOBAL *pDataArray, DWORD dwCount, BOOL fRelease = TRUE); BOOL SetDropTip(DROPIMAGETYPE type, PCWSTR pszMsg, PCWSTR pszInsert); // The com interface. IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv); IFACEMETHODIMP_(ULONG) AddRef(); IFACEMETHODIMP_(ULONG) Release(); IFACEMETHODIMP GetData(FORMATETC *pformatetcIn, STGMEDIUM *pmedium); IFACEMETHODIMP SetData(FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease); IFACEMETHODIMP GetDataHere(FORMATETC *pformatetc , STGMEDIUM *pmedium ); IFACEMETHODIMP QueryGetData(FORMATETC *pformatetc); IFACEMETHODIMP GetCanonicalFormatEtc(FORMATETC *pformatetcIn, FORMATETC *pformatetcOut); IFACEMETHODIMP EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc); IFACEMETHODIMP DAdvise(FORMATETC *pformatetc , DWORD advf , IAdviseSink *pAdvSnk , DWORD *pdwConnection); IFACEMETHODIMP DUnadvise(DWORD dwConnection); IFACEMETHODIMP EnumDAdvise(IEnumSTATDATA **ppenumAdvise); private: ~SdkDataObject(void); SdkDataObject(const SdkDataObject&); SdkDataObject& operator = (const SdkDataObject&); HRESULT CopyMedium(STGMEDIUM* pMedDest, STGMEDIUM* pMedSrc, FORMATETC* pFmtSrc); HRESULT SetBlob(CLIPFORMAT cf, const void *pvBlob, UINT cbBlob); private: //!< The reference of count volatile LONG m_lRefCount; //!< The pointer to CDropSource object SdkDropSource *m_pDropSource; //!< The collection of DATASTORAGE_t structure vector<DATASTORAGE_t> m_dataStorageCL; }; #endif // _SDKDATAOBJECT_H_ #endif // __cplusplus
上面SdkDataObject.h定义了类SdkDataObject类,它实现了IDataObject接口,包括IUnknown接口。
几点说明如下:
1、SdkDataObject类里面也声明了拷贝构造、赋值操作符等,而且是私有的,就是不想让这个对象可以复制
2、IsDataAvailable:判断指定的格式是否支持。
3、GetGlobalData:得到全局的数据。
4、SetGlobalData:设置全局的数据。
5、CopyMedium:复制媒体数据。
6、上面列出的这些函数,几乎都是辅助函数,我设计时是根据我自己的业务需求来设计的,不同的需求可能不同,但最本质的都是实现一些COM接口。同时,我还定义了一个结构体DATASTORAGE_t,用来保存数据格式对,把设置的数据格式对存在一个vector中。
7、成员变量volatile LONG m_lRefCount,表示当前类的引用计数,构造函数一定要初始化为1,不能是0,其中volatile的意思就是说,告诉编译器不要其优化,每次要用访问这个值时,都是到它的地址上去取,而不是从寄存器中读取。
2. SdkDataObject.cpp的实现
2.1 构造函数
很简单,就是进行一些初始化操作,注意引用计数一定要是1,而不是0。SdkDataObject::SdkDataObject(SdkDropSource *pDropSource) { m_pDropSource = pDropSource; m_lRefCount = 1; }
2.2 析构函数
负责释放内存,这个函数是私有的,调用者只能调用Release来释放它。SdkDataObject::~SdkDataObject(void) { m_lRefCount = 0; int nSize = (int)m_dataStorageCL.size(); for (int i = 0; i < nSize; ++i) { DATASTORAGE_t dataEntry = m_dataStorageCL.at(i); ReleaseStgMedium(dataEntry.m_stgMedium); SAFE_DELETE(dataEntry.m_stgMedium); SAFE_DELETE(dataEntry.m_formatEtc); } }
2.3 QueryInterface
内部实现最终也是调用API来实现:STDMETHODIMP SdkDataObject::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(SdkDataObject, IDataObject), { 0 }, }; return QISearch(this, qit, riid, ppv); }
2.4 AddRef和Release
方法就相对简单了,几乎所有的COM接口实现都一样。STDMETHODIMP_(ULONG) SdkDataObject::AddRef() { return InterlockedIncrement(&m_lRefCount); } STDMETHODIMP_(ULONG) SdkDataObject::Release() { ULONG lRef = InterlockedDecrement(&m_lRefCount); if (0 == lRef) { delete this; } return m_lRefCount; }
2.5 GetData和SetData
相当重要的方法:就是向你写的Data Object要数据和传数据。内部必须把这些数据存起来。同时,这两个方法还依赖CopyMedium函数,这个用来复制数据。这个方法的实现后面会说明。GetDataHere这里没有实现,直接返回E_NOTIMPL。STDMETHODIMP SdkDataObject::GetData(FORMATETC *pformatetcIn, STGMEDIUM *pmedium) { if ( (NULL == pformatetcIn) || (NULL == pmedium) ) { return E_INVALIDARG; } pmedium->hGlobal = NULL; int nSize = (int)m_dataStorageCL.size(); for (int i = 0; i < nSize; ++i) { DATASTORAGE_t dataEntry = m_dataStorageCL.at(i); if( (pformatetcIn->tymed & dataEntry.m_formatEtc->tymed) && (pformatetcIn->dwAspect == dataEntry.m_formatEtc->dwAspect) && (pformatetcIn->cfFormat == dataEntry.m_formatEtc->cfFormat) ) { return CopyMedium(pmedium, dataEntry.m_stgMedium, dataEntry.m_formatEtc); } } return DV_E_FORMATETC; } STDMETHODIMP SdkDataObject::SetData(FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease) { if ( (NULL == pformatetc) || (NULL == pmedium) ) { return E_INVALIDARG; } if ( pformatetc->tymed != pmedium->tymed ) { return E_FAIL; } FORMATETC* fetc = new FORMATETC; STGMEDIUM* pStgMed = new STGMEDIUM; ZeroMemory(fetc, sizeof(FORMATETC)); ZeroMemory(pStgMed, sizeof(STGMEDIUM)); *fetc = *pformatetc; if ( TRUE == fRelease ) { *pStgMed = *pmedium; } else { CopyMedium(pStgMed, pmedium, pformatetc); } DATASTORAGE_t dataEntry = { fetc, pStgMed }; m_dataStorageCL.push_back(dataEntry); return S_OK; } STDMETHODIMP SdkDataObject::GetDataHere( FORMATETC *pformatetc , STGMEDIUM *pmedium) { UNREFERENCED_PARAMETER(pformatetc); UNREFERENCED_PARAMETER(pmedium); return E_NOTIMPL; }
2.6 QueryGetData函数
用来查询指定的数据是否支持,这个方法跟自己提供的IsDataAvailable功能相似,只是接口复杂一点,IsDataAvailable内部也是调用QueryGetData来实现的。STDMETHODIMP SdkDataObject::QueryGetData(FORMATETC *pformatetc) { if ( NULL == pformatetc ) { return E_INVALIDARG; } if ( !(DVASPECT_CONTENT & pformatetc->dwAspect) ) { return DV_E_DVASPECT; } HRESULT hr = DV_E_TYMED; int nSize = m_dataStorageCL.size(); for (int i = 0; i < nSize; ++i) { DATASTORAGE_t dataEnrty = m_dataStorageCL.at(i); if ( dataEnrty.m_formatEtc->tymed & pformatetc->tymed ) { if ( dataEnrty.m_formatEtc->cfFormat == pformatetc->cfFormat ) { return S_OK; } else { hr = DV_E_CLIPFORMAT; } } else { hr = DV_E_TYMED; } } return hr; }
2.7 EnumFormatEtc函数
一般我是调用Shell API来实现,这个方法很重要,用来说明当前这个Data Object支持什么格式。目前这里面只支持CF_UNICODETEXT。STDMETHODIMP SdkDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc) { if ( NULL == ppenumFormatEtc ) { return E_INVALIDARG; } *ppenumFormatEtc = NULL; HRESULT hr = E_NOTIMPL; if ( DATADIR_GET == dwDirection ) { FORMATETC rgfmtetc[] = { { CF_UNICODETEXT, NULL, DVASPECT_CONTENT, 0, TYMED_HGLOBAL }, }; hr = SHCreateStdEnumFmtEtc(ARRAYSIZE(rgfmtetc), rgfmtetc, ppenumFormatEtc); } return hr; }
IDataObject::DAdvise、IDataObject::EnumDAdvise和IDataObject::DUnadivise函数简单的返回OLE_E_ADVISENOTSUPPORTED。
STDMETHODIMP SdkDataObject::DAdvise(FORMATETC *pformatetc , DWORD advf , IAdviseSink *pAdvSnk , DWORD *pdwConnection) { UNREFERENCED_PARAMETER(pformatetc); UNREFERENCED_PARAMETER(advf); UNREFERENCED_PARAMETER(pAdvSnk); UNREFERENCED_PARAMETER(pdwConnection); return E_NOTIMPL; } STDMETHODIMP SdkDataObject::DUnadvise(DWORD dwConnection) { UNREFERENCED_PARAMETER(dwConnection); return E_NOTIMPL; } STDMETHODIMP SdkDataObject::EnumDAdvise(IEnumSTATDATA **ppenumAdvise) { UNREFERENCED_PARAMETER(ppenumAdvise); return E_NOTIMPL; }
2.8 CopyMedium实现
HRESULT SdkDataObject::CopyMedium(STGMEDIUM* pMedDest, STGMEDIUM* pMedSrc, FORMATETC* pFmtSrc) { if ( (NULL == pMedDest) || (NULL ==pMedSrc) || (NULL == pFmtSrc) ) { return E_INVALIDARG; } switch(pMedSrc->tymed) { case TYMED_HGLOBAL: pMedDest->hGlobal = (HGLOBAL)OleDuplicateData(pMedSrc->hGlobal, pFmtSrc->cfFormat, NULL); break; case TYMED_GDI: pMedDest->hBitmap = (HBITMAP)OleDuplicateData(pMedSrc->hBitmap, pFmtSrc->cfFormat, NULL); break; case TYMED_MFPICT: pMedDest->hMetaFilePict = (HMETAFILEPICT)OleDuplicateData(pMedSrc->hMetaFilePict, pFmtSrc->cfFormat, NULL); break; case TYMED_ENHMF: pMedDest->hEnhMetaFile = (HENHMETAFILE)OleDuplicateData(pMedSrc->hEnhMetaFile, pFmtSrc->cfFormat, NULL); break; case TYMED_FILE: pMedSrc->lpszFileName = (LPOLESTR)OleDuplicateData(pMedSrc->lpszFileName, pFmtSrc->cfFormat, NULL); break; case TYMED_ISTREAM: pMedDest->pstm = pMedSrc->pstm; pMedSrc->pstm->AddRef(); break; case TYMED_ISTORAGE: pMedDest->pstg = pMedSrc->pstg; pMedSrc->pstg->AddRef(); break; case TYMED_NULL: default: break; } pMedDest->tymed = pMedSrc->tymed; pMedDest->pUnkForRelease = NULL; if(pMedSrc->pUnkForRelease != NULL) { pMedDest->pUnkForRelease = pMedSrc->pUnkForRelease; pMedSrc->pUnkForRelease->AddRef(); } return S_OK; } HRESULT SdkDataObject::SetBlob(CLIPFORMAT cf, const void *pvBlob, UINT cbBlob) { void *pv = GlobalAlloc(GPTR, cbBlob); HRESULT hr = pv ? S_OK : E_OUTOFMEMORY; if ( SUCCEEDED(hr) ) { CopyMemory(pv, pvBlob, cbBlob); FORMATETC fmte = {cf, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; // The STGMEDIUM structure is used to define how to handle a global memory transfer. // This structure includes a flag, tymed, which indicates the medium // to be used, and a union comprising pointers and a handle for getting whichever // medium is specified in tymed. STGMEDIUM medium = {}; medium.tymed = TYMED_HGLOBAL; medium.hGlobal = pv; hr = this->SetData(&fmte, &medium, TRUE); if (FAILED(hr)) { GlobalFree(pv); } } return hr; }
下面给出了利用这个Data object 住剪切板复制一些数据。
这里调用了SetGlobalData函数来设置数据,上面没有给出这个实现,现在记住就行了,它内部是调用SetData来实现。设置的数据格式是CF_UNICODETEXT,因为目前这个DataObject只支持CF_UNICODETEXT格式,这个从EnumFormatEtc函数的实现就可以看出。你可以写一个控制台程序,加如下面两个方法,运行后,在记事本里按Ctrl +
V,看看是不是把Hello World.粘贴了。
HGLOBAL CreateGlobalHandle(IN void* ptr, int size) { HGLOBAL hGlobal = NULL; hGlobal = GlobalAlloc(GMEM_FIXED, size); if (NULL != hGlobal) { LPVOID pdest = GlobalLock(hGlobal); if (NULL != pdest) { memcpy_s(pdest, size, ptr, size); } GlobalUnlock(hGlobal); } return hGlobal; } void TestSdkDataObject() { OleInitialize(NULL); SdkDataObject *pDataObject = new SdkDataObject(NULL); WCHAR *pText = L"Hello world."; HGLOBAL hGlobal = CreateGlobalHandle((void*)pText, sizeof(WCHAR) * (wcslen(pText) + 1)); pDataObject->SetGlobalData(CF_UNICODETEXT, hGlobal, FALSE); HRESULT hr = OleSetClipboard(pDataObject); hr = OleFlushClipboard(); SAFE_RELEASE(pDataObject); OleUninitialize(); }
这一节,我们给出了SdkDataObject的实现,有些实现还是很简单,关键是要明白它的本质。
相关文章推荐
- 第三部分:实现IDataObject(OLE drag&drop之旅)
- 第三部分:实现IDataObject(OLE drag&drop之旅)
- 第三部分 MediaPlayer的主要实现分析
- Solr的自动完成实现方式(第三部分:Suggester方式续)
- 实现一个定制的3DListView——第三部分(final)
- SkyEye硬件模拟平台,第三部分: 硬件仿真实现之一
- [翻译]脚本引擎实现 - 第三部分 解析器
- 【译】PHP的变量实现(给PHP开发者的PHP源码-第三部分)
- SkyEye硬件模拟平台,第三部分: 硬件仿真实现之二
- Java密码学原型算法实现——第三部分:双线性对
- SkyEye硬件模拟平台,第三部分: 硬件仿真实现之三
- [翻译]脚本引擎实现 - 第三部分 解析器
- SkyEye硬件模拟平台,第三部分: 硬件仿真实现之四
- 一步步教你实现富文本编辑器(第三部分)
- [翻译]脚本引擎实现 - 第三部分 解析器
- [转]SkyEye硬件模拟平台,第三部分: 硬件仿真实现之一
- C#--第七周实验--任务2--从PhysicalObject中派生出移动物体类MovingObject并实现部分功能
- 第三部分:如何实现分页,包括两个函数,两个调用
- 嵌入式以太网第三部分——以太网协议实现
- SkyEye硬件模拟平台,第三部分: 硬件仿真实现之五