/*************************************************************************
**
**    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-1996 All Rights Reserved
**
*************************************************************************/

#define NONAMELESSUNION     // use strict ANSI standard (for DVOBJ.H)

#define STRICT  1
#include "olestd.h"
#include <stdlib.h>
#include <ctype.h>
#include <shellapi.h>
#include "common.h"

OLEDBGDATA

static char szAssertMemAlloc[] = "CoGetMalloc failed";
static int IsCloseFormatEtc(FORMATETC FAR* pFetcLeft, FORMATETC FAR* pFetcRight);


/* OleStdSetupAdvises
** ------------------
**    Setup the standard View advise required by a standard,
**    compound document-oriented container. Such a container relies on
**    Ole to manage the presentation of the Ole object. The container
**    call IViewObject::Draw to render (display) the object.
**
**    This helper routine performs the following tasks:
**                      setup View advise
**                      Call IOleObject::SetHostNames
**                      Call OleSetContainedObject
**
**    fCreate should be set to TRUE if the object is being created. if
**    an existing object is being loaded, then fCreate should be FALSE.
**    if it is a creation situation, then the ADVF_PRIMEFIRST flag is
**    used when settinp up the IViewObject::Advise. This will result in
**    the immediate sending of the initial picture.
**
**    NOTE: the standard container does NOT need to set up an OLE
**    Advise (IOleObject::Advise). this routine does NOT set up an OLE
**    Advise (a previous version of this function used to setup this
**    advise, but it was not useful).
*/
STDAPI_(BOOL) OleStdSetupAdvises(LPOLEOBJECT lpOleObject, DWORD dwDrawAspect,
               LPOLESTR lpszContainerApp, LPOLESTR lpszContainerObj,
               LPADVISESINK lpAdviseSink, BOOL fCreate)
{
   LPVIEWOBJECT lpViewObject;
   HRESULT hrErr;
   BOOL fStatus = TRUE;
#if defined( SPECIAL_CONTAINER )
   DWORD dwTemp;
#endif

   hrErr = lpOleObject->lpVtbl->QueryInterface(
         lpOleObject,
         &IID_IViewObject,
         (LPVOID FAR*)&lpViewObject
   );

   /* Setup View advise */
   if (hrErr == NOERROR) {

      OLEDBG_BEGIN2("IViewObject::SetAdvise called\r\n")
      lpViewObject->lpVtbl->SetAdvise(
            lpViewObject,
            dwDrawAspect,
            (fCreate ? ADVF_PRIMEFIRST : 0),
            lpAdviseSink
      );
      OLEDBG_END2

      OleStdRelease((LPUNKNOWN)lpViewObject);
   } else {
      fStatus = FALSE;
   }

#if defined( SPECIAL_CONTAINER )
   /* Setup OLE advise.
   **    NOTE: normally containers do NOT need to setup an OLE
   **    advise. this advise connection is only useful for the OLE's
   **    DefHandler and the OleLink object implementation. some
   **    special container's might need to setup this advise for
   **    programatic reasons.
   **
   **    NOTE: this advise will be torn down automatically by the
   **    server when we release the object, therefore we do not need
   **    to store the connection id.
   */
   OLEDBG_BEGIN2("IOleObject::Advise called\r\n")
   hrErr = lpOleObject->lpVtbl->Advise(
         lpOleObject,
         lpAdviseSink,
         (DWORD FAR*)&dwTemp
   );
   OLEDBG_END2
   if (hrErr != NOERROR) fStatus = FALSE;
#endif

   /* Setup the host names for the OLE object. */
   OLEDBG_BEGIN2("IOleObject::SetHostNames called\r\n")
   hrErr = lpOleObject->lpVtbl->SetHostNames(
         lpOleObject,
         lpszContainerApp,
         lpszContainerObj
   );
   OLEDBG_END2
   if (hrErr != NOERROR) fStatus = FALSE;

   /* Inform the loadded object's handler/inproc-server that it is in
   **    its embedding container's process.
   */
   OLEDBG_BEGIN2("OleSetContainedObject(TRUE) called\r\n")
   OleSetContainedObject((LPUNKNOWN)lpOleObject, TRUE);
   OLEDBG_END2

   return fStatus;
}


/* OleStdSwitchDisplayAspect
** -------------------------
**    Switch the currently cached display aspect between DVASPECT_ICON
**    and DVASPECT_CONTENT.
**
**    NOTE: when setting up icon aspect, any currently cached content
**    cache is discarded and any advise connections for content aspect
**    are broken.
**
**    RETURNS:
**      S_OK -- new display aspect setup successfully
**      E_INVALIDARG -- IOleCache interface is NOT supported (this is
**                  required).
**      <other SCODE> -- any SCODE that can be returned by
**                  IOleCache::Cache method.
**      NOTE: if an error occurs then the current display aspect and
**            cache contents unchanged.
*/
STDAPI OleStdSwitchDisplayAspect(
      LPOLEOBJECT             lpOleObj,
      LPDWORD                 lpdwCurAspect,
      DWORD                   dwNewAspect,
      HGLOBAL                 hMetaPict,
      BOOL                    fDeleteOldAspect,
      BOOL                    fSetupViewAdvise,
      LPADVISESINK            lpAdviseSink,
      BOOL FAR*               lpfMustUpdate
)
{
   LPOLECACHE      lpOleCache = NULL;
   LPVIEWOBJECT    lpViewObj = NULL;
   LPENUMSTATDATA  lpEnumStatData = NULL;
   STATDATA        StatData;
   FORMATETC       FmtEtc;
   STGMEDIUM       Medium;
   DWORD           dwAdvf;
   DWORD           dwNewConnection;
   DWORD           dwOldAspect = *lpdwCurAspect;
   HRESULT         hrErr;

   if (lpfMustUpdate)
      *lpfMustUpdate = FALSE;

   lpOleCache = (LPOLECACHE)OleStdQueryInterface(
                              (LPUNKNOWN)lpOleObj,&IID_IOleCache);

   // if IOleCache* is NOT available, do nothing
   if (! lpOleCache)
      return ResultFromScode(E_INVALIDARG);

   // Setup new cache with the new aspect
   FmtEtc.cfFormat = 0;     // whatever is needed to draw
   FmtEtc.ptd      = NULL;
   FmtEtc.dwAspect = dwNewAspect;
   FmtEtc.lindex   = -1;
   FmtEtc.tymed    = TYMED_NULL;

   /* NOTE: if we are setting up Icon aspect with a custom icon
   **    then we do not want DataAdvise notifications to ever change
   **    the contents of the data cache. thus we set up a NODATA
   **    advise connection. otherwise we set up a standard DataAdvise
   **    connection.
   */
   if (dwNewAspect == DVASPECT_ICON && hMetaPict)
      dwAdvf = ADVF_NODATA;
   else
      dwAdvf = ADVF_PRIMEFIRST;

   OLEDBG_BEGIN2("IOleCache::Cache called\r\n")
   hrErr = lpOleCache->lpVtbl->Cache(
         lpOleCache,
         (LPFORMATETC)&FmtEtc,
         dwAdvf,
         (LPDWORD)&dwNewConnection
   );
   OLEDBG_END2

   if (! SUCCEEDED(hrErr)) {
      OleDbgOutHResult("IOleCache::Cache returned", hrErr);
      OleStdRelease((LPUNKNOWN)lpOleCache);
      return hrErr;
   }

   *lpdwCurAspect = dwNewAspect;

   /* NOTE: if we are setting up Icon aspect with a custom icon,
   **    then stuff the icon into the cache. otherwise the cache must
   **    be forced to be updated. set the *lpfMustUpdate flag to tell
   **    caller to force the object to Run so that the cache will be
   **    updated.
   */
   if (dwNewAspect == DVASPECT_ICON && hMetaPict) {

      FmtEtc.cfFormat = CF_METAFILEPICT;
      FmtEtc.ptd      = NULL;
      FmtEtc.dwAspect = DVASPECT_ICON;
      FmtEtc.lindex   = -1;
      FmtEtc.tymed    = TYMED_MFPICT;

      Medium.tymed            = TYMED_MFPICT;
      Medium.u.hGlobal        = hMetaPict;
      Medium.pUnkForRelease   = NULL;

      OLEDBG_BEGIN2("IOleCache::SetData called\r\n")
      hrErr = lpOleCache->lpVtbl->SetData(
            lpOleCache,
            (LPFORMATETC)&FmtEtc,
            (LPSTGMEDIUM)&Medium,
            FALSE   /* fRelease */
      );
      OLEDBG_END2
   } else {
      if (lpfMustUpdate)
         *lpfMustUpdate = TRUE;
   }

   if (fSetupViewAdvise && lpAdviseSink) {
      /* NOTE: re-establish the ViewAdvise connection */
      lpViewObj = (LPVIEWOBJECT)OleStdQueryInterface(
                              (LPUNKNOWN)lpOleObj,&IID_IViewObject);

      if (lpViewObj) {

         OLEDBG_BEGIN2("IViewObject::SetAdvise called\r\n")
         lpViewObj->lpVtbl->SetAdvise(
               lpViewObj,
               dwNewAspect,
               0,
               lpAdviseSink
         );
         OLEDBG_END2

         OleStdRelease((LPUNKNOWN)lpViewObj);
      }
   }

   /* NOTE: remove any existing caches that are set up for the old
   **    display aspect. It WOULD be possible to retain the caches set
   **    up for the old aspect, but this would increase the storage
   **    space required for the object and possibly require additional
   **    overhead to maintain the unused cachaes. For these reasons the
   **    strategy to delete the previous caches is prefered. if it is a
   **    requirement to quickly switch between Icon and Content
   **    display, then it would be better to keep both aspect caches.
   */

   if (fDeleteOldAspect) {
      OLEDBG_BEGIN2("IOleCache::EnumCache called\r\n")
      hrErr = lpOleCache->lpVtbl->EnumCache(
            lpOleCache,
            (LPENUMSTATDATA FAR*)&lpEnumStatData
      );
      OLEDBG_END2

      while(hrErr == NOERROR) {
         hrErr = lpEnumStatData->lpVtbl->Next(
               lpEnumStatData,
               1,
               (LPSTATDATA)&StatData,
               NULL
         );
         if (hrErr != NOERROR)
            break;              // DONE! no more caches.

         if (StatData.formatetc.dwAspect == dwOldAspect) {

            // Remove previous cache with old aspect
            OLEDBG_BEGIN2("IOleCache::Uncache called\r\n")
            lpOleCache->lpVtbl->Uncache(lpOleCache,StatData.dwConnection);
            OLEDBG_END2
         }
      }

      if (lpEnumStatData) {
         OleStdVerifyRelease(
               (LPUNKNOWN)lpEnumStatData,
               OLESTR ("OleStdSwitchDisplayAspect: Cache enumerator NOT released"));
      }
   }

   if (lpOleCache)
      OleStdRelease((LPUNKNOWN)lpOleCache);

   return NOERROR;
}


/* OleStdSetIconInCache
** --------------------
**    SetData a new icon into the existing DVASPECT_ICON cache.
**
**    RETURNS:
**      HRESULT returned from IOleCache::SetData
*/
STDAPI OleStdSetIconInCache(LPOLEOBJECT lpOleObj, HGLOBAL hMetaPict)
{
   LPOLECACHE      lpOleCache = NULL;
   FORMATETC       FmtEtc;
   STGMEDIUM       Medium;
   HRESULT         hrErr;

   if (! hMetaPict)
      return FALSE;   // invalid icon

   lpOleCache = (LPOLECACHE)OleStdQueryInterface(
                              (LPUNKNOWN)lpOleObj,&IID_IOleCache);
   if (! lpOleCache)
      return FALSE;   // if IOleCache* is NOT available, do nothing

   FmtEtc.cfFormat = CF_METAFILEPICT;
   FmtEtc.ptd      = NULL;
   FmtEtc.dwAspect = DVASPECT_ICON;
   FmtEtc.lindex   = -1;
   FmtEtc.tymed    = TYMED_MFPICT;

   // stuff the icon into the cache.
   Medium.tymed            = TYMED_MFPICT;
   Medium.u.hGlobal        = hMetaPict;
   Medium.pUnkForRelease   = NULL;

   OLEDBG_BEGIN2("IOleCache::SetData called\r\n")
   hrErr = lpOleCache->lpVtbl->SetData(
         lpOleCache,
         (LPFORMATETC)&FmtEtc,
         (LPSTGMEDIUM)&Medium,
         FALSE   /* fRelease */
   );
   OLEDBG_END2

   OleStdRelease((LPUNKNOWN)lpOleCache);

   return hrErr;
}



/* OleStdDoConvert
** ---------------
** Do the container-side responsibilities for converting an object.
**    This function would be used in conjunction with the OleUIConvert
**    dialog. If the user selects to convert an object then the
**    container must do the following:
**          1. unload the object.
**          2. write the NEW CLSID and NEW user type name
**              string into the storage of the object,
**              BUT write the OLD format tag.
**          3. force an update of the object to force the actual
**              conversion of the data bits.
**
**    This function takes care of step 2.
*/
STDAPI OleStdDoConvert(LPSTORAGE lpStg, REFCLSID rClsidNew)
{
   HRESULT error;
   CLSID clsidOld;
   CLIPFORMAT cfOld;
   LPOLESTR lpszOld = NULL;
   OLECHAR szNew[OLEUI_CCHKEYMAX];

   if ((error = ReadClassStg(lpStg, &clsidOld)) != NOERROR) {
      clsidOld = CLSID_NULL;
      goto errRtn;
   }

   // read old fmt/old user type; sets out params to NULL on error
   error = ReadFmtUserTypeStg(lpStg, &cfOld, &lpszOld);
   OleDbgAssert(error == NOERROR || (cfOld == 0 && lpszOld == NULL));

   // get new user type name; if error, set to NULL string
   if (OleStdGetUserTypeOfClass(
         (LPCLSID)rClsidNew, szNew, OLEUI_CCHKEYMAX,NULL /* hKey */) == 0)
      OLESTRCPY (szNew, OLESTR (""));

   // write class stg
   if ((error = WriteClassStg(lpStg, rClsidNew)) != NOERROR)
      goto errRtn;

   // write old fmt/new user type;
   if ((error = WriteFmtUserTypeStg(lpStg, cfOld, szNew)) != NOERROR)
      goto errRewriteInfo;

   // set convert bit
   if ((error = SetConvertStg(lpStg, TRUE)) != NOERROR)
      goto errRewriteInfo;

   goto okRtn;

errRewriteInfo:
   (void)WriteClassStg(lpStg, &clsidOld);
   (void)WriteFmtUserTypeStg(lpStg, cfOld, lpszOld);

errRtn:

okRtn:
   OleStdFreeString(lpszOld, NULL);
   return error;
}


/* OleStdGetTreatAsFmtUserType
** ---------------------------
**    Determine if the application should perform a TreatAs (ActivateAs
**    object or emulation) operation for the object that is stored in
**    the storage.
**
**    if the CLSID written in the storage is not the same as the
**    application's own CLSID (clsidApp), then a TreatAs operation
**    should take place. if so determine the format the data should be
**    written and the user type name of the object the app should
**    emulate (ie. pretend to be). if this information is not written
**    in the storage then it is looked up in the REGDB. if it can not
**    be found in the REGDB, then the TreatAs operation can NOT be
**    executed.
**
**    RETURNS: TRUE -- if TreatAs should be performed.
**               valid lpclsid, lplpszType, lpcfFmt to TreatAs are returned
**                      (NOTE: lplpszType must be freed by caller)
**             FALSE -- NO TreatAs. lpszType will be NULL.
**               lpclsid = CLSID_NULL; lplpszType = lpcfFmt = NULL;
*/
STDAPI_(BOOL) OleStdGetTreatAsFmtUserType(
      REFCLSID        rclsidApp,
      LPSTORAGE       lpStg,
      CLSID FAR*      lpclsid,
      CLIPFORMAT FAR* lpcfFmt,
      LPOLESTR FAR*   lplpszType
)
{
   HRESULT hrErr;
   HKEY    hKey;
   LONG    lRet;
   UINT    lSize;
   OLECHAR szBuf[OLEUI_CCHKEYMAX];

   *lpclsid    = CLSID_NULL;
   *lpcfFmt    = 0;
   *lplpszType = NULL;

   hrErr = ReadClassStg(lpStg, lpclsid);
   if (hrErr == NOERROR &&
      ! IsEqualCLSID(lpclsid, &CLSID_NULL) &&
      ! IsEqualCLSID(lpclsid, rclsidApp)) {

      hrErr = ReadFmtUserTypeStg(lpStg,(CLIPFORMAT FAR*)lpcfFmt,lplpszType);
      if (hrErr == NOERROR && lplpszType && *lpcfFmt != 0)
         return TRUE;    // Do TreatAs. info was in lpStg.

      /* read info from REGDB
      **    *lpcfFmt = value of field: CLSID\{...}\DataFormats\DefaultFile
      **    *lplpszType = value of field: CLSID\{...}
      */
      //Open up the root key.
      lRet=RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hKey);
      if (lRet != (LONG)ERROR_SUCCESS)
         return FALSE;
      *lpcfFmt = OleStdGetDefaultFileFormatOfClass(lpclsid, hKey);
      if (*lpcfFmt == 0)
         return FALSE;
      lSize = OleStdGetUserTypeOfClass(lpclsid,szBuf,OLEUI_CCHKEYMAX, hKey);
      if (lSize == 0)
         return FALSE;
      *lplpszType = OleStdCopyString(szBuf, NULL);
   } else {
      return FALSE;       // NO TreatAs
   }
}



/* OleStdDoTreatAsClass
** --------------------
** Do the container-side responsibilities for "ActivateAs" (aka.
**    TreatAs) for an object.
**    This function would be used in conjunction with the OleUIConvert
**    dialog. If the user selects to ActivateAs an object then the
**    container must do the following:
**          1. unload ALL objects of the OLD class that app knows about
**          2. add the TreatAs tag in the registration database
**              by calling CoTreatAsClass().
**          3. lazily it can reload the objects; when the objects
**              are reloaded the TreatAs will take effect.
**
**    This function takes care of step 2.
*/
STDAPI OleStdDoTreatAsClass(LPOLESTR lpszUserType, REFCLSID rclsid, REFCLSID rclsidNew)
{
   HRESULT hrErr;
   LPOLESTR   lpszCLSID = NULL;
   char       lpszAnsiCLSID[256];
   LONG    lRet;
   HKEY    hKey;
   char    lpszAnsiUserType[256];

   OLEDBG_BEGIN2("CoTreatAsClass called\r\n")
   hrErr = CoTreatAsClass(rclsid, rclsidNew);
   OLEDBG_END2

   if ((hrErr != NOERROR) && lpszUserType) {
      lRet = RegOpenKey(HKEY_CLASSES_ROOT, (LPCSTR)"CLSID",
            (HKEY FAR *)&hKey);
      StringFromCLSID(rclsid, /*(LPOLESTR FAR*)*/&lpszCLSID);
      W2A (lpszCLSID, lpszAnsiCLSID, 256);
      W2A (lpszUserType, lpszAnsiUserType, 256);
      RegSetValue(hKey, lpszAnsiCLSID, REG_SZ, lpszAnsiUserType,
            lstrlen(lpszAnsiUserType));

      if (lpszCLSID)
         OleStdFreeString(lpszCLSID, NULL);

      hrErr = CoTreatAsClass(rclsid, rclsidNew);
      RegCloseKey(hKey);
   }

   return hrErr;
}



/* 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)
{
   LPOLELINK lpOleLink;

   lpOleLink = (LPOLELINK)OleStdQueryInterface(lpUnk, &IID_IOleLink);

   if (lpOleLink) {
      OleStdRelease((LPUNKNOWN)lpOleLink);
      return TRUE;
   } else
      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->lpVtbl->QueryInterface(
         lpUnk,
         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("IDataObject::GetData called\r\n")
   hrErr = lpDataObj->lpVtbl->GetData(
         lpDataObj,
         (LPFORMATETC)&formatetc,
         lpMedium
   );
   OLEDBG_END2

   if (hrErr != NOERROR)
      return NULL;

   if ((hGlobal = lpMedium->u.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) {
      /* NOTE: 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->u.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->lpVtbl->Alloc(pmalloc, ulSize);

   if (pmalloc != NULL) {
      ULONG refs = pmalloc->lpVtbl->Release(pmalloc);
   }

   return pout;
}


/* OleStdRealloc
** -------------
**    re-allocate memory using the currently active IMalloc* allocator
*/
STDAPI_(LPVOID) OleStdRealloc(LPVOID pmem, ULONG ulSize)
{
   LPVOID pout = NULL;
   LPMALLOC pmalloc;

   if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) {
      OleDbgAssertSz(0, szAssertMemAlloc);
      return NULL;
   }

   if (pmalloc != NULL)
   {
       ULONG refs;
       pout = (LPVOID)pmalloc->lpVtbl->Realloc(pmalloc, pmem, ulSize);
       refs = pmalloc->lpVtbl->Release(pmalloc);
   }

   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 (pmalloc != NULL)
    {
        ULONG refs;
#if defined(_DEBUG)
        int iReturn;
        iReturn = pmalloc->lpVtbl->DidAlloc(pmalloc, pmem);
        OleDbgAssertSz(1 == iReturn, "OleStdFree tried to release a suspicious pointer");
#endif
        pmalloc->lpVtbl->Free(pmalloc, pmem);
        refs = pmalloc->lpVtbl->Release(pmalloc);
    }
}


/* 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 = pmalloc->lpVtbl->GetSize(pmalloc, pmem);

   if (pmalloc != NULL) {
      ULONG refs = pmalloc->lpVtbl->Release(pmalloc);
   }

   return ulSize;
}


/* OleStdFreeString
** ----------------
**    Free a string that was allocated with the currently active
**    IMalloc* allocator.
**
**    if the caller has the current IMalloc* handy, then it can be
**    passed as a argument, otherwise this function will retrieve the
**    active allocator and use it.
*/
STDAPI_(void) OleStdFreeString(LPOLESTR lpsz, LPMALLOC lpMalloc)
{
   BOOL fMustRelease = FALSE;

   if (! lpMalloc) {
      if (CoGetMalloc(MEMCTX_TASK, &lpMalloc) != NOERROR)
         return;
      fMustRelease = TRUE;
   }

   lpMalloc->lpVtbl->Free(lpMalloc, lpsz);

   if (fMustRelease)
      lpMalloc->lpVtbl->Release(lpMalloc);
}


/* OleStdCopyString
** ----------------
**    Copy a string into memory allocated with the currently active
**    IMalloc* allocator.
**
**    if the caller has the current IMalloc* handy, then it can be
**    passed as a argument, otherwise this function will retrieve the
**    active allocator and use it.
*/
STDAPI_(LPOLESTR) OleStdCopyString(LPOLESTR lpszSrc, LPMALLOC lpMalloc)
{
   LPOLESTR lpszDest = NULL;
   BOOL fMustRelease = FALSE;
   UINT lSize = OLESTRLEN(lpszSrc);

   if (! lpMalloc) {
      if (CoGetMalloc(MEMCTX_TASK, &lpMalloc) != NOERROR)
         return NULL;
      fMustRelease = TRUE;
   }

   lpszDest = lpMalloc->lpVtbl->Alloc(lpMalloc, (lSize+1)*sizeof(OLECHAR));

   if (lpszDest)
      OLESTRCPY(lpszDest, lpszSrc);

   if (fMustRelease)
      lpMalloc->lpVtbl->Release(lpMalloc);
   return lpszDest;
}


/*
 * OleStdCreateStorageOnHGlobal()
 *
 * Purpose:
 *  Create a memory based IStorage*.
 *
 *  NOTE: if fDeleteOnRelease==TRUE, then the ILockBytes is created
 *            such that it will delete them memory on its last release.
 *            the IStorage on created on top of the ILockBytes in NOT
 *            created with STGM_DELETEONRELEASE. when the IStorage receives
 *            its last release, it will release the ILockBytes which will
 *            in turn free the memory. it is in fact an error to specify
 *            STGM_DELETEONRELEASE in this situation.
 *
 * Parameters:
 *  hGlobal --  handle to MEM_SHARE allocated memory. may be NULL and
 *              memory will be automatically allocated.
 *  fDeleteOnRelease -- controls if the memory is freed on the last release.
 *  grfMode --  flags passed to StgCreateDocfileOnILockBytes
 *
 *  NOTE: if hGlobal is NULL, then a new IStorage is created and
 *              STGM_CREATE flag is passed to StgCreateDocfileOnILockBytes.
 *        if hGlobal is non-NULL, then it is assumed that the hGlobal already
 *              has an IStorage inside it and STGM_CONVERT flag is passed
 *              to StgCreateDocfileOnILockBytes.
 *
 * Return Value:
 *    SCODE  -  S_OK if successful
 */
STDAPI_(LPSTORAGE) OleStdCreateStorageOnHGlobal(
      HANDLE hGlobal,
      BOOL fDeleteOnRelease,
      DWORD grfMode
)
{
   DWORD grfCreateMode=grfMode | (hGlobal==NULL ? STGM_CREATE:STGM_CONVERT);
   HRESULT hrErr;
   LPLOCKBYTES lpLockBytes = NULL;
   DWORD reserved = 0;
   LPSTORAGE lpStg = NULL;

   hrErr = CreateILockBytesOnHGlobal(
         hGlobal,
         fDeleteOnRelease,
         (LPLOCKBYTES FAR*)&lpLockBytes
   );
   if (hrErr != NOERROR)
      return NULL;

   hrErr = StgCreateDocfileOnILockBytes(
         lpLockBytes,
         grfCreateMode,
         reserved,
         (LPSTORAGE FAR*)&lpStg
   );
   if (hrErr != NOERROR) {
      OleStdRelease((LPUNKNOWN)lpLockBytes);
      return NULL;
   }
   OleStdRelease((LPUNKNOWN)lpLockBytes);
   return lpStg;
}



/*
 * OleStdCreateTempStorage()
 *
 * Purpose:
 *  Create a temporay IStorage* that will DeleteOnRelease.
 *  this can be either memory based or file based.
 *
 * Parameters:
 *  fUseMemory -- controls if memory-based or file-based stg is created
 *  grfMode --  storage mode flags
 *
 * Return Value:
 *    LPSTORAGE  -  if successful, NULL otherwise
 */
STDAPI_(LPSTORAGE) OleStdCreateTempStorage(BOOL fUseMemory, DWORD grfMode)
{
   LPSTORAGE   lpstg;
   HRESULT     hrErr;
   DWORD       reserved = 0;

   if (fUseMemory) {
      lpstg = OleStdCreateStorageOnHGlobal(
            NULL,  /* auto allocate */
            TRUE,  /* delete on release */
            grfMode
      );
   } else {
      /* allocate a temp docfile that will delete on last release */
      hrErr = StgCreateDocfile(
            NULL,
            grfMode | STGM_DELETEONRELEASE | STGM_CREATE,
            reserved,
            &lpstg
      );
      if (hrErr != NOERROR)
         return NULL;
   }
   return lpstg;
}


/* OleStdGetOleObjectData
** ----------------------
**    Render CF_EMBEDSOURCE/CF_EMBEDDEDOBJECT data on an TYMED_ISTORAGE
**    medium by asking the object to save into the storage.
**    the object must support IPersistStorage.
**
**    if lpMedium->tymed == TYMED_NULL, then a delete-on-release
**    storage is allocated (either file-based or memory-base depending
**    the value of fUseMemory). this is useful to support an
**    IDataObject::GetData call where the callee must allocate the
**    medium.
**
**    if lpMedium->tymed == TYMED_ISTORAGE, then the data is writen
**    into the passed in IStorage. this is useful to support an
**    IDataObject::GetDataHere call where the caller has allocated his
**    own IStorage.
*/
STDAPI OleStdGetOleObjectData(
      LPPERSISTSTORAGE        lpPStg,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpMedium,
      BOOL                    fUseMemory
)
{
   LPSTORAGE   lpstg = NULL;
   DWORD       reserved = 0;
   SCODE       sc = S_OK;
   HRESULT     hrErr;

   lpMedium->pUnkForRelease = NULL;

   if (lpMedium->tymed == TYMED_NULL) {

      if (lpformatetc->tymed & TYMED_ISTORAGE) {

         /* allocate a temp docfile that will delete on last release */
         lpstg = OleStdCreateTempStorage(
               TRUE /*fUseMemory*/,
               STGM_READWRITE | STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE
         );
         if (!lpstg)
            return ResultFromScode(E_OUTOFMEMORY);

         lpMedium->u.pstg = lpstg;
         lpMedium->tymed = TYMED_ISTORAGE;
         lpMedium->pUnkForRelease = NULL;
      } else {
         return ResultFromScode(DATA_E_FORMATETC);
      }
   } else if (lpMedium->tymed == TYMED_ISTORAGE) {
      lpMedium->tymed = TYMED_ISTORAGE;
   } else {
      return ResultFromScode(DATA_E_FORMATETC);
   }

   // NOTE: even if OleSave returns an error you should still call
   // SaveCompleted.

   OLEDBG_BEGIN2("OleSave called\r\n")
   hrErr = OleSave(lpPStg, lpMedium->u.pstg, FALSE /* fSameAsLoad */);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      OleDbgOutHResult("WARNING: OleSave returned", hrErr);
      sc = GetScode(hrErr);
   }
   OLEDBG_BEGIN2("IPersistStorage::SaveCompleted called\r\n")
   hrErr = lpPStg->lpVtbl->SaveCompleted(lpPStg, NULL);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      OleDbgOutHResult("WARNING: SaveCompleted returned",hrErr);
      if (sc == S_OK)
         sc = GetScode(hrErr);
   }

   return ResultFromScode(sc);
}


STDAPI OleStdGetLinkSourceData(
      LPMONIKER           lpmk,
      LPCLSID             lpClsID,
      LPFORMATETC         lpformatetc,
      LPSTGMEDIUM         lpMedium
)
{
   LPSTREAM    lpstm = NULL;
   DWORD       reserved = 0;
   HRESULT     hrErr;

   if (lpMedium->tymed == TYMED_NULL) {
      if (lpformatetc->tymed & TYMED_ISTREAM) {
         hrErr = CreateStreamOnHGlobal(
               NULL, /* auto allocate */
               TRUE, /* delete on release */
               (LPSTREAM FAR*)&lpstm
         );
         if (hrErr != NOERROR) {
            lpMedium->pUnkForRelease = NULL;
            return ResultFromScode(E_OUTOFMEMORY);
         }
         lpMedium->u.pstm = lpstm;
         lpMedium->tymed = TYMED_ISTREAM;
         lpMedium->pUnkForRelease = NULL;
      } else {
         lpMedium->pUnkForRelease = NULL;
         return ResultFromScode(DATA_E_FORMATETC);
      }
   } else {
      if (lpMedium->tymed == TYMED_ISTREAM) {
         lpMedium->tymed = TYMED_ISTREAM;
         lpMedium->u.pstm = lpMedium->u.pstm;
         lpMedium->pUnkForRelease = NULL;
      } else {
         lpMedium->pUnkForRelease = NULL;
         return ResultFromScode(DATA_E_FORMATETC);
      }
   }

   hrErr = OleSaveToStream((LPPERSISTSTREAM)lpmk, lpMedium->u.pstm);
   if (hrErr != NOERROR) return hrErr;
   return WriteClassStm(lpMedium->u.pstm, lpClsID);
}

/*
 * 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,
   LPOLESTR  lpszFullUserTypeName,
   LPOLESTR  lpszSrcOfCopy
)
{
   HGLOBAL            hMem = NULL;
   IBindCtx   FAR    *pbc = NULL;
   LPOBJECTDESCRIPTOR lpOD;
   DWORD     dwObjectDescSize, dwFullUserTypeNameLen, dwSrcOfCopyLen;
   DWORD dwODSize ;

   // Get the length of Full User Type Name; Add 1 for the null terminator
   dwFullUserTypeNameLen = lpszFullUserTypeName ?
                                    OLESTRLEN(lpszFullUserTypeName)+1 : 0;

   // Get the Source of Copy string and it's length; Add 1 for the null terminator
   if (lpszSrcOfCopy)
      dwSrcOfCopyLen = OLESTRLEN(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);
   dwODSize = dwObjectDescSize + (sizeof(OLECHAR) *
                                (dwFullUserTypeNameLen + dwSrcOfCopyLen)) ;
   hMem = GlobalAlloc((GMEM_MOVEABLE | GMEM_SHARE), dwODSize) ;
   if (NULL == hMem)
      goto error;

   lpOD = (LPOBJECTDESCRIPTOR)GlobalLock(hMem);

   // Set the FullUserTypeName offset and copy the string
   if (lpszFullUserTypeName)
   {
      lpOD->dwFullUserTypeName = dwObjectDescSize;
      OLESTRCPY((LPOLESTR)((LPSTR)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 +
                               (sizeof(OLECHAR) * dwFullUserTypeNameLen) ;
      OLESTRCPY((LPOLESTR)((LPSTR)lpOD + lpOD->dwSrcOfCopy), lpszSrcOfCopy);
   }
   else lpOD->dwSrcOfCopy = 0;  // zero offset indicates that string is not present

   // Initialize the rest of the OBJECTDESCRIPTOR
   lpOD->cbSize       = dwODSize ;
   lpOD->clsid        = clsid;
   lpOD->dwDrawAspect = dwDrawAspect;
   lpOD->sizel        = sizel;
   lpOD->pointl       = pointl;
   lpOD->dwStatus     = dwStatus;

   GlobalUnlock(hMem);
   return hMem;

error:
   if (hMem)
   {
      GlobalUnlock(hMem);
      GlobalFree(hMem);
   }
   return NULL;
}

/*
 * OleStdGetObjectDescriptorDataFromOleObject
 *
 * Purpose:
 *  Fills and returns a OBJECTDESCRIPTOR structure. Information for the structure is
 *  obtained from an OLEOBJECT.
 *  See OBJECTDESCRIPTOR for more information.
 *
 * Parameters:
 *  lpOleObj        LPOLEOBJECT OleObject from which ONJECTDESCRIPTOR info
 *                  is obtained.
 *  lpszSrcOfCopy   LPSTR string to identify source of copy.
 *                  May be NULL in which case IOleObject::GetMoniker is called
 *                  to get the moniker of the object. if the object is loaded
 *                  as part of a data transfer document, then usually
 *                  lpOleClientSite==NULL is passed to OleLoad when loading
 *                  the object. in this case the IOleObject:GetMoniker call
 *                  will always fail (it tries to call back to the object's
 *                  client site). in this situation a non-NULL lpszSrcOfCopy
 *                  parameter should be passed.
 *  dwDrawAspect    DWORD   Display Aspect of object
 *  pointl          POINTL  Offset from upper-left corner of object where
 *                  mouse went down for drag. Meaningful only when drag-drop
 *                  is used.
 *  lpSizelHim      SIZEL   (optional) If the object is being scaled in its
 *                  container, then the container should pass the extents
 *                  that it is using to display the object.
 *                  May be NULL if the object is NOT being scaled. in this
 *                  case, IViewObject2::GetExtent will be called to get the
 *                  extents from the object.
 *
 * Return Value:
 *  HBGLOBAL         Handle to OBJECTDESCRIPTOR structure.
 */

STDAPI_(HGLOBAL) OleStdGetObjectDescriptorDataFromOleObject(
      LPOLEOBJECT lpOleObj,
      LPOLESTR       lpszSrcOfCopy,
      DWORD       dwDrawAspect,
      POINTL      pointl,
      LPSIZEL     lpSizelHim
)
{
   CLSID clsid;
   LPOLESTR lpszFullUserTypeName = NULL;
   char     lpszAnsiFullUserTypeName[256];
   LPMONIKER lpSrcMonikerOfCopy = NULL;
   HGLOBAL hObjDesc;
   IBindCtx  FAR  *pbc = NULL;
   HRESULT hrErr;
   SIZEL sizelHim;
   BOOL  fFreeSrcOfCopy = FALSE;
   LPOLELINK lpOleLink = (LPOLELINK)
         OleStdQueryInterface((LPUNKNOWN)lpOleObj,&IID_IOleLink);
   LPVIEWOBJECT2 lpViewObj2 = (LPVIEWOBJECT2)
         OleStdQueryInterface((LPUNKNOWN)lpOleObj, &IID_IViewObject2);
   BOOL  fIsLink = (lpOleLink ? TRUE : FALSE);
   OLECHAR  szLinkedTypeFmt[80];
   char     szAnsiLinkedTypeFmt[80];
   LPOLESTR lpszBuf = NULL;
   char     lpszAnsiBuf[256];
   DWORD dwStatus = 0;

   // Get CLSID
   OLEDBG_BEGIN2("IOleObject::GetUserClassID called\r\n")
   hrErr = lpOleObj->lpVtbl->GetUserClassID(lpOleObj, &clsid);
   OLEDBG_END2
   if (hrErr != NOERROR)
      clsid = CLSID_NULL;

   // Get FullUserTypeName
   OLEDBG_BEGIN2("IOleObject::GetUserType called\r\n")
   hrErr = lpOleObj->lpVtbl->GetUserType(
         lpOleObj,
         USERCLASSTYPE_FULL,
         /*(LPOLESTR FAR*)*/&lpszFullUserTypeName
   );
   OLEDBG_END2

   /* if object is a link, then expand usertypename to be "Linked %s" */
   if (fIsLink && lpszFullUserTypeName) {
      if (0 == LoadString(ghInst, (UINT)IDS_OLE2UIPASTELINKEDTYPE,
                  /*(LPSTR)*/szAnsiLinkedTypeFmt, 80))
         lstrcpy(szAnsiLinkedTypeFmt, /*(LPSTR)*/"Linked %s");
      A2W (szAnsiLinkedTypeFmt, szLinkedTypeFmt, 80);
      lpszBuf = OleStdMalloc(
            (OLESTRLEN(lpszFullUserTypeName)+OLESTRLEN(szLinkedTypeFmt)+1)*sizeof(OLECHAR));
      if (lpszBuf) {
         W2A (lpszFullUserTypeName, lpszAnsiFullUserTypeName, 256);
         wsprintf(lpszAnsiBuf, szAnsiLinkedTypeFmt, lpszAnsiFullUserTypeName);
         OleStdFreeString(lpszFullUserTypeName, NULL);
         A2W (lpszAnsiBuf, lpszBuf, 256);
         lpszFullUserTypeName = lpszBuf;
      }
   }

   /* Get Source Of Copy
   **    if the object is an embedding, then get the object's moniker
   **    if the object is a link, then get the link source moniker
   */
   if (fIsLink) {

      OLEDBG_BEGIN2("IOleLink::GetSourceDisplayName called\r\n")
      hrErr = lpOleLink->lpVtbl->GetSourceDisplayName(
            lpOleLink, &lpszSrcOfCopy );
      OLEDBG_END2
      fFreeSrcOfCopy = TRUE;

   } else {

      if (lpszSrcOfCopy == NULL) {
         OLEDBG_BEGIN2("IOleObject::GetMoniker called\r\n")
         hrErr = lpOleObj->lpVtbl->GetMoniker(
               lpOleObj,
               OLEGETMONIKER_TEMPFORUSER,
               OLEWHICHMK_OBJFULL,
               (LPMONIKER FAR*)&lpSrcMonikerOfCopy
         );
         OLEDBG_END2
         if (hrErr == NOERROR)
         {
            CreateBindCtx(0, (LPBC FAR*)&pbc);
            lpSrcMonikerOfCopy->lpVtbl->GetDisplayName(
                  lpSrcMonikerOfCopy, pbc, NULL, &lpszSrcOfCopy);
            pbc->lpVtbl->Release(pbc);
            fFreeSrcOfCopy = TRUE;
         }
      }
   }

   // Get SIZEL
   if (lpSizelHim) {
      // Use extents passed by the caller
      sizelHim = *lpSizelHim;
   } else if (lpViewObj2) {
      // Get the current extents from the object
      OLEDBG_BEGIN2("IViewObject2::GetExtent called\r\n")
      hrErr = lpViewObj2->lpVtbl->GetExtent(
            lpViewObj2,
            dwDrawAspect,
            -1,     /*lindex*/
            NULL,   /*ptd*/
            (LPSIZEL)&sizelHim
      );
      OLEDBG_END2
      if (hrErr != NOERROR)
         sizelHim.cx = sizelHim.cy = 0;
   } else {
      sizelHim.cx = sizelHim.cy = 0;
   }

   // Get DWSTATUS
   OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n")
   hrErr = lpOleObj->lpVtbl->GetMiscStatus(
            lpOleObj,
            dwDrawAspect,
            &dwStatus
   );
   OLEDBG_END2
   if (hrErr != NOERROR)
      dwStatus = 0;

   // Get OBJECTDESCRIPTOR
   hObjDesc = OleStdGetObjectDescriptorData(
         clsid,
         dwDrawAspect,
         sizelHim,
         pointl,
         dwStatus,
         lpszFullUserTypeName,
         lpszSrcOfCopy
   );
   if (! hObjDesc)
      goto error;

   // Clean up
   if (lpszFullUserTypeName)
      OleStdFreeString(lpszFullUserTypeName, NULL);
   if (fFreeSrcOfCopy && lpszSrcOfCopy)
      OleStdFreeString(lpszSrcOfCopy, NULL);
   if (lpSrcMonikerOfCopy)
      OleStdRelease((LPUNKNOWN)lpSrcMonikerOfCopy);
   if (lpOleLink)
      OleStdRelease((LPUNKNOWN)lpOleLink);
   if (lpViewObj2)
      OleStdRelease((LPUNKNOWN)lpViewObj2);

   return hObjDesc;

error:
   if (lpszFullUserTypeName)
      OleStdFreeString(lpszFullUserTypeName, NULL);
   if (fFreeSrcOfCopy && lpszSrcOfCopy)
      OleStdFreeString(lpszSrcOfCopy, NULL);
   if (lpSrcMonikerOfCopy)
      OleStdRelease((LPUNKNOWN)lpSrcMonikerOfCopy);
   if (lpOleLink)
      OleStdRelease((LPUNKNOWN)lpOleLink);
   if (lpViewObj2)
      OleStdRelease((LPUNKNOWN)lpViewObj2);

   return NULL;
}

/*
 * 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;
   LPOLESTR           lpsz, szFullUserTypeName, szSrcOfCopy, szClassName, szDocName, szItemName;
   int                nClassName, nDocName, nItemName, nFullUserTypeName;
   LPOLESTR           szBuf = NULL;
   HGLOBAL            hMem = NULL;
   HKEY               hKey = NULL;
   LPMALLOC           pIMalloc = NULL;
   DWORD              dw = OLEUI_CCHKEYMAX;
   HGLOBAL            hObjDesc;
   HRESULT            hrErr;
   char               szAnsiString[256];


   // GetData CF_OBJECTDESCRIPTOR format from the object on the clipboard.
   // Only OLE 2 objects on the clipboard will offer CF_OBJECTDESCRIPTOR
   if (hMem = OleStdGetData(
         lpDataObject,
         (CLIPFORMAT)cfObjectDescriptor,
         NULL,
         DVASPECT_CONTENT,
         lpmedium))
   {
      *lpcfFmt = 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 if (hMem = OleStdGetData(
            lpDataObject,
            (CLIPFORMAT)cfOwnerLink,
            NULL,
            DVASPECT_CONTENT,
            lpmedium))
   {
      *lpcfFmt = cfOwnerLink;
      // CF_OWNERLINK contains null-terminated strings for class name, document name
      // and item name with two null terminating characters at the end
      szClassName = /*(LPOLESTR)*/GlobalLock(hMem);
      nClassName  = OLESTRLEN(szClassName);
      szDocName   = szClassName + nClassName + 1;
      nDocName    = OLESTRLEN(szDocName);
      szItemName  = szDocName + nDocName + 1;
      nItemName   = OLESTRLEN(szItemName);

      hrErr = CoGetMalloc(MEMCTX_TASK, &pIMalloc);
      if (hrErr != NOERROR)
         goto error;

      // 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. SrcOfCopy is constructed by concatenating FullUserTypeName, Document
      // Name and ItemName separated by spaces.
      szBuf = /*(LPOLESTR)*/pIMalloc->lpVtbl->Alloc(pIMalloc,
                     (DWORD)2*OLEUI_CCHKEYMAX+nDocName+nItemName+4);
      if (NULL == szBuf)
         goto error;
      szFullUserTypeName = szBuf;
      szSrcOfCopy = szFullUserTypeName+OLEUI_CCHKEYMAX+1;

      // Get FullUserTypeName
      if (RegQueryValue(hKey, NULL, szAnsiString, &dw) != ERROR_SUCCESS)
         goto error;
      A2W (szAnsiString, szFullUserTypeName, OLEUI_CCHKEYMAX);

      // Build up SrcOfCopy string from FullUserTypeName, DocumentName & ItemName
      lpsz = szSrcOfCopy;
      OLESTRCPY(lpsz, szFullUserTypeName);
      nFullUserTypeName = OLESTRLEN(szFullUserTypeName);
      lpsz[nFullUserTypeName]=' ';
      lpsz += nFullUserTypeName+1;
      OLESTRCPY(lpsz, szDocName);
      lpsz[nDocName] = ' ';
      lpsz += nDocName+1;
      OLESTRCPY(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;
    }
    // Check if object is CF_FILENAME
    else if (hMem = OleStdGetData(
            lpDataObject,
            (CLIPFORMAT)cfFileName,
            NULL,
            DVASPECT_CONTENT,
            lpmedium))
    {
       *lpcfFmt = cfFileName;
       lpsz = /*(LPOLESTR)*/GlobalLock(hMem);
       hrErr = GetClassFile(lpsz, &clsid);

       /* NOTE: 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;

       hrErr = CoGetMalloc(MEMCTX_TASK, &pIMalloc);
       if (hrErr != NOERROR)
         goto error;
       szBuf = /*(LPOLESTR)*/pIMalloc->lpVtbl->Alloc(pIMalloc, (DWORD)OLEUI_CCHKEYMAX);
       if (NULL == szBuf)
         goto error;

       OleStdGetUserTypeOfClass(&clsid, szBuf, OLEUI_CCHKEYMAX, NULL);

       hObjDesc = OleStdGetObjectDescriptorData(
            clsid,
            DVASPECT_CONTENT,
            sizelHim,
            pointl,
            0,
            szBuf,
            lpsz
      );
      if (!hObjDesc)
         goto error;
    }
    else goto error;

    // Clean up
    if (szBuf)
       pIMalloc->lpVtbl->Free(pIMalloc, (LPVOID)szBuf);
    if (pIMalloc)
       pIMalloc->lpVtbl->Release(pIMalloc);
    if (hMem)
    {
       GlobalUnlock(hMem);
       GlobalFree(hMem);
    }
    if (hKey)
       RegCloseKey(hKey);
    return hObjDesc;

error:
   if (szBuf)
      pIMalloc->lpVtbl->Free(pIMalloc, (LPVOID)szBuf);
   if (pIMalloc)
      pIMalloc->lpVtbl->Release(pIMalloc);
    if (hMem)
    {
       GlobalUnlock(hMem);
       GlobalFree(hMem);
    }
    if (hKey)
       RegCloseKey(hKey);
    return NULL;
}


#if defined( OBSOLETE )

/*************************************************************************
** The following API's have been converted into macros:
**          OleStdQueryOleObjectData
**          OleStdQueryLinkSourceData
**          OleStdQueryObjectDescriptorData
**          OleStdQueryFormatMedium
**          OleStdCopyMetafilePict
**          OleStdGetDropEffect
**
**    These macros are defined in olestd.h
*************************************************************************/

STDAPI OleStdQueryOleObjectData(LPFORMATETC lpformatetc)
{
   if (lpformatetc->tymed & TYMED_ISTORAGE) {
      return NOERROR;
   } else {
      return ResultFromScode(DATA_E_FORMATETC);
   }
}


STDAPI OleStdQueryLinkSourceData(LPFORMATETC lpformatetc)
{
   if (lpformatetc->tymed & TYMED_ISTREAM) {
      return NOERROR;
   } else {
      return ResultFromScode(DATA_E_FORMATETC);
   }
}


STDAPI OleStdQueryObjectDescriptorData(LPFORMATETC lpformatetc)
{
   if (lpformatetc->tymed & TYMED_HGLOBAL) {
      return NOERROR;
   } else {
      return ResultFromScode(DATA_E_FORMATETC);
   }
}


STDAPI OleStdQueryFormatMedium(LPFORMATETC lpformatetc, TYMED tymed)
{
   if (lpformatetc->tymed & tymed) {
      return NOERROR;
   } else {
      return ResultFromScode(DATA_E_FORMATETC);
   }
}


/*
 * OleStdCopyMetafilePict()
 *
 * Purpose:
 *    Make an independent copy of a MetafilePict
 * Parameters:
 *
 * Return Value:
 *    TRUE if successful, else FALSE.
 */
STDAPI_(BOOL) OleStdCopyMetafilePict(HANDLE hpictin, HANDLE FAR* phpictout)
{
   HANDLE hpictout;
   LPMETAFILEPICT ppictin, ppictout;

   if (hpictin == NULL || phpictout == NULL) {
      OleDbgAssert(hpictin == NULL || phpictout == NULL);
      return FALSE;
   }

   *phpictout = NULL;

   if ((ppictin = (LPMETAFILEPICT)GlobalLock(hpictin)) == NULL) {
      return FALSE;
   }

   hpictout = GlobalAlloc(GHND|GMEM_SHARE, sizeof(METAFILEPICT));

   if (hpictout && (ppictout = (LPMETAFILEPICT)GlobalLock(hpictout))){
      ppictout->hMF  = CopyMetaFile(ppictin->hMF, NULL);
      ppictout->xExt = ppictin->xExt;
      ppictout->yExt = ppictin->yExt;
      ppictout->mm   = ppictin->mm;
      GlobalUnlock(hpictout);
   }

   *phpictout = hpictout;

   return TRUE;

}


/* OleStdGetDropEffect
** -------------------
**
** Convert a keyboard state into a DROPEFFECT.
**
** returns the DROPEFFECT value derived from the key state.
**    the following is the standard interpretation:
**          no modifier -- Default Drop     (NULL is returned)
**          CTRL        -- DROPEFFECT_COPY
**          SHIFT       -- DROPEFFECT_MOVE
**          CTRL-SHIFT  -- DROPEFFECT_LINK
**
**    Default Drop: this depends on the type of the target application.
**    this is re-interpretable by each target application. a typical
**    interpretation is if the drag is local to the same document
**    (which is source of the drag) then a MOVE operation is
**    performed. if the drag is not local, then a COPY operation is
**    performed.
*/
STDAPI_(DWORD) OleStdGetDropEffect( DWORD grfKeyState )
{

   if (grfKeyState & MK_CONTROL) {

      if (grfKeyState & MK_SHIFT)
         return DROPEFFECT_LINK;
      else
         return DROPEFFECT_COPY;

   } else if (grfKeyState & MK_SHIFT)
      return DROPEFFECT_MOVE;

   return 0;    // no modifier -- do default operation
}
#endif  // OBSOLETE


/*
 * OleStdGetMetafilePictFromOleObject()
 *
 * Purpose:
 *      Generate a MetafilePict by drawing the OLE object.
 * Parameters:
 *  lpOleObj        LPOLEOBJECT pointer to OLE Object
 *  dwDrawAspect    DWORD   Display Aspect of object
 *  lpSizelHim      SIZEL   (optional) If the object is being scaled in its
 *                  container, then the container should pass the extents
 *                  that it is using to display the object.
 *                  May be NULL if the object is NOT being scaled. in this
 *                  case, IViewObject2::GetExtent will be called to get the
 *                  extents from the object.
 *  ptd             TARGETDEVICE FAR*   (optional) target device to render
 *                  metafile for. May be NULL.
 *
 * Return Value:
 *    HANDLE    -- handle of allocated METAFILEPICT
 */
STDAPI_(HANDLE) OleStdGetMetafilePictFromOleObject(
      LPOLEOBJECT         lpOleObj,
      DWORD               dwDrawAspect,
      LPSIZEL             lpSizelHim,
      DVTARGETDEVICE FAR* ptd
)
{
   LPVIEWOBJECT2 lpViewObj2 = NULL;
   HDC hDC;
   HMETAFILE hmf;
   HANDLE hMetaPict;
   LPMETAFILEPICT lpPict;
   RECT rcHim;
   RECTL rclHim;
   SIZEL sizelHim;
   HRESULT hrErr;
   SIZE size;
   POINT point;

   lpViewObj2 = (LPVIEWOBJECT2)OleStdQueryInterface(
         (LPUNKNOWN)lpOleObj, &IID_IViewObject2);
   if (! lpViewObj2)
      return NULL;

   // Get SIZEL
   if (lpSizelHim) {
      // Use extents passed by the caller
      sizelHim = *lpSizelHim;
   } else {
      // Get the current extents from the object
      OLEDBG_BEGIN2("IViewObject2::GetExtent called\r\n")
      hrErr = lpViewObj2->lpVtbl->GetExtent(
            lpViewObj2,
            dwDrawAspect,
            -1,     /*lindex*/
            ptd,    /*ptd*/
            (LPSIZEL)&sizelHim
      );
      OLEDBG_END2
      if (hrErr != NOERROR)
         sizelHim.cx = sizelHim.cy = 0;
   }

   hDC = CreateMetaFile(NULL);

   rclHim.left     = 0;
   rclHim.top      = 0;
   rclHim.right    = sizelHim.cx;
   rclHim.bottom   = sizelHim.cy;

   rcHim.left      = (int)rclHim.left;
   rcHim.top       = (int)rclHim.top;
   rcHim.right     = (int)rclHim.right;
   rcHim.bottom    = (int)rclHim.bottom;

   SetWindowOrgEx(hDC, rcHim.left, rcHim.top, &point);
   SetWindowExtEx(hDC, rcHim.right-rcHim.left, rcHim.bottom-rcHim.top,&size);

   OLEDBG_BEGIN2("IViewObject::Draw called\r\n")
   hrErr = lpViewObj2->lpVtbl->Draw(
         lpViewObj2,
         dwDrawAspect,
         -1,
         NULL,
         ptd,
         NULL,
         hDC,
         (LPRECTL)&rclHim,
         (LPRECTL)&rclHim,
         NULL,
         0
   );
   OLEDBG_END2

   OleStdRelease((LPUNKNOWN)lpViewObj2);
   if (hrErr != NOERROR) {
      OleDbgOutHResult("IViewObject::Draw returned", hrErr);
      CloseMetaFile(hDC);
      return(NULL);
   }

   hmf = CloseMetaFile(hDC);

   hMetaPict = GlobalAlloc(GHND|GMEM_SHARE, sizeof(METAFILEPICT));

   if (hMetaPict && (lpPict = (LPMETAFILEPICT)GlobalLock(hMetaPict))){
      lpPict->hMF  = hmf;
      lpPict->xExt = (int)sizelHim.cx ;
      lpPict->yExt = (int)sizelHim.cy ;
      lpPict->mm   = MM_ANISOTROPIC;
      GlobalUnlock(hMetaPict);
   }

   return hMetaPict;
}


/* Call Release on the object that is expected to go away.
**      if the refcnt of the object did no go to 0 then give a debug message.
*/
STDAPI_(ULONG) OleStdVerifyRelease(LPUNKNOWN lpUnk, LPOLESTR lpszMsg)
{
   ULONG cRef;
   char  lpszAnsiMsg[256];

   W2A (lpszMsg, lpszAnsiMsg, 256);
   cRef = lpUnk->lpVtbl->Release(lpUnk);

#if defined( _DEBUG )
   if (cRef != 0) {
      char szBuf[80];
      if (lpszMsg)
         MessageBox(NULL, lpszAnsiMsg, NULL, MB_ICONEXCLAMATION | MB_OK);
      wsprintf(
            /*(LPSTR)*/szBuf,
            "refcnt (%ld) != 0 after object (0x%lx) release\n",
            cRef,
            lpUnk
      );
      if (lpszMsg)
         OleDbgOut1(lpszAnsiMsg);
      OleDbgOut1(/*(LPSTR)*/szBuf);
      OleDbgAssertSz(cRef == 0, /*(LPSTR)*/szBuf);
   } else {
      char szBuf[80];
      wsprintf(
            /*(LPSTR)*/szBuf,
            "refcnt = 0 after object (0x%lx) release\n", lpUnk
      );
      OleDbgOut4(/*(LPSTR)*/szBuf);
   }
#endif
   return cRef;
}


/* Call Release on the object that is NOT necessarily expected to go away.
*/
STDAPI_(ULONG) OleStdRelease(LPUNKNOWN lpUnk)
{
   ULONG cRef;

   cRef = lpUnk->lpVtbl->Release(lpUnk);

#if defined( _DEBUG )
   {
      char szBuf[80];
      wsprintf(
            /*(LPSTR)*/szBuf,
            "refcnt = %ld after object (0x%lx) release\n",
            cRef,
            lpUnk
      );
      OleDbgOut4(/*(LPSTR)*/szBuf);
   }
#endif
   return cRef;
}


/* OleStdInitVtbl
** --------------
**
**    Initialize an interface Vtbl to ensure that there are no NULL
**    function pointers in the Vtbl. All entries in the Vtbl are
**    set to a valid funtion pointer (OleStdNullMethod) that issues
**        debug assert message (message box) and returns E_NOTIMPL if called.
**
**    NOTE: this funtion does not initialize the Vtbl with usefull
**    function pointers, only valid function pointers to avoid the
**    horrible run-time crash when a call is made through the Vtbl with
**    a NULL function pointer. this API is only necessary when
**    initializing the Vtbl's in C. C++ guarantees that all interface
**    functions (in C++ terms -- pure virtual functions) are implemented.
*/

STDAPI_(void) OleStdInitVtbl(LPVOID lpVtbl, UINT nSizeOfVtbl)
{
   LPVOID FAR* lpFuncPtrArr = (LPVOID FAR*)lpVtbl;
   UINT nMethods = nSizeOfVtbl/sizeof(VOID FAR*);
   UINT i;

   for (i = 0; i < nMethods; i++) {
      lpFuncPtrArr[i] = OleStdNullMethod;
   }
}


/* OleStdCheckVtbl
** ---------------
**
**    Check if all entries in the Vtbl are properly initialized with
**    valid function pointers. If any entries are either NULL or
**    OleStdNullMethod, then this function returns FALSE. If compiled
**    for _DEBUG this function reports which function pointers are
**    invalid.
**
**    RETURNS:  TRUE if all entries in Vtbl are valid
**                              FALSE otherwise.
*/

STDAPI_(BOOL) OleStdCheckVtbl(LPVOID lpVtbl, UINT nSizeOfVtbl, LPOLESTR lpszIface)
{
   LPVOID FAR* lpFuncPtrArr = (LPVOID FAR*)lpVtbl;
   UINT nMethods = nSizeOfVtbl/sizeof(VOID FAR*);
   UINT i;
   BOOL fStatus = TRUE;
   int nChar = 0;

   for (i = 0; i < nMethods; i++) {
      if (lpFuncPtrArr[i] == NULL || lpFuncPtrArr[i] == OleStdNullMethod) {
#if defined( _DEBUG )
         char szAnsiStr[256];
         char szBuf[256];
         W2A (lpszIface, szAnsiStr, 256);
         wsprintf(szBuf, "%s::method# %d NOT valid!", szAnsiStr, i);
         OleDbgOut1(/*(LPSTR)*/szBuf);
#endif
         fStatus = FALSE;
      }
   }
   return fStatus;
}


/* OleStdNullMethod
** ----------------
**    Dummy method used by OleStdInitVtbl to initialize an interface
**    Vtbl to ensure that there are no NULL function pointers in the
**    Vtbl. All entries in the Vtbl are set to this function. this
**    function issues a debug assert message (message box) and returns
**    E_NOTIMPL if called. If all is done properly, this function will
**    NEVER be called!
*/
STDMETHODIMP OleStdNullMethod(LPUNKNOWN lpThis)
{
   MessageBox(
         NULL,
         "ERROR: INTERFACE METHOD NOT IMPLEMENTED!\r\n",
         NULL,
         MB_SYSTEMMODAL | MB_ICONHAND | MB_OK
   );

   return ResultFromScode(E_NOTIMPL);
}



static BOOL  GetFileTimes(LPSTR lpszFileName, FILETIME FAR* pfiletime)
{
#ifdef WIN32
   WIN32_FIND_DATA fd;
   HANDLE hFind;
   hFind = FindFirstFile(lpszFileName,&fd);
   if (hFind == NULL || hFind == INVALID_HANDLE_VALUE) {
      return FALSE;
   }
   FindClose(hFind);
   *pfiletime = fd.ftLastWriteTime;
   return TRUE;
#else
   static char sz[256];
   static struct _find_t fileinfo;

   LSTRCPYN(/*(LPSTR)*/sz, lpszFileName, sizeof(sz)-1);
   sz[sizeof(sz)-1]= '\0';
   AnsiToOem(sz, sz);
   return (_dos_findfirst(sz,_A_NORMAL|_A_HIDDEN|_A_SUBDIR|_A_SYSTEM,
                (struct _find_t *)&fileinfo) == 0 &&
      CoDosDateTimeToFileTime(fileinfo.wr_date,fileinfo.wr_time,pfiletime));
#endif
}



/* OleStdRegisterAsRunning
** -----------------------
**    Register a moniker in the RunningObjectTable.
**    if there is an existing registration (*lpdwRegister!=NULL), then
**    first revoke that registration.
**
**    new dwRegister key is returned via *lpdwRegister parameter.
*/
STDAPI_(void) OleStdRegisterAsRunning(LPUNKNOWN lpUnk, LPMONIKER lpmkFull, DWORD FAR* lpdwRegister)
{
   LPRUNNINGOBJECTTABLE lpROT;
   HRESULT hrErr;
   DWORD dwOldRegister = *lpdwRegister;

   OLEDBG_BEGIN2("OleStdRegisterAsRunning\r\n")

   OLEDBG_BEGIN2("GetRunningObjectTable called\r\n")
   hrErr = GetRunningObjectTable(0,(LPRUNNINGOBJECTTABLE FAR*)&lpROT);
   OLEDBG_END2

   if (hrErr == NOERROR) {

      /* register as running if a valid moniker is passed
      **
      ** NOTE: we deliberately register the new moniker BEFORE
      **    revoking the old moniker just in case the object
      **    currently has no external locks. if the object has no
      **    locks then revoking it from the running object table will
      **    cause the object's StubManager to initiate shutdown of
      **    the object.
      */
      if (lpmkFull) {

         OLEDBG_BEGIN2("IRunningObjectTable::Register called\r\n")
         lpROT->lpVtbl->Register(lpROT, 0, lpUnk,lpmkFull,lpdwRegister);
         OLEDBG_END2

#if defined(_DEBUG)
         {
            char szBuf[512];
            LPOLESTR lpszDisplay;
            char     lpszAnsiDisplay[256];
            LPBC lpbc;

            CreateBindCtx(0, (LPBC FAR*)&lpbc);
            lpmkFull->lpVtbl->GetDisplayName(
                  lpmkFull,
                  lpbc,
                  NULL,
                  /*(LPOLESTR FAR*)*/&lpszDisplay
            );
            OleStdRelease((LPUNKNOWN)lpbc);
            W2A (lpszDisplay, lpszAnsiDisplay, 256);
            wsprintf(
                  szBuf,
                  "Moniker '%s' REGISTERED as [0x%lx] in ROT\r\n",
                  lpszAnsiDisplay,
                  *lpdwRegister
            );
            OleDbgOut2(szBuf);
            OleStdFreeString(lpszDisplay, NULL);
         }
#endif  // _DEBUG

      }

      // if already registered, revoke
      if (dwOldRegister != 0) {

#if defined(_DEBUG)
         {
            char szBuf[512];

            wsprintf(
                  szBuf,
                  "Moniker [0x%lx] REVOKED from ROT\r\n",
                  dwOldRegister
            );
            OleDbgOut2(szBuf);
         }
#endif  // _DEBUG

         OLEDBG_BEGIN2("IRunningObjectTable::Revoke called\r\n")
         lpROT->lpVtbl->Revoke(lpROT, dwOldRegister);
         OLEDBG_END2
      }

      OleStdRelease((LPUNKNOWN)lpROT);
   } else {
      OleDbgAssertSz(
            lpROT != NULL,
            "OleStdRegisterAsRunning: GetRunningObjectTable FAILED\r\n"
      );
   }

   OLEDBG_END2
}



/* OleStdRevokeAsRunning
** ---------------------
**    Revoke a moniker from the RunningObjectTable if there is an
**    existing registration (*lpdwRegister!=NULL).
**
**    *lpdwRegister parameter will be set to NULL.
*/
STDAPI_(void) OleStdRevokeAsRunning(DWORD FAR* lpdwRegister)
{
   LPRUNNINGOBJECTTABLE lpROT;
   HRESULT hrErr;

   OLEDBG_BEGIN2("OleStdRevokeAsRunning\r\n")

   // if still registered, then revoke
   if (*lpdwRegister != 0) {

      OLEDBG_BEGIN2("GetRunningObjectTable called\r\n")
      hrErr = GetRunningObjectTable(0,(LPRUNNINGOBJECTTABLE FAR*)&lpROT);
      OLEDBG_END2

      if (hrErr == NOERROR) {

#if defined(_DEBUG)
         {
            char szBuf[512];

            wsprintf(
                  szBuf,
                  "Moniker [0x%lx] REVOKED from ROT\r\n",
                  *lpdwRegister
            );
            OleDbgOut2(szBuf);
         }
#endif  // _DEBUG

         OLEDBG_BEGIN2("IRunningObjectTable::Revoke called\r\n")
         lpROT->lpVtbl->Revoke(lpROT, *lpdwRegister);
         OLEDBG_END2

         *lpdwRegister = 0;

         OleStdRelease((LPUNKNOWN)lpROT);
      } else {
         OleDbgAssertSz(
               lpROT != NULL,
               "OleStdRevokeAsRunning: GetRunningObjectTable FAILED\r\n"
         );
      }
   }
   OLEDBG_END2
}


/* OleStdNoteFileChangeTime
** ------------------------
**    Note the time a File-Based object has been saved in the
**    RunningObjectTable. These change times are used as the basis for
**    IOleObject::IsUpToDate.
**    It is important to set the time of the file-based object
**    following a save operation to exactly the time of the saved file.
**    this helps IOleObject::IsUpToDate to give the correct answer
**    after a file has been saved.
*/
STDAPI_(void) OleStdNoteFileChangeTime(LPOLESTR lpszFileName, DWORD dwRegister)
{
   char lpszAnsiFileName[256];

   W2A (lpszFileName, lpszAnsiFileName, 256);
   if (dwRegister != 0) {

      LPRUNNINGOBJECTTABLE lprot;
      FILETIME filetime;

      if (GetFileTimes(lpszAnsiFileName, &filetime) &&
         GetRunningObjectTable(0,&lprot) == NOERROR)
      {
         lprot->lpVtbl->NoteChangeTime( lprot, dwRegister, &filetime );
         lprot->lpVtbl->Release(lprot);

         OleDbgOut2("IRunningObjectTable::NoteChangeTime called\r\n");
      }
   }
}


/* OleStdNoteObjectChangeTime
** --------------------------
**    Set the last change time of an object that is registered in the
**    RunningObjectTable. These change times are used as the basis for
**    IOleObject::IsUpToDate.
**
**    every time the object sends out a OnDataChange notification, it
**    should update the Time of last change in the ROT.
**
**    NOTE: this function set the change time to the current time.
*/
STDAPI_(void) OleStdNoteObjectChangeTime(DWORD dwRegister)
{
   if (dwRegister != 0) {

      LPRUNNINGOBJECTTABLE lprot;
      FILETIME filetime;

      if (GetRunningObjectTable(0,&lprot) == NOERROR)
      {
         CoFileTimeNow( &filetime );
         lprot->lpVtbl->NoteChangeTime( lprot, dwRegister, &filetime );
         lprot->lpVtbl->Release(lprot);

         OleDbgOut2("IRunningObjectTable::NoteChangeTime called\r\n");
      }
   }
}


/* OleStdCreateTempFileMoniker
** ---------------------------
**    return the next available FileMoniker that can be used as the
**    name of an untitled document.
**    the FileMoniker is built of the form:
**          <lpszPrefixString><number>
**      eg. "Outline1", "Outline2", etc.
**
**    The RunningObjectTable (ROT) is consulted to determine if a
**    FileMoniker is in use. If the name is in use then the number is
**    incremented and the ROT is checked again.
**
** Parameters:
**    LPSTR lpszPrefixString    - prefix used to build the name
**    UINT FAR* lpuUnique       - (IN-OUT) last used number.
**                                  this number is used to make the
**                                  name unique. on entry, the input
**                                  number is incremented. on output,
**                                  the number used is returned. this
**                                  number should be passed again
**                                  unchanged on the next call.
**    LPSTR lpszName            - (OUT) buffer used to build string.
**                                  caller must be sure buffer is large
**                                  enough to hold the generated string.
**    LPMONIKER FAR* lplpmk     - (OUT) next unused FileMoniker
**
** Returns:
**    void
**
** Comments:
**    This function is similar in spirit to the Windows API
**    CreateTempFileName.
*/
STDAPI_(void) OleStdCreateTempFileMoniker(
      LPOLESTR        lpszPrefixString,
      UINT FAR*       lpuUnique,
      LPOLESTR        lpszName,
      LPMONIKER FAR*  lplpmk
)
{
   LPRUNNINGOBJECTTABLE lpROT = NULL;
   UINT i = (lpuUnique != NULL ? *lpuUnique : 1);
   HRESULT hrErr;
   char    lpszAnsiPrefixString[256], lpszAnsiName[256];

   W2A (lpszPrefixString, lpszAnsiPrefixString, 256);
   wsprintf(lpszAnsiName, "%s%d", lpszAnsiPrefixString, i++);
   A2W (lpszAnsiName, lpszName, OLEUI_CCHLABELMAX);
   CreateFileMoniker(lpszName, lplpmk);

   OLEDBG_BEGIN2("GetRunningObjectTable called\r\n")
   hrErr = GetRunningObjectTable(0,(LPRUNNINGOBJECTTABLE FAR*)&lpROT);
   OLEDBG_END2

   if (hrErr == NOERROR) {

      while (1) {
         if (! *lplpmk)
            break;  // failed to create FileMoniker

         OLEDBG_BEGIN2("IRunningObjectTable::IsRunning called\r\n")
         hrErr = lpROT->lpVtbl->IsRunning(lpROT,*lplpmk);
         OLEDBG_END2

         if (hrErr != NOERROR)
            break;  // the Moniker is NOT running; found unused one!

         OleStdVerifyRelease(
               (LPUNKNOWN)*lplpmk,
               OLESTR("OleStdCreateTempFileMoniker: Moniker NOT released")
            );

         wsprintf(lpszAnsiName, "%s%d", lpszAnsiPrefixString, i++);
         A2W (lpszAnsiName, lpszName, OLEUI_CCHLABELMAX);
         CreateFileMoniker(lpszName, lplpmk);
      }

      OleStdRelease((LPUNKNOWN)lpROT);
   }

   if (lpuUnique != NULL)
      *lpuUnique = i;
}


/* OleStdGetFirstMoniker
** ---------------------
**    return the first piece of a moniker.
**
**    NOTE: if the given moniker is not a generic composite moniker,
**    then an AddRef'ed pointer to the given moniker is returned.
*/
STDAPI_(LPMONIKER) OleStdGetFirstMoniker(LPMONIKER lpmk)
{
   LPMONIKER       lpmkFirst = NULL;
   LPENUMMONIKER   lpenumMoniker;
   DWORD           dwMksys;
   HRESULT         hrErr;

   if (! lpmk)
      return NULL;

   if (lpmk->lpVtbl->IsSystemMoniker(lpmk, (LPDWORD)&dwMksys) == NOERROR
      && dwMksys == MKSYS_GENERICCOMPOSITE) {

      /* NOTE: the moniker is a GenericCompositeMoniker.
      **    enumerate the moniker to pull off the first piece.
      */

      hrErr = lpmk->lpVtbl->Enum(
            lpmk,
            TRUE /* fForward */,
            (LPENUMMONIKER FAR*)&lpenumMoniker
      );
      if (hrErr != NOERROR)
         return NULL;    // ERROR: give up!

      hrErr = lpenumMoniker->lpVtbl->Next(
            lpenumMoniker,
            1,
            (LPMONIKER FAR*)&lpmkFirst,
            NULL
      );
      lpenumMoniker->lpVtbl->Release(lpenumMoniker);
      return lpmkFirst;

   } else {
      /* NOTE: the moniker is NOT a GenericCompositeMoniker.
      **    return an AddRef'ed pointer to the input moniker.
      */
      lpmk->lpVtbl->AddRef(lpmk);
      return lpmk;
   }
}


/* OleStdGetLenFilePrefixOfMoniker
** -------------------------------
**    if the first piece of the Moniker is a FileMoniker, then return
**    the length of the filename string.
**
**    lpmk      pointer to moniker
**
**    Returns
**      0       if moniker does NOT start with a FileMoniker
**      uLen    string length of filename prefix of the display name
**              retrieved from the given (lpmk) moniker.
*/
STDAPI_(ULONG) OleStdGetLenFilePrefixOfMoniker(LPMONIKER lpmk)
{
   LPMONIKER       lpmkFirst = NULL;
   DWORD           dwMksys;
   LPOLESTR        lpsz = NULL;
   LPBC            lpbc = NULL;
   ULONG           uLen = 0;
   HRESULT         hrErr;

   if (! lpmk)
      return 0;

   lpmkFirst = OleStdGetFirstMoniker(lpmk);
   if (lpmkFirst) {
      if ( (lpmkFirst->lpVtbl->IsSystemMoniker(
                     lpmkFirst, (LPDWORD)&dwMksys) == NOERROR)
            && dwMksys == MKSYS_FILEMONIKER) {

         hrErr = CreateBindCtx(0, (LPBC FAR*)&lpbc);
         if (hrErr == NOERROR) {
            hrErr = lpmkFirst->lpVtbl->GetDisplayName(
                  lpmkFirst,
                  lpbc,
                  NULL,   /* pmkToLeft */
                  /*(LPOLESTR FAR*)*/&lpsz
            );
            if (hrErr == NOERROR && lpsz != NULL) {
               uLen = OLESTRLEN(lpsz);
               OleStdFreeString(lpsz, NULL);
            }
            OleStdRelease((LPUNKNOWN)lpbc);
         }
      }
      lpmkFirst->lpVtbl->Release(lpmkFirst);
   }
   return uLen;
}


/* OleStdMkParseDisplayName
**    Parse a string into a Moniker by calling the OLE API
**    MkParseDisplayName. if the original link source class was an OLE1
**    class, then attempt the parsing assuming the same class applies.
**
**    if the class of the previous link source was an OLE1 class,
**    then first attempt to parse a string that it is qualified
**    with the progID associated with the OLE1 class. this more
**    closely matches the semantics of OLE1 where the class of
**    link sources is not expected to change. prefixing the
**    string with "@<ProgID -- OLE1 class name>!" will force the
**    parsing of the string to assume the file is of that
**    class.
**    NOTE: this trick of prepending the string with "@<ProgID>
**    only works for OLE1 classes.
**
**  PARAMETERS:
**    REFCLSID rClsid       -- original class of link source.
**                              CLSID_NULL if class is unknown
**    ... other parameters the same as MkParseDisplayName API ...
**
**  RETURNS
**    NOERROR if string parsed successfully
**    else error code returned by MkParseDisplayName
*/
STDAPI OleStdMkParseDisplayName(
      REFCLSID        rClsid,
      LPBC            lpbc,
      LPOLESTR        lpszUserName,
      ULONG FAR*      lpchEaten,
      LPMONIKER FAR*  lplpmk
)
{
   HRESULT hrErr;

   if (!IsEqualCLSID(rClsid,&CLSID_NULL) && CoIsOle1Class(rClsid) &&
      lpszUserName[0] != '@') {
      LPOLESTR lpszBuf;
      LPOLESTR lpszProgID;
      char     lpszAnsiBuf[256], lpszAnsiProgID[256], lpszAnsiUserName[256];

      // Prepend "@<ProgID>!" to the input string
      ProgIDFromCLSID(rClsid, &lpszProgID);
      if (lpszProgID == NULL)
         goto Cont1;
      lpszBuf = OleStdMalloc(
            ((ULONG)OLESTRLEN(lpszUserName)+OLESTRLEN(lpszProgID)+3) * sizeof(OLECHAR));
      if (lpszBuf == NULL) {
         if (lpszProgID)
            OleStdFree(lpszProgID);
         goto Cont1;
      }

      W2A (lpszProgID, lpszAnsiProgID, 256);
      W2A (lpszUserName, lpszAnsiUserName, 256);
      wsprintf(lpszAnsiBuf, "@%s!%s", lpszAnsiProgID, lpszAnsiUserName);
      A2W (lpszAnsiBuf, lpszBuf, OLESTRLEN(lpszUserName)+OLESTRLEN(lpszProgID)+3);
      OLEDBG_BEGIN2("MkParseDisplayName called\r\n")
      hrErr = MkParseDisplayName(lpbc, lpszBuf, lpchEaten, lplpmk);
      OLEDBG_END2

      if (lpszProgID)
         OleStdFree(lpszProgID);
      if (lpszBuf)
         OleStdFree(lpszBuf);

      if (hrErr == NOERROR)
         return NOERROR;
   }

Cont1:
   OLEDBG_BEGIN2("MkParseDisplayName called\r\n")
   hrErr = MkParseDisplayName(lpbc, lpszUserName, lpchEaten, lplpmk);
   OLEDBG_END2

   return hrErr;
}


/*
 * 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;
   long                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 == cfEmbeddedObject
            || lpPriorityList[i].fmtetc.cfFormat == cfEmbedSource
            || lpPriorityList[i].fmtetc.cfFormat == 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("OleQueryCreateFromData called\r\n")
         hrErr = OleQueryCreateFromData(lpSrcDataObj);
         OLEDBG_END2
         if(NOERROR == hrErr) {
            lpPriorityList[i].dwScratchSpace = TRUE;
         }
      } else if (lpPriorityList[i].fmtetc.cfFormat == cfLinkSource) {

         // if there is OLE 2.0 LinkSource format, then handle it
         // specially by calling OleQueryLinkFromData.
         OLEDBG_BEGIN2("OleQueryLinkFromData called\r\n")
         hrErr = OleQueryLinkFromData(lpSrcDataObj);
         OLEDBG_END2
         if(NOERROR == hrErr) {
            lpPriorityList[i].dwScratchSpace = TRUE;
         }
      }
   }

   OLEDBG_BEGIN2("IDataObject::EnumFormatEtc called\r\n")
   hrErr = lpSrcDataObj->lpVtbl->EnumFormatEtc(
         lpSrcDataObj,
         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;
   _fmemset(rgfmtetc,0,sizeof(rgfmtetc[FORMATETC_MAX]) );
   if (lpEnumFmtEtc->lpVtbl->Next(
         lpEnumFmtEtc, 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(&rgfmtetc[j], &lpPriorityList[i].fmtetc))
            {
               lpPriorityList[i].dwScratchSpace = TRUE;
            }
         }
      }
   } // endif

   // Clean up
   if (lpEnumFmtEtc)
      OleStdRelease((LPUNKNOWN)lpEnumFmtEtc);
}


/* OleStdGetPriorityClipboardFormat
** --------------------------------
**
**    Retrieve the first clipboard format in a list for which data
**    exists in the source IDataObject*.
**
**    Returns -1 if no acceptable match is found.
**            index of first acceptable match in the priority list.
**
*/
STDAPI_(int) OleStdGetPriorityClipboardFormat(
      LPDATAOBJECT        lpSrcDataObj,
      LPOLEUIPASTEENTRY   lpPriorityList,
      int                 cEntries
)
{
   int i;
   int nFmtEtc = -1;

   // Mark all entries that the Source provides
   OleStdMarkPasteEntryList(lpSrcDataObj, lpPriorityList, cEntries);

   // Loop over the target's priority list of formats
   for (i = 0; i < cEntries; i++)
   {
      if (lpPriorityList[i].dwFlags != OLEUIPASTE_PASTEONLY &&
            !(lpPriorityList[i].dwFlags & OLEUIPASTE_PASTE))
         continue;

      // get first marked entry
      if (lpPriorityList[i].dwScratchSpace) {
         nFmtEtc = i;
         break;          // Found priority format; DONE
      }
   }

   return nFmtEtc;
}


/* OleStdIsDuplicateFormat
** -----------------------
**    Returns TRUE if the lpFmtEtc->cfFormat is found in the array of
**    FormatEtc structures.
*/
STDAPI_(BOOL) OleStdIsDuplicateFormat(
      LPFORMATETC         lpFmtEtc,
      LPFORMATETC         arrFmtEtc,
      int                 nFmtEtc
)
{
   int i;

   for (i = 0; i < nFmtEtc; i++) {
      if (IsEqualFORMATETC((*lpFmtEtc), arrFmtEtc[i]))
         return TRUE;
   }

   return FALSE;
}


/* OleStdGetItemToken
 * ------------------
 *
 * LPSTR lpszSrc - Pointer to a source string
 * LPSTR lpszDst - Pointer to destination buffer
 *
 * Will copy one token from the lpszSrc buffer to the lpszItem buffer.
 * It considers all alpha-numeric and white space characters as valid
 * characters for a token. the first non-valid character delimates the
 * token.
 *
 * returns the number of charaters eaten.
 */
STDAPI_(ULONG) OleStdGetItemToken(LPOLESTR lpszSrc, LPOLESTR lpszDst, int nMaxChars)
{
   ULONG chEaten = 0L;
   char szAnsiSrc[256], szAnsiDst[256];
   LPSTR lpszAnsiDst = szAnsiDst;
   LPSTR lpszAnsiSrc = szAnsiSrc;

   W2A (lpszSrc, szAnsiSrc, 256);

   // skip leading delimeter characters
   while (*lpszAnsiSrc && --nMaxChars > 0
                        && ((*lpszAnsiSrc=='/') || (*lpszAnsiSrc=='\\') ||
                           (*lpszAnsiSrc=='!') || (*lpszAnsiSrc==':'))) {
      *lpszAnsiSrc++;
      chEaten++;
   }

   // Extract token string (up to first delimeter char or EOS)
   while (*lpszAnsiSrc && --nMaxChars > 0
                        && !((*lpszAnsiSrc=='/') || (*lpszAnsiSrc=='\\') ||
                           (*lpszAnsiSrc=='!') || (*lpszAnsiSrc==':'))) {
      *lpszAnsiDst++ = *lpszAnsiSrc++;
      chEaten++;
   }
   *lpszAnsiDst = '\0';

   A2W (szAnsiDst, lpszDst, OLEUI_CCHPATHMAX);
   return chEaten;
}

/*************************************************************************
** OleStdCreateRootStorage
**    create a root level Storage given a filename that is compatible
**    to be used by a top-level OLE container. if the filename
**    specifies an existing file, then an error is returned.
**    the root storage (Docfile) that is created by this function
**    is suitable to be used to create child storages for embedings.
**    (CreateChildStorage can be used to create child storages.)
**    NOTE: the root-level storage is opened in transacted mode.
*************************************************************************/

STDAPI_(LPSTORAGE) OleStdCreateRootStorage(LPOLESTR lpszStgName, DWORD grfMode)
{
   HRESULT hr;
   DWORD grfCreateMode = STGM_READWRITE | STGM_TRANSACTED;
   DWORD reserved = 0;
   LPSTORAGE lpRootStg;
   char szMsg[64];

   // if temp file is being created, enable delete-on-release
   if (! lpszStgName)
      grfCreateMode |= STGM_DELETEONRELEASE;

   hr = StgCreateDocfile(
         lpszStgName,
         grfMode | grfCreateMode,
         reserved,
         (LPSTORAGE FAR*)&lpRootStg
      );

   if (hr == NOERROR)
      return lpRootStg;               // existing file successfully opened

   OleDbgOutHResult("StgCreateDocfile returned", hr);

   if (0 == LoadString(ghInst, (UINT)IDS_OLESTDNOCREATEFILE, /*(LPSTR)*/szMsg, 64))
     return NULL;

   MessageBox(NULL, /*(LPSTR)*/szMsg, NULL,MB_ICONEXCLAMATION | MB_OK);
   return NULL;
}


/*************************************************************************
** OleStdOpenRootStorage
**    open a root level Storage given a filename that is compatible
**    to be used by a top-level OLE container. if the file does not
**    exist then an error is returned.
**    the root storage (Docfile) that is opened by this function
**    is suitable to be used to create child storages for embedings.
**    (CreateChildStorage can be used to create child storages.)
**    NOTE: the root-level storage is opened in transacted mode.
*************************************************************************/

STDAPI_(LPSTORAGE) OleStdOpenRootStorage(LPOLESTR lpszStgName, DWORD grfMode)
{
   HRESULT hr;
   DWORD reserved = 0;
   LPSTORAGE lpRootStg;
   char  szMsg[64];

   if (lpszStgName) {

      hr = StgOpenStorage(
            lpszStgName,
            NULL,
            grfMode | STGM_TRANSACTED,
            NULL,
            reserved,
            (LPSTORAGE FAR*)&lpRootStg
         );

      if (hr == NOERROR)
         return lpRootStg;     // existing file successfully opened

      OleDbgOutHResult("StgOpenStorage returned", hr);
   }

   if (0 == LoadString(ghInst, (UINT)IDS_OLESTDNOOPENFILE, szMsg, 64))
     return NULL;

   MessageBox(NULL, /*(LPSTR)*/szMsg, NULL,MB_ICONEXCLAMATION | MB_OK);
   return NULL;
}


/*************************************************************************
** OpenOrCreateRootStorage
**    open a root level Storage given a filename that is compatible
**    to be used by a top-level OLE container. if the filename
**    specifies an existing file, then it is open, otherwise a new file
**    with the given name is created.
**    the root storage (Docfile) that is created by this function
**    is suitable to be used to create child storages for embedings.
**    (CreateChildStorage can be used to create child storages.)
**    NOTE: the root-level storage is opened in transacted mode.
*************************************************************************/

STDAPI_(LPSTORAGE) OleStdOpenOrCreateRootStorage(LPOLESTR lpszStgName, DWORD grfMode)
{
   HRESULT hrErr;
   SCODE sc;
   DWORD reserved = 0;
   LPSTORAGE lpRootStg;
   char      szMsg[64];

   if (lpszStgName) {

      hrErr = StgOpenStorage(
            lpszStgName,
            NULL,
            grfMode | STGM_READWRITE | STGM_TRANSACTED,
            NULL,
            reserved,
            (LPSTORAGE FAR*)&lpRootStg
      );

      if (hrErr == NOERROR)
         return lpRootStg;      // existing file successfully opened

      OleDbgOutHResult("StgOpenStorage returned", hrErr);
      sc = GetScode(hrErr);

      if (sc!=STG_E_FILENOTFOUND && sc!=STG_E_FILEALREADYEXISTS) {
         return NULL;
      }
   }

   /* if file did not already exist, try to create a new one */
   hrErr = StgCreateDocfile(
         lpszStgName,
         grfMode | STGM_READWRITE | STGM_TRANSACTED,
         reserved,
         (LPSTORAGE FAR*)&lpRootStg
   );

   if (hrErr == NOERROR)
      return lpRootStg;               // existing file successfully opened

   OleDbgOutHResult("StgCreateDocfile returned", hrErr);

   if (0 == LoadString(ghInst, (UINT)IDS_OLESTDNOCREATEFILE, /*(LPSTR)*/szMsg, 64))
     return NULL;

   MessageBox(NULL, /*(LPSTR)*/szMsg, NULL, MB_ICONEXCLAMATION | MB_OK);
   return NULL;
}


/*
** OleStdCreateChildStorage
**    create a child Storage inside the given lpStg that is compatible
**    to be used by an embedded OLE object. the return value from this
**    function can be passed to OleCreateXXX functions.
**    NOTE: the child storage is opened in transacted mode.
*/
STDAPI_(LPSTORAGE) OleStdCreateChildStorage(LPSTORAGE lpStg, LPOLESTR lpszStgName)
{
   if (lpStg != NULL) {
      LPSTORAGE lpChildStg;
      DWORD grfMode = (STGM_READWRITE | STGM_TRANSACTED |
            STGM_SHARE_EXCLUSIVE);
      DWORD reserved = 0;

      HRESULT hrErr = lpStg->lpVtbl->CreateStorage(
            lpStg,
            lpszStgName,
            grfMode,
            reserved,
            reserved,
            (LPSTORAGE FAR*)&lpChildStg
         );

      if (hrErr == NOERROR)
         return lpChildStg;

      OleDbgOutHResult("lpStg->lpVtbl->CreateStorage returned", hrErr);
   }
   return NULL;
}


/*
** OleStdOpenChildStorage
**    open a child Storage inside the given lpStg that is compatible
**    to be used by an embedded OLE object. the return value from this
**    function can be passed to OleLoad function.
**    NOTE: the child storage is opened in transacted mode.
*/
STDAPI_(LPSTORAGE) OleStdOpenChildStorage(LPSTORAGE lpStg, LPOLESTR lpszStgName, DWORD grfMode)
{
   LPSTORAGE lpChildStg;
   LPSTORAGE lpstgPriority = NULL;
   DWORD reserved = 0;
   HRESULT hrErr;

   if (lpStg  != NULL) {

      hrErr = lpStg->lpVtbl->OpenStorage(
            lpStg,
            lpszStgName,
            lpstgPriority,
            grfMode | STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE,
            NULL,
            reserved,
            (LPSTORAGE FAR*)&lpChildStg
         );

      if (hrErr == NOERROR)
         return lpChildStg;

      OleDbgOutHResult("lpStg->lpVtbl->OpenStorage returned", hrErr);
   }
   return NULL;
}


/* OleStdCommitStorage
** -------------------
**    Commit the changes to the given IStorage*. This routine can be
**    called on either a root-level storage as used by an OLE-Container
**    or by a child storage as used by an embedded object.
**
**    This routine first attempts to perform this commit in a safe
**    manner. if this fails it then attempts to do the commit in a less
**    robust manner (STGC_OVERWRITE).
*/
STDAPI_(BOOL) OleStdCommitStorage(LPSTORAGE lpStg)
{
   HRESULT hrErr;

   // make the changes permanent
   hrErr = lpStg->lpVtbl->Commit(lpStg, 0);

   if (GetScode(hrErr) == STG_E_MEDIUMFULL) {
      // try to commit changes in less robust manner.
      OleDbgOut("Warning: commiting with STGC_OVERWRITE specified\n");
      hrErr = lpStg->lpVtbl->Commit(lpStg, STGC_OVERWRITE);
   }

   if (hrErr != NOERROR)
   {
      char szMsg[64];

      if (0 == LoadString(ghInst, (UINT)IDS_OLESTDDISKFULL, /*(LPSTR)*/szMsg, 64))
         return FALSE;

      MessageBox(NULL, /*(LPSTR)*/szMsg, NULL, MB_ICONEXCLAMATION | MB_OK);
      return FALSE;
   }
   else {
      return TRUE;
   }
}


/* OleStdDestroyAllElements
** ------------------------
**    Destroy all elements within an open storage. this is subject
**    to the current transaction.
*/
STDAPI OleStdDestroyAllElements(LPSTORAGE lpStg)
{
   IEnumSTATSTG FAR* lpEnum;
   STATSTG sstg;
   HRESULT hrErr;

   hrErr = lpStg->lpVtbl->EnumElements(
         lpStg, 0, NULL, 0, (IEnumSTATSTG FAR* FAR*)&lpEnum);

   if (hrErr != NOERROR)
      return hrErr;

   while (1) {
      if (lpEnum->lpVtbl->Next(lpEnum, 1, &sstg, NULL) != NOERROR)
         break;
      lpStg->lpVtbl->DestroyElement(lpStg, sstg.pwcsName);
      OleStdFree(sstg.pwcsName);
   }
   lpEnum->lpVtbl->Release(lpEnum);
   return NOERROR;
}

// 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);
}


