OLE DrapDrop(3)

    技术2022-05-11  98

    上一张我们着重介绍了怎么样使用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函数。 

    最新回复(0)