OLE Drap/Drop(3)

上一张我们着重介绍了怎么样使用OLE和IDataObject来访问windows粘贴板。本章主要实现一个IDataObject接口,然后使用我们完成的数据对象来存储文本“Hello World”到粘贴板中。
创建一个COM接口-IDataObject
为了创建一个COM对象,我们需要定义一个实现所有这些函数的C++类,并且让COM的虚函数表为我们自动包含,我们使用C++类继承:
class CDataObject : public IDataObject
{
Public:
    // IUnknown members
    HRESULT __stdcall QueryInterface (REFIID iid, void ** ppvObject);
    ULONG   __stdcall AddRef (void);
    ULONG   __stdcall Release (void);
        
    // IDataObject members
    HRESULT __stdcall GetData (FORMATETC *pFormatEtc, STGMEDIUM *pmedium);
    HRESULT __stdcall GetDataHere (FORMATETC *pFormatEtc, STGMEDIUM *pmedium);
    HRESULT __stdcall QueryGetData (FORMATETC *pFormatEtc);
    HRESULT __stdcall GetCanonicalFormatEtc (FORMATETC *pFormatEct, FORMATETC *pFormatEtcOut);
    HRESULT __stdcall SetData (FORMATETC *pFormatEtc, STGMEDIUM *pMedium,  BOOL fRelease);
    HRESULT __stdcall EnumFormatEtc (DWORD dwDirection, IEnumFORMATETC **ppEnumFormatEtc);
    HRESULT __stdcall DAdvise (FORMATETC *pFormatEtc, DWORD advf, IAdviseSink *, DWORD *);
    HRESULT __stdcall DUnadvise (DWORD      dwConnection);
    HRESULT __stdcall EnumDAdvise (IEnumSTATDATA **ppEnumAdvise);
        stgmed
    // Constructor / Destructor
    CDataObject (FORMATETC *fmtetc, STGMEDIUM *, int count);
    ~CDataObject ()
private:
LONG m_lRefCount;
int LookupFormatEtc(FORMATETC *pFormatEtc);
};
上面列出了所有IDataObject成员,包括IUnknown接口成员,这是因为我们现在需要实现整个COM对象,因此每个成员必须正确的包含。
由于IUnknown函数我们在前面已经介绍了,我们继续介绍IDataObject函数。有些好的消息,同时也有些坏的消息;好的消息是,不是所有饿函数都需要实现,在IDataObject的9个函数中,我们仅仅需要实现3个来支持OLE的拖放操作,因此显著节省了我们的工作量。
坏的消息是:一般我们已经实现了IDataObject方法,我们需要实现完全独立的COM接口-IEnumFORMATETC接口。然而到这步还有很大的距离,因此让我们以一个简单分配新IDataObject的实例作为一个开始。
构造IDataObject
IDataObject的主要任务是允许一个消费者查询数据,这些查询从QueryData或EnumFormatEtc调用来发起的,因此,IDataObject需要知道存储什么样的数据格式,并且在消费者需要数据的时候,它能够提供。
我们因此需要找到一些办法来以FORMATETC结构的形式用真正的数据片来组装IDataObject且说明数据是什么。
IDataObject在C++类构造函数的时候组装,为了更弹性,可能需要添加内部帮助程序来执行这个任务,但对于我们简单实现仅在构造函数中使用。
CDataObject::CDataObject (FORMATETC *fmtetc, STGMEDIUM *stgmed, int count)

{
    // reference count must ALWAYS start at 1
    m_lRefCount    = 1;
    m_nNumFormats  = count;
 
    m_pFormatEtc   = new FORMATETC[count];
    m_pStgMedium   = new STGMEDIUM[count];
 
    for(int i = 0; i < count; i++)
    {
        m_pFormatEtc[i] = fmtetc[i];
        m_pStgMedium[i] = stgmed[i];
    }
}
构造函数执行两个重要的任务,首先是初始化COM对象引用记数为1。我看到过许多不正确的COM代码,他们初始化记数为0,COM规约明确地声明,一个COM对象必须以“1”作为生命周期的开始,如果你记得,一个记数为0的COM对象应该被删除,因此它应该从不应该被初始化为这个值。
第二个任务是在类构造函数中做一个私有的FORMATETC和STGMEDIUM的副本。数据对象不是每个STGMEDIUM结构体内部的所有者,它纯粹是引用并且在请求调用GetData的时候复制数据。
创建IDataObject对象
现在我们有一个定义良好的IDataObject构造函数,我可以写一个包装函数来隐藏类的细节:
HRESULT CreateDataObject (FORMATETC *fmtetc, STGMEDIUM *stgmeds, UINT count, IDataObject **ppDataObject)
{
    if(ppDataObject == 0)
        return E_INVALIDARG;
 
    *ppDataObject = new CDataObject (fmtetc, stgmeds, count);
 
    return (*ppDataObject) ? S_OK: E_OUTOFMEMORY;
}
现在创建一个IDataObject变的非常简单:
FORMATETC fmtetc = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
STGMEDIUM stgmed = {TYMED_HGLOBAL, {0}, 0};
 
stgmed.hGlobal = StringToHandle ("Hello, World!");
 
IDataObject *pDataObject;
 
CreateDataObject (&fmtetc, &stgmed, 1, &pDataObject);
许多IDataObject的实现包含许多接口内部执行内存分配的程序指定编码;在这个实现后面的思想是可以提供一个用于各种程序的通用IDataObject。好了,在创建数据对象之前有点工作需要做就是创建FORMATETC和STGMEDIUM结构,但这很容易被隔离,并且不会污染接口编码。
IDataObject::QueryGetData
该成员函数在某程序想检查IDataObject看是否包含指定类型的数据时候调用。一个指向FORMATETC结构的指针作为一个参数,且IDataObject::QueryGetData来检查这个结构且返回一个值来指示请求的数据是否可用。
HRESULT __stdcall IDataObject::QueryGetData(FORMATETC *pFormatEtc)
{
    return (LookupFormatEtc(pFormat) == -1) ? DV_E_FORMATETC : S_OK;
}
这个例子中的QueryGetData函数非常简单,我们放弃私有协助函数-LookupFormatEtc的所有工作:
int CDataObject::LookupFormatEtc(FORMATETC *pFormatEtc)
{
    // 轮流检查格式看是否能找到匹配的格式
    for(int i = 0; i < m_nNumFormats; i++)
    {
        if((m_pFormatEtc[i].tymed    &  pFormatEtc->tymed)   &&
            m_pFormatEtc[i].cfFormat == pFormatEtc->cfFormat &&
            m_pFormatEtc[i].dwAspect == pFormatEtc->dwAspect)
        {
            // return index of stored format
            return i;
        }
    }
 
    // error, format not found
    return -1;
}
上面的函数尽量在我们数据对象的可用结构中查找一个与指定FORMATETC结构匹配的对象,如果找到一个匹配的,就简单的返回相应m_pFormatEtc数组的索引,如果找不到,返回-1表示一个错误。
注意,在if从句中的位与操作符:
if( m_pFormatEtc[i].tymed & pFormatEtc->tymed ) 
AND操作符用在这里是因为FORMATETC::tymed成员实际上是一个位标志,它能够包含不止一个值;例如:QueryGetData的调用者可以完全指定一个FORMATETC::tymed值(TYMED_HGLOBAL|TYMED_ISTREAM)就意味着你支持HGLOBAL或IStream吗?
IDataObject::GetData
GetData函数和QueryGetData有许多相似之处,除了如果支持请求的数据格式,它必须返回指定的存储类型。
HRESULT __stdcall CDataObject::GetData (FORMATETC *pFormatEtc, STGMEDIUM *pStgMedium)
{
    int idx;
    // try to match the specified FORMATETC with one of our supported formats
    if((idx = LookupFormatEtc(pFormatEtc)) == -1)
        return DV_E_FORMATETC;
    // found a match - transfer data into supplied storage medium
    pMedium->tymed           = m_pFormatEtc[idx].tymed;
    pMedium->pUnkForRelease  = 0;
    // copy the data into the caller's storage medium
    switch(m_pFormatEtc[idx].tymed)
    {
    case TYMED_HGLOBAL:
        pMedium->hGlobal     = DupGlobalMem(m_pStgMedium[idx].hGlobal);
        break;
    default:
        return DV_E_FORMATETC;
    }
    return S_OK;
}
同样要调用内部协助函数LookupFormatEtc来检查是否支持请求的数据格式,如果支持,相应的STGMEDIUM数据被复制到调用者提供的结构。
注意,现在调用DupGlobalMem程序,这是一个协助函数,它返回指定HGLOBAL内存的HANDLE的副本,并且必须返回部分,因为每个GetData调用都要求一个新的数据副本。
HGLOBAL DupGlobalMemMem (HGLOBAL hMem)
{
    DWORD   len    = GlobalSize (hMem);
    PVOID   source = GlobalLock (hMem);
    PVOID   dest   = GlobalAlloc (GMEM_FIXED, len);
    memcpy (dest, source, len);
    GlobalUnlock (hMem);
    return dest;
}
我们需要同样的程序来支持TYMED_xxx存储类型,但现在我们设想实现的支持格式是IStream。
IDataObject::EnumFormatEtc
这是最后需要自己动手的成员,不幸的是这个成员函数实现如此简单,但也要求我们写IEnumFORMATETC对象。
HRESULT __stdcall CDataObject::EnumFormatEtc (DWORD dwDirection, IEnumFORMATETC **ppEnumFormatEtc)
{
    // OLE仅仅支持得到方向成员
    if(dwDirection == DATADIR_GET)
    {
        // 在WIN2K下,你可以调用AIP函数SHCreateStdEnumFmtEtc来完成,但为了支持//所有的window平台,我们需要实现IEnumFormatEtc。
        return CreateEnumFormatEtc(m_NumFormats, m_FormatEtc, ppEnumFormatEtc);
    }
    else
    {
        // the direction specified is not supported for drag+drop
        return E_NOTIMPL;
    }
}
看到上面的代码,你会提到SHCreateStdEnumFmtEtc这个API调用,它能够代表我们创建IEnumFORMATETC接口,不幸的是,这个API仅仅在WIN2K上可用,因此,我们需要提供其他创建IEnumFORMATETC对象。
因此下面的旅程中,我们将提供一个CreateEnumFormatEtc的完整实现,来代替Shell API调用。
不支持的IDataObject函数
仍然有一些IDataObject函数需要实现,而同时每个函数必须是一个有效的程序,有个简单的办法可以指定给OLE,我们不支持这些拖放操作以外的函数。
IDataObject::DAdvise、IDataObject::EnumDAdvise和IDataObject::DUnadivise函数简单的返回OLE_E_ADVISENOTSUPPORTED。
HRESULT CDataObject::DAdvise (FORMATETC *pFormatEtc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection)
{
    return OLE_E_ADVISENOTSUPPORTED;
}
 
HRESULT CDataObject::DUnadvise (DWORD dwConnection)
{
    return OLE_E_ADVISENOTSUPPORTED;
}
 
HRESULT CDataObject::EnumDAdvise (IEnumSTATDATA **ppEnumAdvise)
{
    return OLE_E_ADVISENOTSUPPORTED;
}
GetDataHere只需要实现IStream和IStorage接口来支持数据对象,在我们的例子中,我们只支持HGLOBAL数据,因此返回DATA_E_FORMATETC是一个明智的选择。
 
HRESULT CDataObject::GetDataHere (FORMATETC *pFormatEtc, STGMEDIUM *pMedium)
{
return DATA_E_FORMATETC;
}
SetData和GetCanonicalFormatEtc也只要简单的实现,本例中可以返回E_NOTIMPL值,即使我们返回错误的值,一个GetCanonicalFormatEtc记名票据,输出的FORMATETC结构ptd成员应该是0。
HRESULT CDataObject::GetCanonicalFormatEtc (FORMATETC *pFormatEct, FORMATETC *pFormatEtcOut)
{
    // Apparently we have to set this field to NULL even though we don't do anything else
    pFormatEtcOut->ptd = NULL;
    return E_NOTIMPL;
}
 
HRESULT CDataObject::SetData (FORMATETC *pFormatEtc, STGMEDIUM *pMedium,  BOOL fRelease)
{
    return E_NOTIMPL;
}
添加数据到粘贴板
好了,这里有一个简单那的程序用来通过OLE和数据对象来添加“Hello World”到Windows的粘贴板。
#include <windows.h>
int main(void)
{
    OleInitialize (0);
    IDataObject *pDataObject;
    FORMATETC fmtetc = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    STGMEDIUM stgmed = {TYMED_HGLOBAL, {0}, 0};
    stgmed.hGlobal = StringToHandle ("Hello, World!”, -1);
    // create the data object
    if (CreateDataObject(&fmtetc, &stgmed, 1, &pDataObject) == S_OK)
    {
        // add data to the clipboard
        OleSetClipboard (pDataObject);
        OleFlushClipboard ();
        pDataObject->Release();
    }
    // cleanup
    ReleaseStgMedium (&stgmed);
    OleUninitialize ();
    return 0;
}
不幸的是这个程序不能工作,因为我们还没有实现IEnumFORMATETC和CreateEnumFormatEtc函数。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值