第二部分我们介绍了OLE数据传输的相关知识,这一节主要讲怎么实现一个IDataObject接口。然后再给出一个例子。
首先我们要明白,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接口,包括头文件SdkDataObject.h的定义和SdkDataObject.cpp的具体实现。SdkDataObject类继承自IDataObject,提供了如IsDataAvailable、GetGlobalData、SetGlobalData等方法。构造函数初始化引用计数,析构函数负责释放内存。关键方法如QueryInterface、AddRef、Release、GetData和SetData等的实现细节也在文中阐述。
2610

被折叠的 条评论
为什么被折叠?



