您的位置:首页 > 移动开发 > Objective-C

第三部分:IDataObject实现

2013-02-25 14:53 387 查看
第二部分我们介绍了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的实现,有些实现还是很简单,关键是要明白它的本质。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: