/************************************************************************* ** ** OLE 2 Standard Utilities ** ** olestd.c ** ** This file contains utilities that are useful for most standard ** OLE 2.0 compound document type applications. ** ** (c) Copyright Microsoft Corp. 1992 All Rights Reserved ** *************************************************************************/ #include "precomp.h" #include "common.h" #include "utility.h" #include #include #include OLEDBGDATA #ifdef _DEBUG static TCHAR szAssertMemAlloc[] = TEXT("CoGetMalloc failed"); #endif static int IsCloseFormatEtc(FORMATETC FAR* pFetcLeft, FORMATETC FAR* pFetcRight); /* OleStdInitialize ** ---------------- ** Call to initialize this library sample code ** */ UINT _g_cfObjectDescriptor; UINT _g_cfLinkSrcDescriptor; UINT _g_cfEmbedSource; UINT _g_cfEmbeddedObject; UINT _g_cfLinkSource; UINT _g_cfOwnerLink; UINT _g_cfFileName; UINT _g_cfFileNameW; HINSTANCE _g_hOleStdInst; HINSTANCE _g_hOleStdResInst; #pragma code_seg(".text$initseg") STDAPI_(void) OleStdInitialize(HINSTANCE hInstance, HINSTANCE hResInstance) { _g_hOleStdInst = hInstance; _g_hOleStdResInst = hResInstance ? hResInstance : hInstance; _g_cfObjectDescriptor = RegisterClipboardFormat(CF_OBJECTDESCRIPTOR); _g_cfLinkSrcDescriptor = RegisterClipboardFormat(CF_LINKSRCDESCRIPTOR); _g_cfEmbedSource = RegisterClipboardFormat(CF_EMBEDSOURCE); _g_cfEmbeddedObject = RegisterClipboardFormat(CF_EMBEDDEDOBJECT); _g_cfLinkSource = RegisterClipboardFormat(CF_LINKSOURCE); _g_cfOwnerLink = RegisterClipboardFormat(CF_OWNERLINK); _g_cfFileName = RegisterClipboardFormat(CF_FILENAME); _g_cfFileNameW = RegisterClipboardFormat(CF_FILENAMEW); } #pragma code_seg() /* OleStdIsOleLink ** --------------- ** Returns TRUE if the OleObject is infact an OLE link object. this ** checks if IOleLink interface is supported. if so, the object is a ** link, otherwise not. */ STDAPI_(BOOL) OleStdIsOleLink(LPUNKNOWN lpUnk) { LPUNKNOWN lpOleLink; lpOleLink = OleStdQueryInterface(lpUnk, IID_IOleLink); if (lpOleLink) { OleStdRelease(lpOleLink); return TRUE; } return FALSE; } /* OleStdQueryInterface ** -------------------- ** Returns the desired interface pointer if exposed by the given object. ** Returns NULL if the interface is not available. ** eg.: ** lpDataObj = OleStdQueryInterface(lpOleObj, &IID_DataObject); */ STDAPI_(LPUNKNOWN) OleStdQueryInterface(LPUNKNOWN lpUnk, REFIID riid) { LPUNKNOWN lpInterface; HRESULT hrErr; hrErr = lpUnk->QueryInterface( riid, (LPVOID FAR*)&lpInterface ); if (hrErr == NOERROR) return lpInterface; else return NULL; } /* OleStdGetData ** ------------- ** Retrieve data from an IDataObject in a specified format on a ** global memory block. This function ALWAYS returns a private copy ** of the data to the caller. if necessary a copy is made of the ** data (ie. if lpMedium->pUnkForRelease != NULL). The caller assumes ** ownership of the data block in all cases and must free the data ** when done with it. The caller may directly free the data handle ** returned (taking care whether it is a simple HGLOBAL or a HANDLE ** to a MetafilePict) or the caller may call ** ReleaseStgMedium(lpMedium). this OLE helper function will do the ** right thing. ** ** PARAMETERS: ** LPDATAOBJECT lpDataObj -- object on which GetData should be ** called. ** CLIPFORMAT cfFormat -- desired clipboard format (eg. CF_TEXT) ** DVTARGETDEVICE FAR* lpTargetDevice -- target device for which ** the data should be composed. This may ** be NULL. NULL can be used whenever the ** data format is insensitive to target ** device or when the caller does not care ** what device is used. ** LPSTGMEDIUM lpMedium -- ptr to STGMEDIUM struct. the ** resultant medium from the ** IDataObject::GetData call is ** returned. ** ** RETURNS: ** HGLOBAL -- global memory handle of retrieved data block. ** NULL -- if error. */ STDAPI_(HGLOBAL) OleStdGetData( LPDATAOBJECT lpDataObj, CLIPFORMAT cfFormat, DVTARGETDEVICE FAR* lpTargetDevice, DWORD dwDrawAspect, LPSTGMEDIUM lpMedium) { HRESULT hrErr; FORMATETC formatetc; HGLOBAL hGlobal = NULL; HGLOBAL hCopy; LPVOID lp; formatetc.cfFormat = cfFormat; formatetc.ptd = lpTargetDevice; formatetc.dwAspect = dwDrawAspect; formatetc.lindex = -1; switch (cfFormat) { case CF_METAFILEPICT: formatetc.tymed = TYMED_MFPICT; break; case CF_BITMAP: formatetc.tymed = TYMED_GDI; break; default: formatetc.tymed = TYMED_HGLOBAL; break; } OLEDBG_BEGIN2(TEXT("IDataObject::GetData called\r\n")) hrErr = lpDataObj->GetData( (LPFORMATETC)&formatetc, lpMedium ); OLEDBG_END2 if (hrErr != NOERROR) return NULL; if ((hGlobal = lpMedium->hGlobal) == NULL) return NULL; // Check if hGlobal really points to valid memory if ((lp = GlobalLock(hGlobal)) != NULL) { if (IsBadReadPtr(lp, 1)) { GlobalUnlock(hGlobal); return NULL; // ERROR: memory is NOT valid } GlobalUnlock(hGlobal); } if (hGlobal != NULL && lpMedium->pUnkForRelease != NULL) { /* OLE2NOTE: the callee wants to retain ownership of the data. ** this is indicated by passing a non-NULL pUnkForRelease. ** thus, we will make a copy of the data and release the ** callee's copy. */ hCopy = OleDuplicateData(hGlobal, cfFormat, GHND|GMEM_SHARE); ReleaseStgMedium(lpMedium); // release callee's copy of data hGlobal = hCopy; lpMedium->hGlobal = hCopy; lpMedium->pUnkForRelease = NULL; } return hGlobal; } /* OleStdMalloc ** ------------ ** allocate memory using the currently active IMalloc* allocator */ STDAPI_(LPVOID) OleStdMalloc(ULONG ulSize) { LPVOID pout; LPMALLOC pmalloc; if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) { OleDbgAssertSz(0, szAssertMemAlloc); return NULL; } pout = (LPVOID)pmalloc->Alloc(ulSize); pmalloc->Release(); return pout; } /* OleStdRealloc ** ------------- ** re-allocate memory using the currently active IMalloc* allocator */ STDAPI_(LPVOID) OleStdRealloc(LPVOID pmem, ULONG ulSize) { LPVOID pout; LPMALLOC pmalloc; if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) { OleDbgAssertSz(0, szAssertMemAlloc); return NULL; } pout = (LPVOID)pmalloc->Realloc(pmem, ulSize); pmalloc->Release(); return pout; } /* OleStdFree ** ---------- ** free memory using the currently active IMalloc* allocator */ STDAPI_(void) OleStdFree(LPVOID pmem) { LPMALLOC pmalloc; if (pmem == NULL) return; if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) { OleDbgAssertSz(0, szAssertMemAlloc); return; } if (1 == pmalloc->DidAlloc(pmem)) { pmalloc->Free(pmem); } pmalloc->Release(); } /* OleStdGetSize ** ------------- ** Get the size of a memory block that was allocated using the ** currently active IMalloc* allocator. */ STDAPI_(ULONG) OleStdGetSize(LPVOID pmem) { ULONG ulSize; LPMALLOC pmalloc; if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) { OleDbgAssertSz(0, szAssertMemAlloc); return (ULONG)-1; } ulSize = (ULONG) pmalloc->GetSize(pmem); pmalloc->Release(); return ulSize; } /* OleStdLoadString ** ---------------- ** Load a string from resources. The string is allocated ** with the active IMalloc allocator. */ STDAPI_(LPTSTR) OleStdLoadString(HINSTANCE hInst, UINT nID) { LPTSTR lpszResult = (LPTSTR)OleStdMalloc(256 * sizeof(TCHAR)); if (lpszResult == NULL) return NULL; LoadString(hInst, nID, lpszResult, 256); return lpszResult; } /* OleStdCopyString ** ---------------- ** Copy a string into memory allocated with the currently active ** IMalloc* allocator. */ STDAPI_(LPTSTR) OleStdCopyString(LPTSTR lpszSrc) { UINT nSize = (lstrlen(lpszSrc)+1) * sizeof(TCHAR); LPTSTR lpszResult = (LPTSTR)OleStdMalloc(nSize); if (lpszResult == NULL) return NULL; memcpy(lpszResult, lpszSrc, nSize); return lpszResult; } /* * OleStdGetObjectDescriptorData * * Purpose: * Fills and returns a OBJECTDESCRIPTOR structure. * See OBJECTDESCRIPTOR for more information. * * Parameters: * clsid CLSID CLSID of object being transferred * dwDrawAspect DWORD Display Aspect of object * sizel SIZEL Size of object in HIMETRIC * pointl POINTL Offset from upper-left corner of object where mouse went * down for drag. Meaningful only when drag-drop is used. * dwStatus DWORD OLEMISC flags * lpszFullUserTypeName LPSTR Full User Type Name * lpszSrcOfCopy LPSTR Source of Copy * * Return Value: * HBGLOBAL Handle to OBJECTDESCRIPTOR structure. */ STDAPI_(HGLOBAL) OleStdGetObjectDescriptorData( CLSID clsid, DWORD dwDrawAspect, SIZEL sizel, POINTL pointl, DWORD dwStatus, LPTSTR lpszFullUserTypeName, LPTSTR lpszSrcOfCopy) { HGLOBAL hMem = NULL; IBindCtx FAR *pbc = NULL; LPOBJECTDESCRIPTOR lpOD; DWORD dwObjectDescSize, dwFullUserTypeNameLen, dwSrcOfCopyLen; // Get the length of Full User Type Name; Add 1 for the null terminator #if defined(WIN32) && !defined(UNICODE) dwFullUserTypeNameLen = lpszFullUserTypeName ? ATOWLEN(lpszFullUserTypeName) : 0; // Get the Source of Copy string and it's length; Add 1 for the null terminator if (lpszSrcOfCopy) dwSrcOfCopyLen = ATOWLEN(lpszSrcOfCopy); else { // No src moniker so use user type name as source string. lpszSrcOfCopy = lpszFullUserTypeName; dwSrcOfCopyLen = dwFullUserTypeNameLen; } #else dwFullUserTypeNameLen = lpszFullUserTypeName ? lstrlen(lpszFullUserTypeName)+1 : 0; // Get the Source of Copy string and it's length; Add 1 for the null terminator if (lpszSrcOfCopy) dwSrcOfCopyLen = lstrlen(lpszSrcOfCopy)+1; else { // No src moniker so use user type name as source string. lpszSrcOfCopy = lpszFullUserTypeName; dwSrcOfCopyLen = dwFullUserTypeNameLen; } #endif // Allocate space for OBJECTDESCRIPTOR and the additional string data dwObjectDescSize = sizeof(OBJECTDESCRIPTOR); hMem = GlobalAlloc(GHND|GMEM_SHARE, dwObjectDescSize + (dwFullUserTypeNameLen + dwSrcOfCopyLen) * sizeof(OLECHAR)); if (!hMem) return NULL; lpOD = (LPOBJECTDESCRIPTOR)GlobalLock(hMem); // Set the FullUserTypeName offset and copy the string if (lpszFullUserTypeName) { lpOD->dwFullUserTypeName = dwObjectDescSize; #if defined(WIN32) && !defined(UNICODE) ATOW((LPWSTR)((LPBYTE)lpOD+lpOD->dwFullUserTypeName), lpszFullUserTypeName, dwFullUserTypeNameLen); #else lstrcpy((LPTSTR)((LPBYTE)lpOD+lpOD->dwFullUserTypeName), lpszFullUserTypeName); #endif } else lpOD->dwFullUserTypeName = 0; // zero offset indicates that string is not present // Set the SrcOfCopy offset and copy the string if (lpszSrcOfCopy) { lpOD->dwSrcOfCopy = dwObjectDescSize + dwFullUserTypeNameLen * sizeof(OLECHAR); #if defined(WIN32) && !defined(UNICODE) ATOW((LPWSTR)((LPBYTE)lpOD+lpOD->dwSrcOfCopy), lpszSrcOfCopy, dwSrcOfCopyLen); #else lstrcpy((LPTSTR)((LPBYTE)lpOD+lpOD->dwSrcOfCopy), lpszSrcOfCopy); #endif } else lpOD->dwSrcOfCopy = 0; // zero offset indicates that string is not present // Initialize the rest of the OBJECTDESCRIPTOR lpOD->cbSize = dwObjectDescSize + (dwFullUserTypeNameLen + dwSrcOfCopyLen) * sizeof(OLECHAR); lpOD->clsid = clsid; lpOD->dwDrawAspect = dwDrawAspect; lpOD->sizel = sizel; lpOD->pointl = pointl; lpOD->dwStatus = dwStatus; GlobalUnlock(hMem); return hMem; } /* * OleStdFillObjectDescriptorFromData * * Purpose: * Fills and returns a OBJECTDESCRIPTOR structure. The source object will * offer CF_OBJECTDESCRIPTOR if it is an OLE2 object, CF_OWNERLINK if it * is an OLE1 object, or CF_FILENAME if it has been copied to the clipboard * by FileManager. * * Parameters: * lpDataObject LPDATAOBJECT Source object * lpmedium LPSTGMEDIUM Storage medium * lpcfFmt CLIPFORMAT FAR * Format offered by lpDataObject * (OUT parameter) * * Return Value: * HBGLOBAL Handle to OBJECTDESCRIPTOR structure. */ STDAPI_(HGLOBAL) OleStdFillObjectDescriptorFromData( LPDATAOBJECT lpDataObject, LPSTGMEDIUM lpmedium, CLIPFORMAT FAR* lpcfFmt) { CLSID clsid; SIZEL sizelHim; POINTL pointl; LPTSTR lpsz, szFullUserTypeName, szSrcOfCopy, szClassName, szDocName, szItemName; int nClassName, nDocName, nItemName, nFullUserTypeName; LPTSTR szBuf = NULL; HGLOBAL hMem = NULL; HKEY hKey = NULL; DWORD dw = OLEUI_CCHKEYMAX_SIZE; HGLOBAL hObjDesc; HRESULT hrErr; // GetData CF_OBJECTDESCRIPTOR format from the object on the clipboard. // Only OLE 2 objects on the clipboard will offer CF_OBJECTDESCRIPTOR hMem = OleStdGetData( lpDataObject, (CLIPFORMAT) _g_cfObjectDescriptor, NULL, DVASPECT_CONTENT, lpmedium); if (hMem) { *lpcfFmt = (CLIPFORMAT)_g_cfObjectDescriptor; return hMem; // Don't drop to clean up at the end of this function } // If CF_OBJECTDESCRIPTOR is not available, i.e. if this is not an OLE2 object, // check if this is an OLE 1 object. OLE 1 objects will offer CF_OWNERLINK else { hMem = OleStdGetData( lpDataObject, (CLIPFORMAT) _g_cfOwnerLink, NULL, DVASPECT_CONTENT, lpmedium); if (hMem) { *lpcfFmt = (CLIPFORMAT)_g_cfOwnerLink; // CF_OWNERLINK contains null-terminated strings for class name, document name // and item name with two null terminating characters at the end szClassName = (LPTSTR)GlobalLock(hMem); nClassName = lstrlen(szClassName); szDocName = szClassName + nClassName + 1; nDocName = lstrlen(szDocName); szItemName = szDocName + nDocName + 1; nItemName = lstrlen(szItemName); // Find FullUserTypeName from Registration database using class name if (RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hKey) != ERROR_SUCCESS) goto error; // Allocate space for szFullUserTypeName & szSrcOfCopy. Maximum length of FullUserTypeName // is OLEUI_CCHKEYMAX_SIZE. SrcOfCopy is constructed by concatenating FullUserTypeName, Document // Name and ItemName separated by spaces. szBuf = (LPTSTR)OleStdMalloc( (DWORD)2*OLEUI_CCHKEYMAX_SIZE+ (nDocName+nItemName+4)*sizeof(TCHAR)); if (NULL == szBuf) goto error; szFullUserTypeName = szBuf; szSrcOfCopy = szFullUserTypeName+OLEUI_CCHKEYMAX_SIZE+1; // Get FullUserTypeName if (RegQueryValue(hKey, NULL, szFullUserTypeName, (LONG*)&dw) != ERROR_SUCCESS) goto error; // Build up SrcOfCopy string from FullUserTypeName, DocumentName & ItemName lpsz = szSrcOfCopy; lstrcpy(lpsz, szFullUserTypeName); nFullUserTypeName = lstrlen(szFullUserTypeName); lpsz[nFullUserTypeName]= ' '; lpsz += nFullUserTypeName+1; lstrcpy(lpsz, szDocName); lpsz[nDocName] = ' '; lpsz += nDocName+1; lstrcpy(lpsz, szItemName); sizelHim.cx = sizelHim.cy = 0; pointl.x = pointl.y = 0; #if defined(WIN32) && !defined(UNICODE) OLECHAR wszClassName[OLEUI_CCHKEYMAX]; ATOW(wszClassName, szClassName, OLEUI_CCHKEYMAX); CLSIDFromProgID(wszClassName, &clsid); #else CLSIDFromProgID(szClassName, &clsid); #endif hObjDesc = OleStdGetObjectDescriptorData( clsid, DVASPECT_CONTENT, sizelHim, pointl, 0, szFullUserTypeName, szSrcOfCopy ); if (!hObjDesc) goto error; } else { BOOL fUnicode = TRUE; hMem = OleStdGetData( lpDataObject, (CLIPFORMAT) _g_cfFileNameW, NULL, DVASPECT_CONTENT, lpmedium); if (!hMem) { hMem = OleStdGetData( lpDataObject, (CLIPFORMAT) _g_cfFileName, NULL, DVASPECT_CONTENT, lpmedium); fUnicode = FALSE; } if (hMem) { *lpcfFmt = fUnicode ? (CLIPFORMAT)_g_cfFileNameW : (CLIPFORMAT)_g_cfFileName; lpsz = (LPTSTR)GlobalLock(hMem); if (!fUnicode) { OLECHAR wsz[OLEUI_CCHKEYMAX]; ATOW(wsz, (LPSTR)lpsz, OLEUI_CCHKEYMAX); hrErr = GetClassFile(wsz, &clsid); } else hrErr = GetClassFile((LPWSTR)lpsz, &clsid); /* OLE2NOTE: if the file does not have an OLE class ** associated, then use the OLE 1 Packager as the class of ** the object to be created. this is the behavior of ** OleCreateFromData API */ if (hrErr != NOERROR) CLSIDFromProgID(OLESTR("Package"), &clsid); sizelHim.cx = sizelHim.cy = 0; pointl.x = pointl.y = 0; #if defined(WIN32) && !defined(UNICODE) LPOLESTR wszBuf = NULL; szBuf = NULL; if (OleRegGetUserType(clsid, USERCLASSTYPE_FULL, &wszBuf) != NOERROR) { OleStdFree(wszBuf); goto error; } if (NULL != wszBuf) { UINT uLen = WTOALEN(wszBuf) + 1; szBuf = (LPTSTR) OleStdMalloc(uLen); if (NULL != szBuf) { WTOA(szBuf, wszBuf, uLen); } else { OleStdFree(wszBuf); goto error; } OleStdFree(wszBuf); } #else if (OleRegGetUserType(clsid, USERCLASSTYPE_FULL, &szBuf) != NOERROR) goto error; #endif hObjDesc = OleStdGetObjectDescriptorData( clsid, DVASPECT_CONTENT, sizelHim, pointl, 0, szBuf, lpsz ); if (!hObjDesc) goto error; } else goto error; } } // Check if object is CF_FILENAME // Clean up OleStdFree(szBuf); if (hMem) { GlobalUnlock(hMem); GlobalFree(hMem); } if (hKey) RegCloseKey(hKey); return hObjDesc; error: OleStdFree(szBuf); if (hMem) { GlobalUnlock(hMem); GlobalFree(hMem); } if (hKey) RegCloseKey(hKey); return NULL; } /* Call Release on the object that is NOT necessarily expected to go away. */ STDAPI_(ULONG) OleStdRelease(LPUNKNOWN lpUnk) { ULONG cRef; cRef = lpUnk->Release(); #ifdef _DEBUG { TCHAR szBuf[80]; wsprintf( szBuf, TEXT("refcnt = %ld after object (0x%lx) release\n"), cRef, lpUnk ); OleDbgOut4(szBuf); } #endif return cRef; } /* * OleStdMarkPasteEntryList * * Purpose: * Mark each entry in the PasteEntryList if its format is available from * the source IDataObject*. the dwScratchSpace field of each PasteEntry * is set to TRUE if available, else FALSE. * * Parameters: * LPOLEUIPASTEENTRY array of PasteEntry structures * int count of elements in PasteEntry array * LPDATAOBJECT source IDataObject* pointer * * Return Value: * none */ STDAPI_(void) OleStdMarkPasteEntryList( LPDATAOBJECT lpSrcDataObj, LPOLEUIPASTEENTRY lpPriorityList, int cEntries) { LPENUMFORMATETC lpEnumFmtEtc = NULL; #define FORMATETC_MAX 20 FORMATETC rgfmtetc[FORMATETC_MAX]; int i; HRESULT hrErr; DWORD j, cFetched; // Clear all marks for (i = 0; i < cEntries; i++) { lpPriorityList[i].dwScratchSpace = FALSE; if (! lpPriorityList[i].fmtetc.cfFormat) { // caller wants this item always considered available // (by specifying a NULL format) lpPriorityList[i].dwScratchSpace = TRUE; } else if (lpPriorityList[i].fmtetc.cfFormat == _g_cfEmbeddedObject || lpPriorityList[i].fmtetc.cfFormat == _g_cfEmbedSource || lpPriorityList[i].fmtetc.cfFormat == _g_cfFileName) { // if there is an OLE object format, then handle it // specially by calling OleQueryCreateFromData. the caller // need only specify one object type format. OLEDBG_BEGIN2(TEXT("OleQueryCreateFromData called\r\n")) hrErr = OleQueryCreateFromData(lpSrcDataObj); OLEDBG_END2 if(NOERROR == hrErr) lpPriorityList[i].dwScratchSpace = TRUE; } else if (lpPriorityList[i].fmtetc.cfFormat == _g_cfLinkSource) { // if there is OLE 2.0 LinkSource format, then handle it // specially by calling OleQueryLinkFromData. OLEDBG_BEGIN2(TEXT("OleQueryLinkFromData called\r\n")) hrErr = OleQueryLinkFromData(lpSrcDataObj); OLEDBG_END2 if(NOERROR == hrErr) { lpPriorityList[i].dwScratchSpace = TRUE; } } } OLEDBG_BEGIN2(TEXT("IDataObject::EnumFormatEtc called\r\n")) hrErr = lpSrcDataObj->EnumFormatEtc( DATADIR_GET, (LPENUMFORMATETC FAR*)&lpEnumFmtEtc ); OLEDBG_END2 if (hrErr != NOERROR) return; // unable to get format enumerator // Enumerate the formats offered by the source // Loop over all formats offered by the source cFetched = 0; memset(rgfmtetc,0,sizeof(rgfmtetc)); if (lpEnumFmtEtc->Next( FORMATETC_MAX, rgfmtetc, &cFetched) == NOERROR || (cFetched > 0 && cFetched <= FORMATETC_MAX) ) { for (j = 0; j < cFetched; j++) { for (i = 0; i < cEntries; i++) { if (!lpPriorityList[i].dwScratchSpace && IsCloseFormatEtc(&lpPriorityList[i].fmtetc, &rgfmtetc[j])) { lpPriorityList[i].dwScratchSpace = TRUE; } } } } // endif OleStdRelease((LPUNKNOWN)lpEnumFmtEtc); } // returns 1 for a close match // (all fields match exactly except the tymed which simply overlaps) // 0 for no match int IsCloseFormatEtc(FORMATETC FAR* pFetcLeft, FORMATETC FAR* pFetcRight) { if (pFetcLeft->cfFormat != pFetcRight->cfFormat) return 0; else if (!OleStdCompareTargetDevice (pFetcLeft->ptd, pFetcRight->ptd)) return 0; if (pFetcLeft->dwAspect != pFetcRight->dwAspect) return 0; return((pFetcLeft->tymed | pFetcRight->tymed) != 0); }