Dict.CN 在线词典, 英语学习, 在线翻译

学海苦作舟,书山勤为径

留下点回忆

常用链接

统计

积分与排名

Denoise

English study

Web技术

数据压缩

一些连接

最新评论

第三部分:实现IDataObject(OLE drag&drop之旅)

上一张我们着重介绍了怎么样使用OLEIDataObject来访问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函数。有些好的消息,同时也有些坏的消息;好的消息是,不是所有饿函数都需要实现,在IDataObject9个函数中,我们仅仅需要实现3个来支持OLE的拖放操作,因此显著节省了我们的工作量。

坏的消息是:一般我们已经实现了IDataObject方法,我们需要实现完全独立的COM接口-IEnumFORMATETC接口。然而到这步还有很大的距离,因此让我们以一个简单分配新IDataObject的实例作为一个开始。

构造IDataObject

IDataObject的主要任务是允许一个消费者查询数据,这些查询从QueryDataEnumFormatEtc调用来发起的,因此,IDataObject需要知道存储什么样的数据格式,并且在消费者需要数据的时候,它能够提供。

我们因此需要找到一些办法来以FORMATETC结构的形式用真正的数据片来组装IDataObject且说明数据是什么。

IDataObjectC++类构造函数的时候组装,为了更弹性,可能需要添加内部帮助程序来执行这个任务,但对于我们简单实现仅在构造函数中使用。

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代码,他们初始化记数为0COM规约明确地声明,一个COM对象必须以“1”作为生命周期的开始,如果你记得,一个记数为0COM对象应该被删除,因此它应该从不应该被初始化为这个值。

第二个任务是在类构造函数中做一个私有的FORMATETCSTGMEDIUM的副本。数据对象不是每个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。好了,在创建数据对象之前有点工作需要做就是创建FORMATETCSTGMEDIUM结构,但这很容易被隔离,并且不会污染接口编码。

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)就意味着你支持HGLOBALIStream吗?

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::DAdviseIDataObject::EnumDAdviseIDataObject::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只需要实现IStreamIStorage接口来支持数据对象,在我们的例子中,我们只支持HGLOBAL数据,因此返回DATA_E_FORMATETC是一个明智的选择。

 

HRESULT CDataObject::GetDataHere (FORMATETC *pFormatEtc, STGMEDIUM *pMedium)
{
return DATA_E_FORMATETC;
}

SetDataGetCanonicalFormatEtc也只要简单的实现,本例中可以返回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;
}
不幸的是这个程序不能工作,因为我们还没有实现IEnumFORMATETCCreateEnumFormatEtc函数。

posted on 2006-03-03 10:08 笨笨 阅读(7565) 评论(3)  编辑 收藏 引用 所属分类: OLE Drag&Drop

评论

# re: 第三部分:实现IDataObject(OLE drag&drop之旅) 2006-11-09 10:47 heng

对这个有异议:
构造函数执行两个重要的任务,首先是初始化COM对象引用记数为1。我看到过许多不正确的COM代码,他们初始化记数为0。

证据:
来自 atlcom.h
class CComObjectRootBase
{
public:
CComObjectRootBase()
{
m_dwRef = 0L;
}
....
}

  回复  更多评论   

# re: 第三部分:实现IDataObject(OLE drag&drop之旅) 2007-08-29 17:45 wu

COM 对象构造时 m_cRef = 0;
在QueryInterface 内部 会 m_cRef++;  回复  更多评论   

# re: 第三部分:实现IDataObject(OLE drag&drop之旅) 2013-04-11 09:52 张瑞强

没有析构函数申请了大量内存  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理