home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / dbmsg / mapi / docfile.ms / mspntfy.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-04-11  |  37.5 KB  |  1,188 lines

  1. /*
  2.  *  M S P N T F Y . C
  3.  *
  4.  *  Code for doing internal cross-process notifications within the 
  5.  *  Sample Message Store provider.
  6.  *
  7.  *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
  8.  */
  9.  
  10. #include "msp.h"
  11. #include <stdarg.h>
  12.  
  13. /* INTERNAL Function prototypes. */
  14.  
  15. long STDAPICALLTYPE LSMSOQNotifCallback(LPVOID lpvContext, ULONG cNotif,
  16.     LPNOTIFICATION lpNotifs);
  17. static void EmptyTable(LPTABLEDATA lptbl, PLMR plmr);
  18. static HRESULT HrApplyOQNotifChanges(LPTABLEDATA lptbl, PONB ponbIn);
  19. static HRESULT HrGetOutgoingNotificationKey(PIMS pims, LPNOTIFKEY * lppKey);
  20. static HRESULT HrNotifyOnOutgoingQueue(PIMS pims, PEID peid, LPSRow prw,
  21.     ULONG ulTableEvent, FILETIME *pftBeforeUpdate);
  22.  
  23. long STDAPICALLTYPE LSMSTblNotifCallback(LPVOID lpvContext, ULONG cNotif,
  24.     LPNOTIFICATION lpNotifs);
  25. HRESULT HrApplyTblNotifChanges(PIMS pims, PTNB ptnbIn);
  26. static HRESULT HrGetTableNotificationKey(PIMS pims, LPNOTIFKEY * lppKey);
  27.  
  28. /*
  29.  *  EXTERNAL FUNCTIONS (called from outside this file).
  30.  */
  31.  
  32. /* 
  33.  * HrCreateOGQueueMutex
  34.  *
  35.  *  Purpose
  36.  *      Create the outgoing queue mutex, and return it to the caller.
  37.  *
  38.  *  Arguments
  39.  *      phQMutex: Pointer to the location to return the new mutex.
  40.  *
  41.  *  Returns:
  42.  *      HRESULT: Will return an error only if the CreateMutex call fails.
  43.  */
  44. HRESULT HrCreateOGQueueMutex(HANDLE *phQMutex)
  45. {
  46.     HRESULT hr = hrSuccess;
  47.     HANDLE hMutex;
  48.     LPTSTR szMutexName = "SMS_OGQUEUEFILE_MUTEX";
  49.  
  50.     hMutex = CreateMutex(NULL, FALSE, szMutexName);
  51.  
  52.     if (hMutex)
  53.         *phQMutex = hMutex;
  54.  
  55.     #ifndef WIN16
  56.     else
  57.     {
  58.         TraceSz1("SampleMS: HrCreateOGQueueMutex: call to"
  59.             " CreateMutex failed (error %08lX)", GetLastError());
  60.         
  61.         hr = ResultFromScode(MAPI_E_CALL_FAILED);
  62.     }
  63.     #endif
  64.  
  65.     DebugTraceResult(HrCreateOGQueueMutex, hr);
  66.     return hr;
  67. }
  68.  
  69. /*
  70.  * HrSetupPrivateNotifications
  71.  *
  72.  *  Purpose
  73.  *      Setup two private channels via the MAPI notification engine to
  74.  *      tell other processes running against this store when 1) the 
  75.  *      outgoing queue changes and 2) when contents and hierarchy tables
  76.  *      change. We communicate between the multiple client processes and
  77.  *      the one spooler process. For the outgoing queue, we use a key that
  78.  *      is the full pathname to the outgoing queue file. For the other
  79.  *      tables, we use a unique 16-byte ID. Remember the connections so
  80.  *      that we can Unsubscribe when we shutdown the store.
  81.  *
  82.  *  Arguments
  83.  *      pims: a pointer to the message store object.
  84.  *
  85.  *  Side Effects
  86.  *      Fills in the ulOQConn and ulTblConn members of pims.
  87.  *
  88.  *  Returns
  89.  *      HRESULT
  90.  */
  91. HRESULT HrSetupPrivateNotifications(PIMS pims)
  92. {
  93.     HRESULT hr;
  94.     LPMAPIADVISESINK lpAdvise;
  95.     ULONG ulOQConn = 0;
  96.     ULONG ulTblConn = 0;
  97.     LPNOTIFKEY lpKey = NULL;
  98.  
  99.     /* Use the MAPI notification engine to tell myself */
  100.     /* (across processes) when to update outgoing queue. */
  101.  
  102.     /* The key is the path to the disk cache of the outbound queue */
  103.     hr = HrGetOutgoingNotificationKey(pims, &lpKey);
  104.     if (hr != hrSuccess)
  105.         goto exit;
  106.  
  107.     hr = HrAllocAdviseSink(&LSMSOQNotifCallback, (LPVOID) pims, &lpAdvise);
  108.     if (hr != hrSuccess)
  109.         goto exit;
  110.  
  111.     hr = pims->psup->lpVtbl->Subscribe(pims->psup, lpKey,
  112.         fnevExtended, 0L, lpAdvise, &ulOQConn);
  113.  
  114.     /* Always release; mapi will have addref'ed it during Subscribe */
  115.     UlRelease(lpAdvise);
  116.  
  117.     if (hr != hrSuccess)
  118.         goto exit;
  119.  
  120.     FreeNull(lpKey);
  121.     lpKey = NULL;
  122.  
  123.     /* Now, setup notifications for the other tables. */
  124.  
  125.     hr = HrGetTableNotificationKey(pims, &lpKey);
  126.     if (hr != hrSuccess)
  127.         goto exit;
  128.  
  129.     hr = HrAllocAdviseSink(&LSMSTblNotifCallback, (LPVOID) pims, &lpAdvise);
  130.     if (hr != hrSuccess)
  131.         goto exit;
  132.  
  133.     hr = pims->psup->lpVtbl->Subscribe(pims->psup, lpKey,
  134.         fnevExtended, 0L, lpAdvise, &ulTblConn);
  135.  
  136.     /* Always release; mapi will have addref'ed it during Subscribe */
  137.     UlRelease(lpAdvise);
  138.  
  139.     if (hr != hrSuccess)
  140.         goto exit;
  141.  
  142. exit:
  143.     FreeNull(lpKey);
  144.  
  145.     if (hr == hrSuccess)
  146.     {
  147.         /* Remember our use of the notification engine. */
  148.         pims->ulOQConn = ulOQConn;
  149.         pims->ulTblConn = ulTblConn;
  150.     }
  151.     else if (ulOQConn != 0)
  152.         (void) pims->psup->lpVtbl->Unsubscribe(pims->psup, ulOQConn);
  153.  
  154.     DebugTraceResult(HrSetupPrivateNotifications, hr);
  155.     return hr;
  156. }
  157.  
  158. /*
  159.  * HrUpdateOutgoingQueue
  160.  *
  161.  *  Purpose
  162.  *      Updates the outgoing queue based on the information given. If the
  163.  *      outgoing queue table is not open, or is out-of-date with respect to
  164.  *      the outgoing queue file on disk, initializes the table. The function
  165.  *      then applies the change requested to the table, writes the table on
  166.  *      disk, and notifies other processes of the change.
  167.  *
  168.  *  Arguments
  169.  *      pims: A pointer to the message store object.
  170.  *      pimsg: For a TABLE_ROW_ADDED event, a pointer to the message being
  171.  *          added to the queue; otherwise, this parameter should be NULL.
  172.  *      peid: For a TABLE_ROW_DELETED event, a pointer to the entryid of the
  173.  *          message being deleted from the queue; otherwise, this parameter
  174.  *          should be NULL.
  175.  *      ulTableEvent: The type of update event: Either TABLE_ROW_ADDED or
  176.  *          TABLE_ROW_DELETED.
  177.  *
  178.  *  Returns
  179.  *      HRESULT
  180.  */
  181. HRESULT HrUpdateOutgoingQueue(PIMS pims, PIMSG pimsg, PEID peid,
  182.     ULONG ulTableEvent)
  183. {
  184.     HRESULT hr = hrSuccess;
  185.     LPTABLEDATA lptbl;
  186.     SRow srNewRow = {0, 0, NULL};
  187.     LPSRow prw;
  188.     BOOL fInMutex = FALSE;
  189.     FILETIME ftBeforeUpdate;
  190.  
  191.     /* If the file mutex doesn't yet exist on this process, create it. */
  192.  
  193.     if (pims->hOGQueueMutex == NULL)
  194.     {
  195.         hr = HrCreateOGQueueMutex(&pims->hOGQueueMutex);
  196.         if (hr != hrSuccess)
  197.             goto exit;
  198.     }
  199.  
  200.     /* Get the file mutex so that we can use the file (and change it) */
  201.     /* without crossing paths with another process. */
  202.  
  203.     WaitForSingleObject(pims->hOGQueueMutex, INFINITE);
  204.     fInMutex = TRUE;
  205.  
  206.     /* This routine will open the outgoing queue table if it's not already */
  207.     /* open in this process, and will leave the opened copy around in pims. */
  208.  
  209.     if (pims->lptblOutgoing == NULL)
  210.     {
  211.         hr = HrNewOutgoingTableData(pims);
  212.         if (hr != hrSuccess)
  213.             goto exit;
  214.     }
  215.  
  216.     lptbl = pims->lptblOutgoing;
  217.     ftBeforeUpdate = pims->ftOGQueue;
  218.  
  219.     if (ulTableEvent == TABLE_ROW_ADDED)
  220.     {
  221.         ULONG cValues;
  222.  
  223.         AssertSz(pimsg, "A msg should be provided on an add");
  224.         AssertSz(peid == NULL, "No entryid should be provided on an add");
  225.  
  226.         hr = pimsg->lpVtbl->GetProps(pimsg, (LPSPropTagArray) &sptaOutgoing,
  227.             0, /* ansi */
  228.             &cValues, &srNewRow.lpProps);
  229.     
  230.         if (HR_FAILED(hr))          /* Ignore warnings from GetProps. */
  231.             goto exit;
  232.     
  233.         srNewRow.cValues = cValues;
  234.     
  235.         hr = lptbl->lpVtbl->HrModifyRow(lptbl, &srNewRow);
  236.         if (hr != hrSuccess)
  237.             goto exit;
  238.  
  239.         prw = &srNewRow;
  240.     }
  241.     else
  242.     {
  243.         AssertSz(ulTableEvent == TABLE_ROW_DELETED,
  244.             "Bad event type received");
  245.         AssertSz(pimsg == NULL, "No msg should be provided on a delete");
  246.         AssertSz(peid, "An entryid should be provided on a delete");
  247.  
  248.         /* remove it from the outgoing queue */
  249.         hr = HrRemoveRow(lptbl, peid);
  250.         if (hr != hrSuccess)
  251.             goto exit;
  252.  
  253.         prw = NULL;
  254.     }
  255.  
  256.     hr = HrWriteTableOnDisk(lptbl, (POBJ) pims, NULL, szOutgoingFileName);
  257.     if (hr != hrSuccess)
  258.         goto exit;
  259.  
  260.     /* Update the last mod time of the table inside this process's */
  261.     /* message store object. */
  262.  
  263.     hr = HrGetFileModTime(pims->szStorePath, szOutgoingFileName,
  264.         &pims->ftOGQueue);
  265.     if (hr != hrSuccess)
  266.         goto exit;
  267.  
  268.     hr = HrNotifyOnOutgoingQueue(pims, peid, prw, ulTableEvent,
  269.         &ftBeforeUpdate);
  270.  
  271. exit:
  272.     if (fInMutex)
  273.         ReleaseMutex(pims->hOGQueueMutex);
  274.  
  275.     LMFree(&pims->lmr, srNewRow.lpProps);
  276.  
  277.     DebugTraceResult(HrUpdateOutgoingQueue, hr);
  278.     return hr;
  279. }
  280.  
  281. /*
  282.  * HrNewOutgoingTableData
  283.  *
  284.  * Purpose  Checks the outgoing table data object in the message store.
  285.  *          If there isn't one, creates it and initializes it from disk.
  286.  *          If there is one, empties it, and re-initializes it from disk.
  287.  *          Should be inside the outgoing queue mutex during this function.
  288.  *
  289.  * Parameters
  290.  *  pims    A pointer to the message store object.
  291.  *
  292.  * Side Effects
  293.  *          Fills in the lptblOutgoing member of the pims.
  294.  *          Updates pims->ftOGQueue with the last mod time of the 
  295.  *          outgoing queue file.
  296.  */
  297. HRESULT HrNewOutgoingTableData(PIMS pims)
  298. {
  299.     HRESULT hr = hrSuccess;
  300.     LPTABLEDATA lptbl = pims->lptblOutgoing;
  301.     BOOL fTableCreated = FALSE;
  302.  
  303.     if (!lptbl)
  304.     {
  305.         PINST pinst;
  306.         SCODE sc = S_OK;
  307.     
  308.         /* The table doesn't exist. Create it. */
  309.  
  310.         pinst = (PINST) PvGetInstanceGlobals();
  311.     
  312.         if (pinst == NULL)
  313.         {
  314.             hr = ResultFromScode(MAPI_E_CALL_FAILED);
  315.             goto exit;
  316.         }
  317.     
  318.         sc = CreateTable((LPIID) &IID_IMAPITableData, pims->lmr.lpAllocBuf,
  319.             pims->lmr.lpAllocMore, pims->lmr.lpFreeBuf, pinst->lpmalloc,
  320.             TBLTYPE_DYNAMIC, PR_INSTANCE_KEY, (LPSPropTagArray) &sptaOutgoing,
  321.             &lptbl);
  322.     
  323.         if (sc != S_OK)
  324.         {
  325.             hr = ResultFromScode(sc);
  326.             goto exit;
  327.         }
  328.  
  329.         fTableCreated = TRUE;
  330.     }
  331.     else
  332.     {
  333.         /* The table exists already. Delete all rows from it. */
  334.         EmptyTable(lptbl, &pims->lmr);
  335.     }
  336.  
  337.     /* read the table in from disk */
  338.     /* outgoing queue tables can't be regenerated, so any error reading */
  339.     /* the table is fatal. We lose all messages in the OG queue if this */
  340.     /* function has an error. */
  341.  
  342.     hr = HrReadTableFromDisk(lptbl, (POBJ) pims, NULL, OUTGOING_COLUMNS,
  343.         szOutgoingFileName);
  344.     if (hr != hrSuccess)
  345.     {
  346.         TraceSz("SMS: Bad OG Queue data on disk.");
  347.         goto exit;
  348.     }
  349.  
  350.     /* Verify that all messages in the table actually exist on disk. If */
  351.     /* not, remove the row(s) and re-write the table to disk. */
  352.     hr = HrSyncOutgoingTable(lptbl, pims);
  353.     if (hr != hrSuccess)
  354.         goto exit;
  355.  
  356.     /* Save away the last mod time of the table inside this process's */
  357.     /* message store object. */
  358.  
  359.     hr = HrGetFileModTime(pims->szStorePath, szOutgoingFileName,
  360.         &pims->ftOGQueue);
  361.     if (hr != hrSuccess)
  362.         goto exit;
  363.  
  364.     pims->lptblOutgoing = lptbl;
  365.  
  366. exit:
  367.     if (hr != hrSuccess && fTableCreated)
  368.         UlRelease(lptbl);
  369.  
  370.     DebugTraceResult(HrNewOutgoingTableData, hr);
  371.     return hr;
  372. }
  373.  
  374. /*
  375.  * INTERNAL Functions (called from within this file ONLY).
  376.  *
  377.  */
  378.  
  379. /*----------------------------------------------------------------------+
  380. |                                                                       |
  381. |           CONTENTS AND HIERARCHY TABLE NOTIFICATION HANDLING          |
  382. |                                                                       |
  383. +----------------------------------------------------------------------*/
  384.  
  385. /*
  386.  * LSMSTblNotifCallback
  387.  *
  388.  *  Purpose
  389.  *      Update the contents or hierarchy table associated with the folder eid
  390.  *      passed across. We should receive a notification when the tabledata
  391.  *      object needs to have a row added, deleted or modified. Calls
  392.  *      ChangeTable after decoding the notification.
  393.  *
  394.  *  Arguments
  395.  *      lpvContext: A pointer to the message store object to use. We need
  396.  *          to verify that the object is still valid before using it.
  397.  *      cNotif: The number of notifications to process.
  398.  *      lpNotif: A pointer to an array of NOTIFICATION structures.
  399.  *
  400.  *  Returns
  401.  *      LONG: Always returns 0.
  402.  */
  403. long STDAPICALLTYPE LSMSTblNotifCallback(LPVOID lpvContext, ULONG cNotif,
  404.     LPNOTIFICATION lpNotif)
  405. {
  406.     PIMS pims = (PIMS) lpvContext;
  407.     SCODE sc = S_OK;
  408.     HRESULT hr = hrSuccess;
  409.     PTNB ptnb;
  410.  
  411.     /*
  412.      * Our code sends one extended notification at a time.
  413.      * The notification consists of the table event that occurred along with
  414.      * an object notification containing the entryids we need. We only use
  415.      * two of the entryids in the object notification. The ParentID fields
  416.      * refer to the parent folder of the table we need to update. The EntryID
  417.      * fields refer to the object that changed within the folder. The
  418.      * ulObjType field will be either MAPI_MESSAGE (for contents table
  419.      * changes) or MAPI_FOLDER (for hierarchy table changes).  All other
  420.      * fields in the structure are unused and should be set to 0.
  421.      */
  422.     if (IMS_IsInvalid(pims)
  423.         || cNotif != 1
  424.         || (IsBadReadPtr(lpNotif, ((UINT) cNotif) * sizeof(NOTIFICATION)))
  425.         || lpNotif->ulEventType != fnevExtended
  426.         || lpNotif->info.ext.ulEvent != 0)
  427.         return 0;
  428.  
  429.     ptnb = (PTNB) lpNotif->info.ext.pbEventParameters;
  430.  
  431.     if (IsBadReadPtr(ptnb, CbNewTNB(0))
  432.         || IsBadReadPtr(ptnb, CbTNB(ptnb)))
  433.         return 0;
  434.  
  435.     IMS_EnterCriticalSection(pims);
  436.  
  437.     hr = HrApplyTblNotifChanges(pims, ptnb);
  438.  
  439.     IMS_LeaveCriticalSection(pims);
  440.  
  441.     if (hr != hrSuccess)
  442.         sc = GetScode(hr);
  443.  
  444.     return sc;
  445. }
  446.  
  447. /* 
  448.  * HrApplyTblNotifChanges
  449.  *
  450.  * Purpose
  451.  *  This function relocates and validates the internal object notification
  452.  *  passed in, and then calls ChangeTable to actually update any open tables
  453.  *  within this process with the change given. The notification needs to be
  454.  *  relocated because the pointers from the other process may not be valid
  455.  *  on this process.
  456.  *
  457.  * Parameters
  458.  *  pims: A pointer to the message store object.
  459.  *  ptnbIn: A pointer to the table notification block (TNB) containing the
  460.  *      data we need in order to update any open tables om this process.
  461.  *
  462.  * Returns: validation errors or hrSuccess.
  463.  */
  464. HRESULT HrApplyTblNotifChanges(PIMS pims, PTNB ptnbIn)
  465. {
  466.     HRESULT hr = hrSuccess;
  467.     SCODE sc = S_OK;
  468.     LPNOTIFICATION lpntf;
  469.     PTNB ptnb = NULL;
  470.     OBJECT_NOTIFICATION *pon;
  471.     ULONG cb;
  472.  
  473.     /* Allocate a new notification block, and copy the data over before */
  474.     /* relocation. */
  475.  
  476.     hr = HrAlloc(CbNewTNB(ptnbIn->cbNtf), &ptnb);
  477.     if (hr != hrSuccess)
  478.         goto ret;
  479.  
  480.     memcpy(ptnb, ptnbIn, (UINT) CbNewTNB(ptnbIn->cbNtf));
  481.  
  482.     lpntf = (LPNOTIFICATION) ptnb->abNtf;
  483.  
  484.     if (lpntf->ulEventType != fnevObjectModified)
  485.     {
  486.         TraceSz1("SMS: HrApplyTblNotifChanges: Bad ulEventType %08lX "
  487.             "received", lpntf->ulEventType);
  488.         hr = ResultFromScode(MAPI_E_CALL_FAILED);
  489.         goto ret;
  490.     }
  491.  
  492.     /* Relocate the notification into the address space of this process. */
  493.     /* We passed along the memory offset of the originating process to allow */
  494.     /* this code to work. Note that ScRelocNotifications currently only */
  495.     /* works from "bad" addresses to "good" addresses. The code assumes that */
  496.     /* pointers are "bad" inside the notification, and that after conversion, */
  497.     /* they are valid. */
  498.  
  499.     sc = ScRelocNotifications(1, lpntf, ptnb->pvRef, (LPVOID) lpntf, &cb);
  500.     if (sc != S_OK)
  501.     {
  502.         hr = ResultFromScode(sc);
  503.         goto ret;
  504.     }
  505.  
  506.     if (ptnb->cbNtf != cb)
  507.     {
  508.         hr = ResultFromScode(MAPI_E_CALL_FAILED);
  509.         goto ret;
  510.     }
  511.  
  512.     pon = (OBJECT_NOTIFICATION *) &(lpntf->info.obj);
  513.  
  514.     if (pon->ulObjType != MAPI_MESSAGE && pon->ulObjType != MAPI_FOLDER)
  515.     {
  516.         TraceSz1("SMS: HrApplyTblNotifChanges: unexpected Object Type %08lX",
  517.             pon->ulObjType);
  518.         hr = ResultFromScode(MAPI_E_CALL_FAILED);
  519.         goto ret;
  520.     }
  521.  
  522.     if (    FIsInvalidEID(pon->cbParentID, (PEID) pon->lpParentID, pims)
  523.         ||  !FIsFolder((PEID) pon->lpParentID))
  524.     {
  525.         TraceSz("SMS: HrApplyTblNotifChanges: invalid parent entryid");
  526.         hr = ResultFromScode(MAPI_E_CALL_FAILED);
  527.         goto ret;
  528.     }
  529.  
  530.     /* TABLE_CHANGED events don't require a lpEntryID, because multiple
  531.      * objects have changed. ChangeTable() simply validates all rows in
  532.      * the table against the files on disk.
  533.      */
  534.     if (    ptnb->ulTableEvent != TABLE_CHANGED
  535.         &&  FIsInvalidEID(pon->cbEntryID, (PEID) pon->lpEntryID, pims))
  536.     {
  537.         TraceSz("SMS: HrApplyTblNotifChanges: invalid entryid");
  538.         hr = ResultFromScode(MAPI_E_CALL_FAILED);
  539.         goto ret;
  540.     }
  541.  
  542.     ChangeTable(pims, (PEID) pon->lpParentID, (PEID) pon->lpEntryID,
  543.         pon->ulObjType, ptnb->ulTableEvent, FALSE);
  544.  
  545. ret:
  546.     FreeNull(ptnb);
  547.  
  548.     DebugTraceResult(HrApplyTblNotifChanges, hr);
  549.     return hr;
  550. }
  551.  
  552. /* 
  553.  * HrSendNotif
  554.  *
  555.  * Purpose
  556.  *  This function constructs and sends a notification to other active Sample
  557.  *  Message Store processes open on this message store file. The notification
  558.  *  describes a change (add, delete, or modify) to either a message (contents
  559.  *  table) or folder (hierarchy table). The receiver will need to get two
  560.  *  entryids, one for the parent folder of the changed object, and one for
  561.  *  the changed object itself. The receiver also needs to know whether the
  562.  *  changed object is a message or a folder, and what type of change occurred:
  563.  *  add, delete, modify, or change (contents tables only).
  564.  *
  565.  * Parameters
  566.  *  pims: Pointer to the message store object.
  567.  *  peidParent: The entryid of the folder containing the table to update and
  568.  *              the object that changed.
  569.  *  peidObject: The entryid of the object that changed. May be NULL when
  570.  *              sending a TABLE_CHANGED notification (contents tables only).
  571.  *  ulTableEvent: TABLE_ROW_ADDED, TABLE_ROW_DELETED, TABLE_ROW_MODIFIED, or
  572.  *              TABLE_CHANGED (TABLE_CHANGED only works on contents tables).
  573.  *  ulObjType: The type of object that changed. This implies the type of table
  574.  *              to update. May be MAPI_MESSAGE (contents table) or MAPI_FOLDER
  575.  *              (hierarchy table).
  576.  *
  577.  * Returns: Memory and disk errors or success.
  578.  */
  579. HRESULT HrSendNotif(PIMS pims, PEID peidParent, PEID peidObject,
  580.     ULONG ulTableEvent, ULONG ulObjType)
  581. {
  582.     HRESULT hr = hrSuccess;
  583.     SCODE sc;
  584.     LPNOTIFKEY lpKey = NULL;
  585.     NOTIFICATION ntfTemp;
  586.     NOTIFICATION ntf;
  587.     PTNB ptnb = NULL;
  588.     ULONG cbNtf;
  589.     ULONG cbOut;
  590.     ULONG ulFlags = 0;
  591.  
  592.     /* Get the key */
  593.     hr = HrGetTableNotificationKey(pims, &lpKey);
  594.     if (hr != hrSuccess)
  595.         goto ret;
  596.  
  597.     /*
  598.      * Our code sends one extended notification at a time. The notification
  599.      * consists of the table event that occurred along with an object
  600.      * notification containing the entryids we need. We send the type of
  601.      * table event as part of our extended notification structure, and
  602.      * package the object notification always as "fnevObjectModified". We
  603.      * only use two of the entryids in the object notification. The ParentID
  604.      * fields refer to the parent folder of the table we need to update. The
  605.      * EntryID fields refer to the object that changed within the folder. The
  606.      * ulObjType field will be either MAPI_MESSAGE (for contents table
  607.      * changes) or MAPI_FOLDER (for hierarchy table changes).  All other
  608.      * fields in the structure are unused and should be set to 0.
  609.      */
  610.     memset(&ntfTemp, 0, sizeof(NOTIFICATION));
  611.  
  612.     /* We always send the same type of event here. This is enough to get the
  613.      * notification code to count and relocate what we send. We send the real
  614.      * table event in ptnb->ulTableEvent (see below).
  615.      */
  616.     ntfTemp.ulEventType = fnevObjectModified;
  617.  
  618.     ntfTemp.info.obj.ulObjType  = ulObjType;
  619.  
  620.     ntfTemp.info.obj.lpParentID = (LPENTRYID) peidParent;
  621.     ntfTemp.info.obj.cbParentID = CbEID(peidParent);
  622.  
  623.     if (ulTableEvent != TABLE_CHANGED)
  624.     {
  625.         ntfTemp.info.obj.lpEntryID  = (LPENTRYID) peidObject;
  626.         ntfTemp.info.obj.cbEntryID  = CbEID(peidObject);
  627.     }
  628.  
  629.     sc = ScCountNotifications(1, &ntfTemp, &cbNtf);
  630.     if (sc != S_OK)
  631.     {                   
  632.         hr = ResultFromScode(sc);
  633.         goto ret;
  634.     }
  635.  
  636.     hr = HrAlloc(CbNewTNB(cbNtf), &ptnb);
  637.     if (hr != hrSuccess)
  638.         goto ret;
  639.  
  640.     /* Here's where we send the table event. It's either TABLE_ROW_ADDED,
  641.      * TABLE_ROW_DELETED, TABLE_ROW_MODIFIED, or TABLE_CHANGED.
  642.      */
  643.     ptnb->ulTableEvent = ulTableEvent;
  644.     ptnb->cbNtf = cbNtf;
  645.  
  646.     sc = ScCopyNotifications(1, &ntfTemp, (LPVOID) ptnb->abNtf, &cbOut);
  647.     if (sc != S_OK)
  648.     {
  649.         hr = ResultFromScode(sc);
  650.         goto ret;
  651.     }
  652.  
  653.     AssertSz(cbOut == cbNtf, "ScCopyNotifications used a different # of bytes "
  654.         "than ScCountNotifications returned.");
  655.  
  656.     /* Pass across the notification's memory offset so that the receiving */
  657.     /* process can relocate the notification to its address space. */
  658.     ptnb->pvRef = (LPVOID) ptnb->abNtf;
  659.     
  660.     ntf.ulEventType = fnevExtended;
  661.     ntf.info.ext.ulEvent = 0;
  662.     ntf.info.ext.cb = CbTNB(ptnb);
  663.     ntf.info.ext.pbEventParameters = (LPBYTE) ptnb;
  664.  
  665.     hr = pims->psup->lpVtbl->Notify(pims->psup, lpKey, 1, &ntf, &ulFlags);
  666.  
  667. ret:
  668.     FreeNull(lpKey);
  669.     FreeNull(ptnb);
  670.  
  671.     DebugTraceResult(HrSendNotif, hr);
  672.     return hr;
  673. }
  674.  
  675. /*
  676.  * HrGetTableNotificationKey
  677.  *
  678.  * Purpose
  679.  *  Generate and return the notification key that will allow cross-process
  680.  *  notifications for changes to any contents or hierarchy tables within a
  681.  *  store. The memory returned should be freed with FreeNull. This
  682.  *  notification key needs to work for processes attached to this particular
  683.  *  message store, and should only receive notifications having to do with
  684.  *  changes to tables. The key contains the store guid (unique for this store)
  685.  *  preceeded by a ULONG with 0x0000ABCD in it. Note that the choice of
  686.  *  0x0000ABCD is arbitrary. As long as the sender sends to the same key as
  687.  *  the receiver listens to, and no unexpected sender sends to that key,
  688.  *  we're fine.
  689.  *
  690.  * Parameters
  691.  *  pims        pointer to the message store object.
  692.  *  lppKey      pointer to the location to return the new key.
  693.  */
  694. static HRESULT HrGetTableNotificationKey(PIMS pims, LPNOTIFKEY * lppKey)
  695. {
  696.     HRESULT hr = hrSuccess;
  697.     LPNOTIFKEY lpKey = NULL;
  698.     ULONG cb;                   /* number of bytes in the key */
  699.  
  700.     /* allocate space for the key */
  701.     cb = sizeof(ULONG) + sizeof(MAPIUID);
  702.     hr = HrAlloc(CbNewNOTIFKEY(cb), (PPV) &lpKey);
  703.     if (hr != hrSuccess)
  704.         goto exit;
  705.  
  706.     *((ULONG *) &(lpKey->ab[0])) = 0x0000ABCD;
  707.     GetResourceUID(pims, (MAPIUID *) &(lpKey->ab[sizeof(ULONG)]));
  708.     lpKey->cb = cb;
  709.  
  710. exit:
  711.     if (HR_FAILED(hr))
  712.     {
  713.         FreeNull(lpKey);
  714.         lpKey = NULL;
  715.     }
  716.  
  717.     *lppKey = lpKey;
  718.  
  719.     DebugTraceResult(HrGetTableNotificationKey, hr);
  720.     return hr;
  721. }
  722.  
  723. /*----------------------------------------------------------------------+
  724. |                                                                       |
  725. |               OUTGOING QUEUE NOTIFICATION HANDLING                    |
  726. |                                                                       |
  727. +----------------------------------------------------------------------*/
  728.  
  729. /*
  730.  * LSMSOQNotifCallback
  731.  *
  732.  *  Purpose
  733.  *      Update the outgoing queue table associated with the process that
  734.  *      the spooler is using. We should receive a notification when the
  735.  *      tabledata object needs to have a row added or deleted. If the
  736.  *      tabledata object exists on the message store object, then call
  737.  *      HrModifyRow (when a row is added), or HrRemoveRow (when a row is
  738.  *      deleted) to update the table appropriately.
  739.  *
  740.  *  Arguments
  741.  *      lpvContext: A pointer to the message store object to use. We need
  742.  *          to verify that the object is still valid before using it.
  743.  *      cNotif: The number of notifications to process.
  744.  *      lpNotif: A pointer to an array of NOTIFICATION structures.
  745.  *
  746.  *  Returns
  747.  *      LONG: Always returns 0.
  748.  */
  749. long STDAPICALLTYPE LSMSOQNotifCallback(LPVOID lpvContext, ULONG cNotif,
  750.     LPNOTIFICATION lpNotif)
  751. {
  752.     PIMS pims = (PIMS) lpvContext;
  753.     SCODE sc = S_OK;
  754.     HRESULT hr = hrSuccess;
  755.     FILETIME ftCurrent;
  756.     BOOL fInMutex = FALSE;
  757.     PONB ponb;
  758.  
  759.     /* Our code sends one extended notification at a time. This */
  760.     /* extended notification contains two filetimes (the time */
  761.     /* before the outgoing queue file was modified, and the time after */
  762.     /* the change was made), and a standard notification with the change */
  763.     /* to apply to the table. It should be an fnevTableModified, and */
  764.     /* should be either TABLE_ROW_ADDED or TABLE_ROW_DELETED. If we ever */
  765.     /* receive anything other than this, the code must change. */
  766.     if (IMS_IsInvalid(pims)
  767.         || cNotif != 1
  768.         || (IsBadReadPtr(lpNotif, ((UINT) cNotif) * sizeof(NOTIFICATION)))
  769.         || lpNotif->ulEventType != fnevExtended
  770.         || lpNotif->info.ext.ulEvent != 0)
  771.         return 0;
  772.  
  773.     ponb = (PONB) lpNotif->info.ext.pbEventParameters;
  774.  
  775.     if (IsBadReadPtr(ponb, CbNewONB(0))
  776.         || IsBadReadPtr(ponb, CbONB(ponb)))
  777.         return 0;
  778.  
  779.     IMS_EnterCriticalSection(pims);
  780.  
  781.     /* Check to see if the outgoing queue table data is open. If it isn't, */
  782.     /* there is nothing to do, because the current table will be read and */
  783.     /* initialized from disk when the spooler opens it. We're done. */
  784.  
  785.     if (!pims->lptblOutgoing)
  786.         goto exit;
  787.  
  788.     /* If the file mutex doesn't yet exist on this process, create it. */
  789.  
  790.     if (pims->hOGQueueMutex == NULL)
  791.     {
  792.         hr = HrCreateOGQueueMutex(&pims->hOGQueueMutex);
  793.         if (hr != hrSuccess)
  794.             goto exit;
  795.     }
  796.  
  797.     /* Get the file mutex so that we can use the file (and change it) */
  798.     /* without crossing paths with another process. */
  799.  
  800.     WaitForSingleObject(pims->hOGQueueMutex, INFINITE);
  801.     fInMutex = TRUE;
  802.  
  803.     /* Get time that the file was last modified */
  804.     hr = HrGetFileModTime(pims->szStorePath, szOutgoingFileName, &ftCurrent);
  805.     if (hr != hrSuccess)
  806.         goto exit;
  807.  
  808.     /* If the time that this process last read the file is the same as */
  809.     /* the time that the file was last modified, then don't do anything */
  810.     /* because we already have all changes, including the one sent to us */
  811.     /* in this notification. This can happen because we picked up two */
  812.     /* changes at once, or because this process is actually the same */
  813.     /* process that sent the notification. */
  814.     if (CompareFileTime(&ftCurrent, &pims->ftOGQueue) == 0)
  815.         goto exit;
  816.  
  817.     /* If the time that this process last read the file is the same as */
  818.     /* the time that the other process read the file, then we can simply */
  819.     /* apply the changes sent in the notification itself, and update our */
  820.     /* time to the time after update sent in the notification. */
  821.     /* If the times are different, or there is a problem applying the */
  822.     /* changes, then reconstruct the table from the on-disk copy. */
  823.  
  824.     if ((CompareFileTime(&ponb->ftBeforeUpdate, &pims->ftOGQueue) == 0)
  825.         && (HrApplyOQNotifChanges(pims->lptblOutgoing, ponb) == hrSuccess))
  826.     {
  827.         pims->ftOGQueue = ponb->ftAfterUpdate;
  828.     }
  829.     else
  830.     {
  831.         hr = HrNewOutgoingTableData(pims);
  832.         if (hr != hrSuccess)
  833.             goto exit;
  834.     }
  835.  
  836. exit:
  837.     if (fInMutex)
  838.         ReleaseMutex(pims->hOGQueueMutex);
  839.  
  840.     IMS_LeaveCriticalSection(pims);
  841.  
  842.     if (hr != hrSuccess)
  843.         sc = GetScode(hr);
  844.  
  845.     return sc;
  846. }
  847.  
  848. /*
  849.  * EmptyTable
  850.  *
  851.  *  Purpose
  852.  *      Deletes all rows from the table data object given.
  853.  *      Helper function for the outgoing queue table notification callback
  854.  *      routine.
  855.  *
  856.  *  Arguments
  857.  *      lptbl: A pointer to the tabledata object to empty.
  858.  *      plmr: A pointer to the linked memory routines.
  859.  *
  860.  *  Returns
  861.  *      void.
  862.  */
  863. static void EmptyTable(LPTABLEDATA lptbl, PLMR plmr)
  864. {
  865.     HRESULT hr;
  866.     LPSRow lpsRow;
  867.     LPSPropValue pval;
  868.     LPSPropValue pvalMac;
  869.  
  870.     while (TRUE)
  871.     {
  872.         /* Get the first row. Note that as we delete rows, this will */
  873.         /* keep giving us a new row. */
  874.  
  875.         hr = lptbl->lpVtbl->HrEnumRow(lptbl, 0, &lpsRow);
  876.         if (hr != hrSuccess)
  877.         {
  878.             TraceSz1("Sample MS: EmptyTable: HrEnumRow failed with"
  879.                 " sc == %s", SzDecodeScode(GetScode(hr)));
  880.             break;
  881.         }
  882.  
  883.         /* The table is empty when no row is returned */
  884.         if (!lpsRow)
  885.             break;
  886.  
  887.         /* find the entryid in the property value array */
  888.         pval = lpsRow->lpProps;
  889.         pvalMac = pval + lpsRow->cValues;
  890.  
  891.         for (; pval < pvalMac; ++pval)
  892.             if (pval->ulPropTag == PR_INSTANCE_KEY)
  893.                 break;
  894.  
  895.         /* Every row should contain an inst key. It is the index property. */
  896.         if (pval == pvalMac)
  897.         {
  898.             TrapSz("No PR_INSTANCE_KEY found in the table row");
  899.             break;
  900.         }
  901.  
  902.         /* delete this row from the table */
  903.         hr = lptbl->lpVtbl->HrDeleteRow(lptbl, pval);
  904.  
  905.         LMFree(plmr, lpsRow);
  906.         lpsRow = NULL;
  907.  
  908.         if (hr != hrSuccess)
  909.         {
  910.             TraceSz1("Sample MS: EmptyTable: HrDeleteRow failed with"
  911.                 " error %s", SzDecodeScode(GetScode(hr)));
  912.             break;
  913.         }
  914.     }
  915.  
  916.     return;
  917. }
  918.  
  919. /*
  920.  * HrApplyOQNotifChanges
  921.  *
  922.  *  Purpose
  923.  *      Helper function of the notification callback routine. If the 
  924.  *      table on disk hasn't changed except for the notification given,
  925.  *      then we can update the outgoing queue table data directly instead
  926.  *      of re-reading the table from disk. This function modifies the
  927.  *      table data directly by calling HrModifyRow (when a row is added),
  928.  *      or HrRemoveRow (when a row is deleted).
  929.  *      Note that we have to convert the notification inside the ONB so
  930.  *      that the pointers are valid. Also note that we may NOT modify the
  931.  *      notification data at all; therefore, we must copy the data before
  932.  *      changing it.
  933.  *
  934.  *  Arguments
  935.  *      lptbl: a pointer to the table data object to update.
  936.  *      ponbIn: a pointer to the ONB received in the callback.
  937.  *
  938.  *  Returns
  939.  *      HRESULT
  940.  */
  941. static HRESULT HrApplyOQNotifChanges(LPTABLEDATA lptbl, PONB ponbIn)
  942. {
  943.     HRESULT hr = hrSuccess;
  944.     SCODE sc;
  945.     ULONG cb;
  946.     LPNOTIFICATION lpntf;
  947.     PONB ponb = NULL;
  948.  
  949.     /* Allocate a new notification block, and copy the data over before */
  950.     /* relocation. */
  951.  
  952.     hr = HrAlloc(CbNewONB(ponbIn->cbNtf), &ponb);
  953.     if (hr != hrSuccess)
  954.         goto exit;
  955.  
  956.     memcpy(ponb, ponbIn, (UINT) CbNewONB(ponbIn->cbNtf));
  957.  
  958.     lpntf = (LPNOTIFICATION) ponb->abNtf;
  959.  
  960.     /* Relocate the notification into the address space of this process. */
  961.     /* We passed along the memory offset of the originating process to allow */
  962.     /* this code to work. Note that ScRelocNotifications currently only */
  963.     /* works from "bad" addresses to "good" addresses. The code assumes that */
  964.     /* pointers are "bad" inside the notification, and that after conversion, */
  965.     /* they are valid. */
  966.  
  967.     sc = ScRelocNotifications(1, lpntf, ponb->pvRef, (LPVOID) lpntf, &cb);
  968.     if (sc != S_OK)
  969.     {
  970.         hr = ResultFromScode(sc);
  971.         goto exit;
  972.     }
  973.  
  974.     if (ponb->cbNtf != cb)
  975.     {
  976.         hr = ResultFromScode(MAPI_E_CALL_FAILED);
  977.         goto exit;
  978.     }
  979.  
  980.     /* We don't expect any events other than those for a table. */
  981.  
  982.     if (lpntf->ulEventType != fnevTableModified)
  983.     {
  984.         TraceSz1("SMS: HrApplyOQNotifChanges: unexpected ulEventType %08lX",
  985.             lpntf->ulEventType);
  986.         hr = ResultFromScode(MAPI_E_CALL_FAILED);
  987.         goto exit;
  988.     }
  989.  
  990.     switch (lpntf->info.tab.ulTableEvent)
  991.     {
  992.     case TABLE_ROW_DELETED:
  993.  
  994.         /* delete the row from the table according to the index */
  995.         /* property value in the notification structure */
  996.         hr = lptbl->lpVtbl->HrDeleteRow(lptbl, &lpntf->info.tab.propIndex);
  997.         if (hr != hrSuccess)
  998.         {
  999.             TraceSz1("SMS: HrApplyOQNotifChanges: HrDeleteRow returns sc == %s",
  1000.                 SzDecodeScode(GetScode(hr)));
  1001.             goto exit;
  1002.         }
  1003.         break;
  1004.  
  1005.     case TABLE_ROW_ADDED:
  1006.  
  1007.         /* add the row to the table. We don't care where in the table */
  1008.         /* it goes, because the row will be sorted by the spooler in */
  1009.         /* its view anyway. */
  1010.  
  1011.         hr = lptbl->lpVtbl->HrModifyRow(lptbl, &lpntf->info.tab.row);
  1012.         if (hr != hrSuccess)
  1013.         {
  1014.             TraceSz1("SMS: HrApplyOQNotifChanges: HrModifyRow returns sc == %s",
  1015.                 SzDecodeScode(GetScode(hr)));
  1016.             goto exit;
  1017.         }
  1018.         break;
  1019.  
  1020.     default:
  1021.  
  1022.         /* We don't expect any other table events than the */
  1023.         /* two above. */
  1024.  
  1025.         TraceSz1("SMS: HrApplyOQNotifChanges: unexpected ulTableEvent %08lX",
  1026.             lpntf->info.tab.ulTableEvent);
  1027.         break;
  1028.     }
  1029.  
  1030. exit:
  1031.     FreeNull(ponb);
  1032.  
  1033.     DebugTraceResult(HrApplyOQNotifChanges, hr);
  1034.     return hr;
  1035. }
  1036.  
  1037. /*
  1038.  * HrGetOutgoingNotificationKey
  1039.  *
  1040.  * Purpose  return the nofication key for the outgoing queue
  1041.  *          memory should be freed with FreeNull
  1042.  *          The key we use contains the full pathname to the outgoing
  1043.  *          queue file on disk. This should be unique for the store
  1044.  *          that we're running against.
  1045.  *
  1046.  * Parameters
  1047.  * pims         the store whose outgoing queue is being referred to
  1048.  * lppKey       pointer to the key
  1049.  */
  1050. static HRESULT HrGetOutgoingNotificationKey(PIMS pims, LPNOTIFKEY * lppKey)
  1051. {
  1052.     HRESULT hr = hrSuccess;
  1053.     LPNOTIFKEY lpKey = NULL;
  1054.     ULONG cb;                   /* number of bytes in the key */
  1055.     LPSTR szPath = NULL;        /* path to outgoing queue */
  1056.  
  1057.     hr = HrGetTableName((POBJ) pims, NULL, szOutgoingFileName, &szPath);
  1058.     if (HR_FAILED(hr))
  1059.         goto exit;
  1060.  
  1061.     /* allocate space for the key */
  1062.     cb = Cbtszsize(szPath);
  1063.     hr = HrAlloc(CbNewNOTIFKEY(cb), (PPV) &lpKey);
  1064.     if (hr != hrSuccess)
  1065.         goto exit;
  1066.  
  1067.     lstrcpy(lpKey->ab, szPath);
  1068.     lpKey->cb = cb;
  1069.  
  1070. exit:
  1071.     FreeNull(szPath);
  1072.     if (HR_FAILED(hr))
  1073.     {
  1074.         FreeNull(lpKey);
  1075.         lpKey = NULL;
  1076.     }
  1077.     *lppKey = lpKey;
  1078.     return hr;
  1079. }
  1080.  
  1081. /*
  1082.  * HrNotifyOnOutgoingQueue
  1083.  *
  1084.  * Purpose
  1085.  *  Send out a notification that the Outgoing Queue has had a row added
  1086.  *  or deleted. Also send the filetime of the queue file before and after
  1087.  *  the modification.
  1088.  *
  1089.  * Parameters
  1090.  *  pims: A pointer to the message store object.
  1091.  *  peid: (For TABLE_ROW_DELETED) The entryid of the message in the
  1092.  *          queue that was deleted.
  1093.  *  prw: (For TABLE_ROW_ADDED) A pointer to the row of data added
  1094.  *          to the OG Queue.
  1095.  *  ulTableEvent: Either TABLE_ROW_ADDED or TABLE_ROW_DELETED.
  1096.  *  pftBeforeUpdate: A pointer to the filetime of the queue file before the
  1097.  *          update was performed.
  1098.  */
  1099. static HRESULT HrNotifyOnOutgoingQueue(PIMS pims, PEID peid, LPSRow prw,
  1100.     ULONG ulTableEvent, FILETIME *pftBeforeUpdate)
  1101. {
  1102.     HRESULT hr;
  1103.     LPNOTIFKEY lpKey = NULL;
  1104.     ULONG ulFlags = 0;
  1105.     NOTIFICATION ntf;
  1106.     NOTIFICATION ntfTemp;
  1107.     PONB ponb = NULL;
  1108.     ULONG cbNtf;
  1109.     ULONG cbOut;
  1110.     SCODE sc;
  1111.  
  1112.     /* get the key */
  1113.     hr = HrGetOutgoingNotificationKey(pims, &lpKey);
  1114.     if (HR_FAILED(hr))
  1115.         goto exit;
  1116.  
  1117.     /* Assemble the notification. */
  1118.     ntfTemp.ulEventType = fnevTableModified;
  1119.     ntfTemp.info.tab.ulTableEvent = ulTableEvent;
  1120.     ntfTemp.info.tab.hResult = hrSuccess;
  1121.  
  1122.     if (ulTableEvent == TABLE_ROW_DELETED)
  1123.     {
  1124.         /* Send across the index property for the row: PR_INSTANCE_KEY */
  1125.         ntfTemp.info.tab.propIndex.ulPropTag = PR_INSTANCE_KEY;
  1126.         ntfTemp.info.tab.propIndex.Value.bin.cb = CbEID(peid);
  1127.         ntfTemp.info.tab.propIndex.Value.bin.lpb = (BYTE *) peid;
  1128.         memset(&(ntfTemp.info.tab.propPrior), 0, sizeof(SPropValue));
  1129.         ntfTemp.info.tab.row.cValues = 0;
  1130.         ntfTemp.info.tab.row.lpProps = NULL;
  1131.     }
  1132.     else
  1133.     {
  1134.         AssertSz(ulTableEvent == TABLE_ROW_ADDED,
  1135.             "Bad event type: about to send bogus internal notification");
  1136.  
  1137.         memset(&(ntfTemp.info.tab.propIndex), 0, sizeof(SPropValue));
  1138.         memset(&(ntfTemp.info.tab.propPrior), 0, sizeof(SPropValue));
  1139.         ntfTemp.info.tab.row = *prw;
  1140.     }
  1141.  
  1142.     sc = ScCountNotifications(1, &ntfTemp, &cbNtf);
  1143.     if (sc != S_OK)
  1144.     {
  1145.         hr = ResultFromScode(sc);
  1146.         goto exit;
  1147.     }
  1148.  
  1149.     hr = HrAlloc(CbNewONB(cbNtf), &ponb);
  1150.     if (hr != hrSuccess)
  1151.         goto exit;
  1152.  
  1153.     ponb->cbNtf = cbNtf;
  1154.  
  1155.     sc = ScCopyNotifications(1, &ntfTemp, (LPVOID) ponb->abNtf, &cbOut);
  1156.     if (sc != S_OK)
  1157.     {
  1158.         hr = ResultFromScode(sc);
  1159.         goto exit;
  1160.     }
  1161.  
  1162.     AssertSz(cbOut == cbNtf, "ScCopyNotifications used a different # of bytes "
  1163.         "than ScCountNotifications returned.");
  1164.  
  1165.     ponb->ftBeforeUpdate = *pftBeforeUpdate;
  1166.     ponb->ftAfterUpdate = pims->ftOGQueue;
  1167.  
  1168.     /* Pass across the notification's memory offset so that the receiving */
  1169.     /* process can relocate the notification to its address space. */
  1170.     ponb->pvRef = (LPVOID) ponb->abNtf;
  1171.     
  1172.     ntf.ulEventType = fnevExtended;
  1173.     ntf.info.ext.ulEvent = 0;
  1174.     ntf.info.ext.cb = CbONB(ponb);
  1175.     ntf.info.ext.pbEventParameters = (LPBYTE) ponb;
  1176.  
  1177.     hr = pims->psup->lpVtbl->Notify(pims->psup, lpKey, 1, &ntf, &ulFlags);
  1178.  
  1179. exit:
  1180.     FreeNull(lpKey);
  1181.     FreeNull(ponb);
  1182.  
  1183.     DebugTraceResult(HrNotifyOnOutgoingQueue, hr);
  1184.     return hr;
  1185. }
  1186.  
  1187.  
  1188.