第二部分我们介绍了OLE数据传输的相关知识,这一节主要讲怎么实现一个IDataObject接口。然后再给出一个例子。
首先我们要明白,IDataObject是一个COM接口,我们就必须得创建一个类,实现这个接口的每一个方法,包括它的基类的方法。
#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的意思就是说,告诉编译器不要其优化,每次要用访问这个值时,都是到它的地址上去取,而不是从寄存器中读取。
很简单,就是进行一些初始化操作,注意引用计数一定要是1,而不是0。
SdkDataObject::SdkDataObject(SdkDropSource *pDropSource) { m_pDropSource = pDropSource; m_lRefCount = 1; }
负责释放内存,这个函数是私有的,调用者只能调用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); } }
内部实现最终也是调用API来实现:
STDMETHODIMP SdkDataObject::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(SdkDataObject, IDataObject), { 0 }, }; return QISearch(this, qit, riid, ppv); }
方法就相对简单了,几乎所有的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; }
相当重要的方法:就是向你写的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; }
用来查询指定的数据是否支持,这个方法跟自己提供的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; }
一般我是调用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; }
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; }
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(); }