home *** CD-ROM | disk | FTP | other *** search
/ QBasic & Borland Pascal & C / Delphi5.iso / C / BC_502 / BOCOLE.PAK / BOLEDATA.CPP < prev    next >
Encoding:
C/C++ Source or Header  |  1997-05-06  |  15.8 KB  |  612 lines

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