home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 1998 May / Pcwk5b98.iso / Borland / Cplus45 / BC45 / BOCOLE.PAK / BOLEDATA.CPP < prev    next >
C/C++ Source or Header  |  1995-08-29  |  16KB  |  615 lines

  1. //
  2. //**************************************************************************
  3. //
  4. // BOleData.cpp --   Implements a data cache object which is used for delayed
  5. //                   rendering.
  6. //
  7. //                   Bolero customers aren't required to use this object (or
  8. //                   delayed rendering at all) but when we cut/copy an
  9. //                   embedded object (i.e. embed from embedding) we need the
  10. //                   data cache to avoid getting changes to the original
  11. //                   object when the user expects a snapshot (the usual
  12. //                   delayed rendering problem).
  13. //
  14. // Copyright (c) 1993,94 by Borland International, Inc. All rights reserved
  15. //
  16. //**************************************************************************
  17.  
  18. #include <windows.h>
  19. #include "BOleData.h"
  20. #include "BOleCman.h"
  21. #include "BOleDoc.h"
  22.  
  23. #ifdef    _DEBUG
  24.  
  25. //**************************************************************************
  26. //
  27. // StgWatcher
  28. //
  29. // StgWatcher is a debugging tool used to watch when temporary storages are
  30. // created and deleted, if they ever are.
  31. //
  32. //**************************************************************************
  33.  
  34. class StgWatcher : public IUnknown, public STATSTG {
  35.  public:
  36.     StgWatcher( IUnknown *Data, IStorage *Stg );
  37.     ~StgWatcher();
  38.     // IUnknown
  39.     virtual HRESULT _IFUNC QueryInterface(REFIID iid, void FAR* FAR* pif);
  40.     virtual ULONG _IFUNC AddRef();
  41.     virtual ULONG _IFUNC Release();
  42.  protected:
  43.     IStorage *Stg;
  44.     IUnknown *Data;
  45.     ULONG    nRef;
  46. };
  47.  
  48. StgWatcher::StgWatcher( IUnknown *data, IStorage *stg ) : nRef( 1 ), Data( data ), Stg( stg ){
  49.     stg->Stat( this, 0 );
  50.     OLECHAR tmp[80];
  51.     wsprintf( tmp, TEXT("Stg \"%s\" created\n\r" ), pwcsName );
  52.     OutputDebugString( tmp );
  53. }
  54. StgWatcher::~StgWatcher(){
  55.     LPMALLOC pMalloc;
  56.     CoGetMalloc( MEMCTX_TASK, &pMalloc );
  57.     pMalloc->Free( pwcsName );
  58.     pMalloc->Release();
  59. }
  60.  
  61. HRESULT _IFUNC StgWatcher::QueryInterface(REFIID iid, void FAR* FAR* pif){
  62.     *pif = ( iid == IID_IUnknown ) ? ( void FAR * )( IUnknown * )this : 0;
  63.     return ( iid == IID_IUnknown ) ? ResultFromScode(S_OK) : ResultFromScode(E_NOINTERFACE);
  64. }
  65. ULONG _IFUNC StgWatcher::AddRef(){
  66.     nRef++;
  67.     OLECHAR tmp[80];
  68.     wsprintf( tmp, TEXT("Stg \"%s\" AddRef to %d\n\r" ), pwcsName, nRef );
  69.     OutputDebugString( tmp );
  70.     return nRef;
  71. }
  72. ULONG _IFUNC StgWatcher::Release(){
  73.     nRef--;
  74.     OLECHAR tmp[80];
  75.     wsprintf( tmp, TEXT("Stg \"%s\" Release to %d\n\r" ), pwcsName, nRef );
  76.     OutputDebugString( tmp );
  77.     if( nRef == 0 ){
  78.         wsprintf( tmp, TEXT("Stg \"%s\" deleted at %d\n\r" ), pwcsName, nRef );
  79.         OutputDebugString( tmp );
  80.         delete this;
  81.         return 0;
  82.     }
  83.     return nRef;
  84. }
  85. #endif
  86.  
  87. //**************************************************************************
  88. //
  89. // BOleData Implementation
  90. //
  91. //**************************************************************************
  92.  
  93. BOleData::BOleData (BOleClassManager *pCM, PIBUnknownMain pOuter) :
  94.     BOleComponent (pCM, pOuter), pFirstItem (NULL), pLastItem (NULL)
  95. {
  96.     #ifdef _DEBUG
  97.     {
  98.         OLECHAR b[ 80 ];
  99.         wsprintf( b, TEXT("BOleData %04x:%04x created\n\r"),
  100.             HIWORD( ( DWORD )this ), LOWORD( ( DWORD )this ) );
  101.         OutputDebugString( b );
  102.     }
  103.     #endif
  104. }
  105.  
  106. BOleData::~BOleData ()
  107. {
  108.     #ifdef _DEBUG
  109.     {
  110.         OLECHAR b[ 80 ];
  111.         wsprintf( b, TEXT("BOleData %04x:%04x deleted\n\r"),
  112.             HIWORD( ( DWORD )this ), LOWORD( ( DWORD )this ) );
  113.         OutputDebugString( b );
  114.     }
  115.     #endif
  116.     FreeItems();
  117.  
  118.     if (NOERROR == OleIsCurrentClipboard (this))
  119.         pFactory->GetService()->NotifyClipboardEmptied();
  120. }
  121.  
  122. void BOleData::FreeItems()
  123. {
  124.     // Release the mediums we might have created for deferred rendering
  125.     //
  126.     LPMALLOC pMalloc;
  127.     CoGetMalloc( MEMCTX_TASK, &pMalloc );
  128.     
  129.     BOleDataItem FAR *pWalk = pFirstItem;
  130.     while (pWalk) {
  131.         BOleDataItem *pTmp = pWalk->pNext;
  132.         ReleaseStgMedium (&(pWalk->tymed));
  133.         pMalloc->Free (pWalk);
  134.         pWalk = pTmp;
  135.     }
  136.     pFirstItem = NULL;
  137.     pMalloc->Release();
  138. }
  139.  
  140. HRESULT _IFUNC BOleData::QueryInterfaceMain (REFIID iid, LPVOID FAR *ppv)
  141. {
  142.     HRESULT hr = ResultFromScode(E_NOINTERFACE);
  143.     *ppv = NULL;
  144.  
  145.     // interfaces
  146.  
  147.     SUCCEEDED(hr = IDataObject_QueryInterface(this, iid, ppv))
  148.  
  149.     // base classes
  150.  
  151.     || SUCCEEDED(hr = BOleComponent::QueryInterfaceMain(iid, ppv))
  152.  
  153.     // helpers
  154.  
  155.     ;
  156.  
  157.     return hr;
  158. };
  159.  
  160. //**************************************************************************
  161. //
  162. // IDataObject Implementation
  163. //
  164. //**************************************************************************
  165.  
  166. HRESULT _IFUNC BOleData::GetData (LPFORMATETC pformatetcIn, 
  167.                                              LPSTGMEDIUM pmedium)
  168. {
  169.     pmedium->tymed = TYMED_NULL;
  170.     pmedium->pstg = NULL;
  171.  
  172.     if (!pFirstItem)
  173.         return ResultFromScode (E_FAIL);
  174.  
  175. #ifdef _DEBUG
  176.     OLECHAR name[32];
  177.     GetClipboardFormatName (pformatetcIn->cfFormat, name, sizeof(name));
  178. #endif
  179.  
  180.     BOleDataItem *pWalk = pFirstItem;
  181.     while (pWalk) {
  182.         if (pformatetcIn->cfFormat == pWalk->fmtEtc.cfFormat &&
  183.              pformatetcIn->tymed == pWalk->fmtEtc.tymed &&
  184.              pformatetcIn->dwAspect == pWalk->fmtEtc.dwAspect) {
  185.             *pmedium = pWalk->tymed;
  186.             if( pWalk->fmtEtc.tymed == TYMED_ISTREAM )
  187.                 pWalk->tymed.pstm->AddRef();
  188.             if( pWalk->fmtEtc.tymed == TYMED_ISTORAGE ) 
  189.                 pWalk->tymed.pstg->AddRef();
  190.             // so we go away when they release the storage
  191.             pmedium->pUnkForRelease = this;
  192.             pmedium->pUnkForRelease->AddRef();
  193.             // old pUnkForRelease is still in the table
  194.             return NOERROR;
  195.         }
  196.         pWalk = pWalk->pNext;
  197.     }
  198.  
  199.     return ResultFromScode (DATA_E_FORMATETC);
  200. }
  201.  
  202. HRESULT _IFUNC BOleData::GetDataHere (LPFORMATETC pformatetc, 
  203.                                       LPSTGMEDIUM pmedium)
  204. {
  205.     pmedium->tymed = TYMED_NULL;
  206.     
  207.     if (!pFirstItem)
  208.         return ResultFromScode (E_FAIL);
  209.     if (!pformatetc || !(pformatetc->tymed & TYMED_ISTORAGE))
  210.         return ResultFromScode (DV_E_FORMATETC);
  211.     if (!pmedium)
  212.         return ResultFromScode (DV_E_STGMEDIUM);
  213.  
  214.     BOleDataItem *pWalk = pFirstItem;
  215.     while (pWalk) {
  216.         if (pformatetc->cfFormat == pWalk->fmtEtc.cfFormat &&
  217.             pformatetc->tymed == pWalk->fmtEtc.tymed &&
  218.              pformatetc->dwAspect == pWalk->fmtEtc.dwAspect) {
  219.             pmedium->tymed = TYMED_ISTORAGE;
  220.             return pWalk->tymed.pstg->CopyTo (NULL, NULL, NULL, pmedium->pstg);
  221.         }
  222.         pWalk = pWalk->pNext;
  223.     }
  224.  
  225.     return ResultFromScode (DV_E_FORMATETC);
  226. }
  227.  
  228. HRESULT _IFUNC BOleData::QueryGetData (LPFORMATETC pformatetc)
  229. {
  230.     if (!pFirstItem)
  231.         return ResultFromScode (E_FAIL);
  232.     if (!pformatetc)
  233.         return ResultFromScode (DV_E_FORMATETC);
  234.  
  235.     BOleDataItem *pWalk = pFirstItem;
  236.     while (pWalk) {
  237.         if (pformatetc->cfFormat == pWalk->fmtEtc.cfFormat &&
  238.             pformatetc->tymed == pWalk->fmtEtc.tymed &&
  239.              pformatetc->dwAspect == pWalk->fmtEtc.dwAspect) {
  240.             return NOERROR;
  241.         }
  242.         pWalk = pWalk->pNext;
  243.     }
  244.  
  245.     return ResultFromScode (DV_E_FORMATETC);
  246. }
  247.  
  248. HRESULT _IFUNC BOleData::GetCanonicalFormatEtc (LPFORMATETC pformatetc,
  249.                                                 LPFORMATETC pformatetcOut)
  250. {
  251.     return ResultFromScode (DATA_S_SAMEFORMATETC);
  252. }
  253.  
  254. HRESULT _IFUNC BOleData::SetData (LPFORMATETC pformatetc,
  255.                                   LPSTGMEDIUM pmedium,
  256.                                   BOOL fRelease)
  257. {
  258.     // We own the data
  259.     //
  260. #ifdef _DEBUG
  261.     assert(fRelease);
  262. #endif
  263.     if (!fRelease)
  264.         return ResultFromScode (E_FAIL);
  265.  
  266.     // If the input pointers are NULL, we're supposed to delete the list
  267.     //
  268.     if (!pformatetc || !pmedium) {
  269.         FreeItems();
  270.         return NOERROR;
  271.     }
  272.  
  273.     // Allocate a new item to hold data
  274.     //
  275.     LPMALLOC pMalloc = NULL;
  276.     HRESULT hr = ::CoGetMalloc (MEMCTX_TASK, &pMalloc);
  277.     if (!SUCCEEDED(hr))
  278.         return hr;
  279.  
  280.     BOleDataItem FAR *pTmp = NULL;
  281.     pTmp = (BOleDataItem FAR*) pMalloc->Alloc (sizeof(BOleDataItem));
  282.     pMalloc->Release ();
  283.     if (!pTmp)
  284.         return ResultFromScode (E_OUTOFMEMORY);
  285.     else
  286.         _fmemset (pTmp, 0, sizeof(BOleDataItem));
  287.  
  288.  
  289.     // Insert formats onto the back of the list. Minimizes list traversal and
  290.     // keeps the priority order of the formats intact
  291.     //
  292.     if (pLastItem) {
  293.         pLastItem->pNext = pTmp;
  294.         pLastItem = pTmp;
  295.     } 
  296.     else {
  297.         pLastItem = pFirstItem = pTmp;
  298.     }
  299.  
  300.     pTmp->fmtEtc = *pformatetc;
  301.     pTmp->tymed = *pmedium;
  302.  
  303.     // When copying an embedding displayed as icon, it seems that the only
  304.     // format which can have DVASPECT_ICON is the metafile. It doesn't work
  305.     // if all formats have DVASPECT_ICON. Weird.
  306.     //
  307.     if (pTmp->fmtEtc.dwAspect == DVASPECT_ICON)
  308.         if (pTmp->fmtEtc.cfFormat != CF_METAFILEPICT)
  309.             pTmp->fmtEtc.dwAspect = DVASPECT_CONTENT;
  310.  
  311.     return NOERROR;
  312. }
  313.  
  314. HRESULT _IFUNC BOleData::EnumFormatEtc (DWORD dwDirection,
  315.                                         LPENUMFORMATETC FAR* ppenumFormatEtc)
  316. {
  317.     *ppenumFormatEtc = NULL;
  318.  
  319.     if (dwDirection == DATADIR_SET) {
  320.         *ppenumFormatEtc = NULL;
  321.         return ResultFromScode (E_FAIL);
  322.     }
  323.     *ppenumFormatEtc = new BOleEnumFormatEtc (this);
  324.     return *ppenumFormatEtc != NULL ? NOERROR : ResultFromScode (E_OUTOFMEMORY);
  325. }
  326.  
  327. HRESULT _IFUNC BOleData::DAdvise (FORMATETC FAR* pFormatetc,
  328.                                   DWORD advf,
  329.                                   LPADVISESINK pAdvSink, 
  330.                                   DWORD FAR* pdwConnection)
  331. {
  332.     *pdwConnection = 0;
  333.     return ResultFromScode (E_FAIL);
  334. }
  335.  
  336. HRESULT _IFUNC BOleData::DUnadvise (DWORD dwConnection)
  337. {
  338.     return ResultFromScode (E_FAIL);
  339. }
  340.  
  341. HRESULT _IFUNC BOleData::EnumDAdvise (LPENUMSTATDATA FAR* ppenumAdvise)
  342. {
  343.     *ppenumAdvise = NULL;
  344.     return ResultFromScode (E_FAIL);
  345. }
  346.  
  347.  
  348. //**************************************************************************
  349. //
  350. // IEnumFORMATETC Implementation
  351. //
  352. //**************************************************************************
  353.  
  354. BOleEnumFormatEtc::BOleEnumFormatEtc (BOleData *data) : pData (data), nRef (1)
  355. {
  356. #ifdef _DEBUG
  357.     OutputDebugString( TEXT("EnumFormatEtc Created\n\r") );
  358. #endif
  359.  
  360.     pCurItem = pFirstItem = pLastItem = NULL;
  361.     Reset();
  362.     // Hold the data object
  363.     pData->AddRef();
  364. }
  365.  
  366. HRESULT _IFUNC BOleEnumFormatEtc::QueryInterface(REFIID iid, LPVOID FAR *ppv)
  367. {
  368.     HRESULT hr = ResultFromScode(E_NOINTERFACE);
  369.     *ppv = NULL;
  370.  
  371.     // interfaces
  372.     hr = IEnumFORMATETC_QueryInterface(this, iid, ppv);
  373.  
  374.     return hr;
  375. }
  376.  
  377. ULONG _IFUNC BOleEnumFormatEtc::AddRef()
  378. {
  379.     return ++nRef;
  380. }
  381.  
  382. ULONG _IFUNC BOleEnumFormatEtc::Release()
  383. {
  384.     if (--nRef == 0) {
  385. #ifdef _DEBUG
  386.         OutputDebugString( TEXT("EnumFormatEtc Deleted\n\r") );
  387. #endif
  388.         pData->Release();
  389.         delete this;
  390.         return 0;
  391.     } 
  392.     else
  393.         return nRef;
  394. }
  395.  
  396. HRESULT _IFUNC BOleEnumFormatEtc::Next (ULONG celt,
  397.                                FORMATETC FAR * rgelt,
  398.                                ULONG FAR* pceltFetched)
  399. {
  400.     // In case the format list has changed since the last call, resynchronize
  401.     Resync();
  402.  
  403.     // Initialize output parameters
  404.     //
  405.     if (pceltFetched)
  406.         *pceltFetched = 0;
  407.     if (rgelt)
  408.         _fmemset (rgelt, 0, sizeof(FORMATETC) * celt);
  409.  
  410.     // Error on output parameter
  411.     //
  412.     if (!rgelt)
  413.         return ResultFromScode (DV_E_FORMATETC);
  414.  
  415.     // Either no data in the cache, or we've already enumerated them all
  416.     //
  417.     if (!pFirstItem || !pCurItem)
  418.         return ResultFromScode (S_FALSE);
  419.  
  420.     // Loop over formats, copying as many as we have, or as many as they
  421.     // asked for, whichever comes first
  422.     //
  423.     short nCopied = 0;
  424.     while (pCurItem && (celt > 0)) {
  425.  
  426.         *rgelt = pCurItem->fmtEtc;        // Copy formats
  427.  
  428.         rgelt++;                                // Increment pointers
  429.         pCurItem = pCurItem->pNext;
  430.  
  431.         nCopied++;                            // Increment counters
  432.         celt--;
  433.     }
  434.  
  435.     if (pceltFetched)
  436.         *pceltFetched = nCopied;
  437.  
  438.     return NOERROR;
  439. }
  440.  
  441. HRESULT _IFUNC BOleEnumFormatEtc::Skip (ULONG celt)
  442. {
  443.     // In case the format list has changed since the last call, resynchronize
  444.     Resync();
  445.  
  446.     // Save off the current item pointer in case Skip fails
  447.     //
  448.     BOleDataItem FAR *pTmp = pCurItem;
  449.  
  450.     // Skip as many items as we can
  451.     //
  452.     ULONG count = 0;
  453.     while (pCurItem && (count < celt)) {
  454.         pCurItem = pCurItem->pNext;
  455.         count++;
  456.     }
  457.  
  458.     // Ran off the edge of the list before skipping the requested number
  459.     // of items. Restore current item pointer
  460.     //
  461.     if (count < celt) {
  462.         pCurItem = pTmp;
  463.         return ResultFromScode (S_FALSE);
  464.     }
  465.  
  466.     return NOERROR;
  467. }
  468.  
  469. HRESULT _IFUNC BOleEnumFormatEtc::Reset ()
  470. {
  471.     // In case the format list has changed since the last call, resynchronize
  472.     Resync();
  473.  
  474.     // Reset the cursor to the head of the list.
  475.     //
  476.     pCurItem = pFirstItem;
  477.     return NOERROR;
  478. }
  479.  
  480. HRESULT _IFUNC BOleEnumFormatEtc::Clone (LPENUMFORMATETC FAR* ppEnum)
  481. {
  482.     // In case the format list has changed since the last call, resynchronize
  483.     Resync();
  484.  
  485.     *ppEnum = NULL;
  486.  
  487.     // Get a memory allocator from COM
  488.     //
  489.     LPMALLOC pMalloc = NULL;
  490.     HRESULT hr = ::CoGetMalloc (MEMCTX_TASK, &pMalloc);
  491.     if (!SUCCEEDED(hr))
  492.         return hr;
  493.  
  494.     // Allocate a new BOleDataObject
  495.     //
  496.     BOleEnumFormatEtc FAR *pClone = (BOleEnumFormatEtc FAR*) pMalloc->Alloc (sizeof(BOleEnumFormatEtc));
  497.     pMalloc->Release ();
  498.     if (!pClone)
  499.         return ResultFromScode (E_OUTOFMEMORY);
  500.  
  501.     // Copy the pointers from this BOleData object into the new one.
  502.     // Note that the data cache is not copied, but I think that's ok since
  503.     // cloning the enumerator is supposed to provide a snapshot of the state,
  504.     // not necessarily the whole data cache
  505.     //
  506.  
  507.     _fmemcpy (pClone, this, sizeof(BOleEnumFormatEtc));
  508.     *ppEnum = pClone;
  509.  
  510.     return NOERROR;
  511. }
  512.  
  513.  
  514. void BOleEnumFormatEtc::Resync(){
  515.     // Now that the enumerator is seperated from the data object, we have
  516.     // to make sure the format list is still the same. The cheap way is just
  517.     // to check that the two ends of the list are still the same, otherwise
  518.     // we decide to restart
  519.     //
  520.     if( pFirstItem != pData->pFirstItem || pLastItem != pData->pLastItem ){
  521.         pFirstItem = pData->pFirstItem;
  522.         pLastItem = pData->pLastItem;
  523.         pCurItem = pData->pFirstItem;
  524.     }
  525. }
  526.  
  527. //**************************************************************************
  528. //
  529. // BOleShadowData --    Prevent EmptyClipboard from deleting our real object
  530. //                   when it calls CoDisconnectObject. (see class defn)
  531. //
  532. //**************************************************************************
  533.  
  534. BOleShadowData::BOleShadowData (BOleClassManager *pCM, 
  535.                                 LPDATAOBJECT pDeleg)     : 
  536.                                 pDelegate (pDeleg), 
  537.                                 BOleComponent(pCM, NULL)
  538. {
  539.     pDelegate->AddRef();
  540. }
  541.  
  542. BOleShadowData::~BOleShadowData () 
  543. {
  544.     if (NOERROR == OleIsCurrentClipboard (this))
  545.         pFactory->GetService()->NotifyClipboardEmptied();
  546.  
  547.     pDelegate->Release();
  548. }
  549.  
  550. HRESULT _IFUNC BOleShadowData::QueryInterfaceMain(REFIID iid, void FAR* FAR* pif)
  551. {
  552.     HRESULT hr = ResultFromScode(E_NOINTERFACE);
  553.     *pif = NULL;
  554.  
  555.     // interfaces
  556.     
  557.     if (iid == IID_BOleShadowData) {
  558.         (BOleShadowData*) *pif = this;
  559.         AddRef ();
  560.         hr = NOERROR;
  561.     }
  562.     else if (SUCCEEDED(hr = IDataObject_QueryInterface(this, iid, pif))) {
  563.     }
  564.     else if SUCCEEDED(hr = BOleComponent::    QueryInterfaceMain(iid, pif)) {
  565.     }
  566.     return hr;
  567. }
  568.  
  569. HRESULT _IFUNC BOleShadowData::GetData (LPFORMATETC pformatetcIn, LPSTGMEDIUM pmedium)
  570. {
  571.     return pDelegate->GetData (pformatetcIn, pmedium);
  572. }
  573.  
  574. HRESULT _IFUNC BOleShadowData::GetDataHere (LPFORMATETC pformatetc, LPSTGMEDIUM pmedium)
  575. {
  576.     return pDelegate->GetDataHere (pformatetc, pmedium);
  577. }
  578.  
  579. HRESULT _IFUNC BOleShadowData::QueryGetData (LPFORMATETC pformatetc)
  580. {
  581.     return pDelegate->QueryGetData (pformatetc);
  582. }
  583.  
  584. HRESULT _IFUNC BOleShadowData::GetCanonicalFormatEtc (LPFORMATETC pformatetc, LPFORMATETC pformatetcOut)
  585. {
  586.     return pDelegate->GetCanonicalFormatEtc (pformatetc, pformatetcOut);
  587. }
  588.  
  589. HRESULT _IFUNC BOleShadowData::SetData (LPFORMATETC pformatetc, STGMEDIUM FAR * pmedium, BOOL fRelease)
  590. {
  591.     return pDelegate->SetData (pformatetc, pmedium, fRelease);
  592. }
  593.  
  594. HRESULT _IFUNC BOleShadowData::EnumFormatEtc (DWORD dwDirection, LPENUMFORMATETC FAR* ppenumFormatEtc)
  595. {
  596.     return pDelegate->EnumFormatEtc (dwDirection, ppenumFormatEtc);
  597. }
  598.  
  599. HRESULT _IFUNC BOleShadowData::DAdvise (FORMATETC FAR* pFormatetc, DWORD advf, LPADVISESINK pAdvSink, DWORD FAR* pdwConnection)
  600. {
  601.     return pDelegate->DAdvise (pFormatetc, advf, pAdvSink, pdwConnection);
  602. }
  603.  
  604. HRESULT _IFUNC BOleShadowData::DUnadvise (DWORD dwConnection)
  605. {
  606.     return pDelegate->DUnadvise (dwConnection);
  607. }
  608.  
  609. HRESULT _IFUNC BOleShadowData::EnumDAdvise (LPENUMSTATDATA FAR* ppenumAdvise)
  610. {
  611.     return pDelegate->EnumDAdvise (ppenumAdvise);
  612. }
  613.  
  614.  
  615.