第三部分:IDataObject实现

第二部分我们介绍了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实现)