Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

765 lines
26 KiB

/*************************************************************************
**
** 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 <stdlib.h>
#include <shellapi.h>
#include <wchar.h>
#include <reghelp.hxx>
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
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;
}
// 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;
lstrcpy((LPTSTR)((LPBYTE)lpOD+lpOD->dwFullUserTypeName), lpszFullUserTypeName);
}
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);
lstrcpy((LPTSTR)((LPBYTE)lpOD+lpOD->dwSrcOfCopy), lpszSrcOfCopy);
}
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 (OpenClassesRootKey(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;
CLSIDFromProgID(szClassName, &clsid);
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 (OleRegGetUserType(clsid, USERCLASSTYPE_FULL, &szBuf) != NOERROR)
goto error;
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);
}