CFGNOTIF.C
// --cfgnotif.c----------------------------------------------------------------- 
// 
//  Routines to provide notification when a given extension data section has  
//  changed, and read the new data. 
// 
// Copyright (C) Microsoft Corp. 1986-1996.  All Rights Reserved. 
// ----------------------------------------------------------------------------- 
 
#include "edk.h" 
#include "attrname.h" 
#include "cfgnotif.chk" 
 
// 
// Internal Function Declarations 
// 
 
static DWORD WINAPI PollThread( 
    IN LPVOID lpvThreadParam); 
 
static HRESULT HrUpdateAdviseNotify( 
    IN      LPSPropValue    lpNewData, 
    IN OUT  LPADVISENOTIFY  lpNotify); 
 
static HRESULT HrDestroyAdviseNotify( 
    IN OUT  LPADVISENOTIFY * lppNotify); 
 
// 
// Configuration API Routines 
// 
 
//$--HrCfgCreateAdviseObj------------------------------------------------------- 
//  Begin monitoring of a MAPI session for changes to extension data. 
//  This routine should be called once to create an advise object for a MAPI  
//  session that is being monitored.  Then, HrCfgAdvise() should be called  
//  once for each extension data section that is being monitored. 
//  If nPollInterval == 0 then no monitoring thread is created, and the user  
//  must do polling manually by calling HrCfgPollAdviseObj(). 
// ----------------------------------------------------------------------------- 
HRESULT HrCfgCreateAdviseObj(               // RETURNS: HRESULT 
    IN  LPMAPISESSION       lpSession,      // MAPI session to monitor 
    IN  ULONG               nPollInterval,  // milliseconds between polling 
    OUT LPADVISEOBJ *       lppAdviseObj)   // created advise object 
{ 
    HRESULT                 hr              = NOERROR; 
    LPADVISEOBJ             lpAdviseObj     = NULL; 
SECURITY_ATTRIBUTES     saEvent         = {0, NULL, TRUE}; 
    BOOL                    fCriticalSectionInitialized     = FALSE; 
 
    hr = CHK_HrCfgCreateAdviseObj( 
        lpSession,  
        nPollInterval,  
        lppAdviseObj); 
    if (FAILED(hr)) 
        RETURN(hr); 
 
    DEBUGPUBLIC("HrCfgCreateAdviseObj()\n"); 
 
    // Get memory for the advise object. 
 
    hr = MAPIAllocateBuffer(sizeof(*lpAdviseObj), &lpAdviseObj); 
    if (FAILED(hr)) 
        goto cleanup; 
 
    ZeroMemory(lpAdviseObj, sizeof(*lpAdviseObj)); 
 
    // Store parameters in advise object. 
 
    lpAdviseObj->lpSession = lpSession; 
    lpAdviseObj->nPollInterval = nPollInterval; 
 
    // Get an IMAPIProp interface on the current object. 
 
    hr = HrOpenSessionObject(lpSession, &lpAdviseObj->lpCurrentObject); 
    if (FAILED(hr)) 
        goto cleanup; 
 
    // Get the property tag for USN-Changed. 
 
    hr = HrCfgPropertyTagFromNameId( 
        lpAdviseObj->lpCurrentObject,  
        NM_USN_CHANGED,  
        &lpAdviseObj->ulUSNChangedPropTag); 
    if (FAILED(hr)) 
        goto cleanup; 
 
    // If it came back without the property type then set it to PT_LONG. 
 
    if (PROP_TYPE(lpAdviseObj->ulUSNChangedPropTag) == 0) 
    { 
        lpAdviseObj->ulUSNChangedPropTag =  
            CHANGE_PROP_TYPE(lpAdviseObj->ulUSNChangedPropTag,  
            PT_LONG); 
    } 
 
    // Get the property tag for Extension-Data. 
 
    hr = HrCfgPropertyTagFromNameId( 
        lpAdviseObj->lpCurrentObject,  
        NM_EXTENSION_DATA,  
        &lpAdviseObj->ulExtensionDataPropTag); 
    if (FAILED(hr)) 
        goto cleanup; 
 
    // If it came back without the property type then set it to PT_MV_BINARY. 
 
    if (PROP_TYPE(lpAdviseObj->ulExtensionDataPropTag) == 0) 
    { 
        lpAdviseObj->ulExtensionDataPropTag =  
            CHANGE_PROP_TYPE(lpAdviseObj->ulExtensionDataPropTag,  
            PT_MV_BINARY); 
    } 
 
    // Create a critical section to synchronize access to the advise object. 
 
    InitializeCriticalSection(&lpAdviseObj->csCriticalSection); 
    fCriticalSectionInitialized = TRUE; 
 
    // "Poll" the object to get the initial values of the extension data blobs. 
 
    hr = HrCfgPollAdviseObj(lpAdviseObj); 
    if (FAILED(hr)) 
        goto cleanup;  
 
    // Only create the thread if nPollInterval != 0. 
 
    if (nPollInterval != 0) 
    { 
        // Create an event to stop the polling thread. 
 
        lpAdviseObj->hPollThreadStop = CreateEvent(&saEvent, FALSE, FALSE, NULL); 
    if (lpAdviseObj->hPollThreadStop == NULL) 
    { 
    hr = HR_LOG(E_FAIL); 
    goto cleanup; 
    } 
 
    // Create the polling thread. 
 
    lpAdviseObj->hPollThread = CreateThread( 
        NULL,  
        0,  
        PollThread,  
        lpAdviseObj,  
        0,  
    &lpAdviseObj->ulPollThreadID); 
    if (lpAdviseObj->hPollThread == NULL) 
    { 
    hr = HR_LOG(E_FAIL); 
    goto cleanup; 
    } 
    } 
 
    // Set the return variable. 
 
    *lppAdviseObj = lpAdviseObj; 
 
cleanup: 
    if (FAILED(hr) && lpAdviseObj) 
    { 
        CLOSEHANDLE(lpAdviseObj->hPollThreadStop); 
 
        if (fCriticalSectionInitialized) 
            DeleteCriticalSection(&lpAdviseObj->csCriticalSection); 
 
        ULRELEASE(lpAdviseObj->lpCurrentObject); 
 
        MAPIFREEBUFFER(lpAdviseObj); 
    } 
 
    RETURN(hr); 
} 
 
//$--HrCfgPollAdviseObj--------------------------------------------------------- 
//  Checks an advise object against what is stored in the DSA.  Calls the  
//  notification routines if there are any changes. 
// ----------------------------------------------------------------------------- 
HRESULT HrCfgPollAdviseObj(                 // RETURNS: HRESULT 
    IN  LPADVISEOBJ     lpAdviseObj)        // advise object to poll 
{ 
    HRESULT             hr                  = NOERROR; 
    ULONG               ulNewUSNChanged     = 0; 
    LPSPropValue        lpNewExtensionData  = NULL; 
    ULONG               cValues             = 0; 
    LPADVISENOTIFY      lpAdviseNotify      = NULL; 
 
    hr = CHK_HrCfgPollAdviseObj( 
        lpAdviseObj); 
    if (FAILED(hr)) 
        RETURN(hr); 
 
    DEBUGPUBLIC("HrCfgPollAdviseObj()\n"); 
 
    // Lock the object against access by other threads. 
 
    EnterCriticalSection(&lpAdviseObj->csCriticalSection); 
 
    // Get the new USN-Changed from the DSA.  If the new USN-Changed is  
    // different from the one we have stored then something in the object  
    // has changed (possibly the extension data).  Note: the first time these  
    // statements are executed, USN-Changed will always have "changed" since  
    // the one we have stored starts out at 0. 
 
    hr = HrMAPIGetPropLong( 
        lpAdviseObj->lpCurrentObject,  
        lpAdviseObj->ulUSNChangedPropTag,  
        &ulNewUSNChanged); 
    if (FAILED(hr)) 
        goto cleanup; 
 
    // If USN-Changed is different then do the update. 
 
    if (ulNewUSNChanged != lpAdviseObj->ulUSNChanged) 
    { 
        SizedSPropTagArray(1, ExtDataTag) =  
            {1, {lpAdviseObj->ulExtensionDataPropTag}}; 
 
        // Read the new extension data. 
 
        hr = MAPICALL(lpAdviseObj->lpCurrentObject)->GetProps( 
            lpAdviseObj->lpCurrentObject,  
            (LPSPropTagArray) &ExtDataTag,  
            0,  
            &cValues,  
            &lpNewExtensionData); 
 
        // If the property was not found then allocate an empty multivalued  
        // binary property and store it as if it had been read from the  
        // extension data property. 
 
        if (hr == MAPI_W_ERRORS_RETURNED &&  
            cValues == 1 &&  
            PROP_TYPE(lpNewExtensionData->ulPropTag) == PT_ERROR &&  
            lpNewExtensionData->Value.err == MAPI_E_NOT_FOUND) 
        { 
            lpNewExtensionData->ulPropTag = lpAdviseObj->ulExtensionDataPropTag; 
            lpNewExtensionData->Value.MVbin.cValues = 0; 
 
            hr = MAPIAllocateMore( 
                0,  
                lpNewExtensionData,  
                &lpNewExtensionData->Value.MVbin.lpbin); 
            if (FAILED(hr)) 
                goto cleanup; 
 
            hr = NOERROR; 
        } 
 
        // If there was any other error then return. 
 
        if (FAILED(hr) || hr == MAPI_W_ERRORS_RETURNED) 
            goto cleanup; 
 
        // Loop for each ADVISENOTIFY and check to see if it's data has changed. 
        // If it has, update it and call the callback routine.  After this  
        // loop is done, the blob pointers in the ADVISENOTIFY structures will  
        // point into the new extension data property, not the old one.  Then,  
        // the old one can be freed and replaced in the ADVISEOBJ structure  
        // by the new one. 
 
        for ( 
            lpAdviseNotify = lpAdviseObj->lpNotifyList;  
            lpAdviseNotify;  
            lpAdviseNotify = lpAdviseNotify->lpNext) 
        { 
            hr = HrUpdateAdviseNotify(lpNewExtensionData, lpAdviseNotify); 
            if (FAILED(hr)) 
                goto cleanup; 
        } 
 
        // Replace the old extension data property with the new one. 
 
        MAPIFREEBUFFER(lpAdviseObj->lpExtensionData); 
        lpAdviseObj->lpExtensionData = lpNewExtensionData; 
        lpNewExtensionData = NULL; 
 
        // Replace the old USN-Changed value with the new one. 
 
        lpAdviseObj->ulUSNChanged = ulNewUSNChanged; 
    } 
 
cleanup: 
    MAPIFREEBUFFER(lpNewExtensionData); 
 
    // Unlock the object to allow access by other threads. 
 
    LeaveCriticalSection(&lpAdviseObj->csCriticalSection); 
 
    RETURN(hr); 
} 
 
//$--HrCfgDestroyAdviseObj------------------------------------------------------ 
//  End monitoring of a MAPI session.  This routine calls HrCfgUnadvise() for  
//  any extension data sections that are actively being monitored. 
// ----------------------------------------------------------------------------- 
HRESULT HrCfgDestroyAdviseObj(              // RETURNS: HRESULT 
    IN  LPADVISEOBJ         lpAdviseObj)    // advise object to destroy 
{ 
    HRESULT             hr                  = NOERROR; 
    HRESULT             hrT                 = NOERROR; 
    BOOL                fItWorked           = TRUE; 
    DWORD               dwStatus            = 0; 
 
    DEBUGPUBLIC("HrCfgDestroyAdviseObj()\n"); 
 
    hr = CHK_HrCfgDestroyAdviseObj( 
        lpAdviseObj); 
    if (FAILED(hr)) 
        RETURN(hr); 
 
    if (lpAdviseObj) 
    { 
        // Signal the thread to stop and then release the signal event. 
 
        if (lpAdviseObj->hPollThreadStop) 
        { 
            fItWorked = SetEvent(lpAdviseObj->hPollThreadStop); 
            if (!fItWorked) 
            { 
                hr = HR_LOG(E_FAIL); 
            } 
            CLOSEHANDLE(lpAdviseObj->hPollThreadStop); 
        } 
 
        // Wait for thread to terminate and then release it's handle. 
 
        if (lpAdviseObj->hPollThread) 
        { 
            dwStatus = WaitForSingleObject(lpAdviseObj->hPollThread, INFINITE); 
            if (dwStatus != WAIT_OBJECT_0) 
            { 
                hr = HR_LOG(E_FAIL); 
            } 
            CLOSEHANDLE(lpAdviseObj->hPollThread); 
        } 
 
        // Free the notification structures. 
 
        while (lpAdviseObj->lpNotifyList) 
        { 
            hrT = HrDestroyAdviseNotify(&lpAdviseObj->lpNotifyList); 
            if (FAILED(hrT)) 
            { 
                hr = hrT; 
            } 
        } 
 
        // Delete the critical section. 
 
        DeleteCriticalSection(&lpAdviseObj->csCriticalSection); 
 
        // Release the IMAPIProp interface. 
 
        ULRELEASE(lpAdviseObj->lpCurrentObject); 
 
        // Free the extension data property. 
 
        MAPIFREEBUFFER(lpAdviseObj->lpExtensionData); 
 
        // Free the advise object itself. 
 
        MAPIFREEBUFFER(lpAdviseObj); 
    } 
 
    RETURN(hr); 
} 
 
//$--HrCfgAdvise---------------------------------------------------------------- 
//  Begin monitoring of an extension data section.  When the extension data  
//  changes, the specified callback routine is called.  Note: The callback  
//  routine will be called once from hrCfgAdvise() to set the initial extension  
//  data values. 
// ----------------------------------------------------------------------------- 
HRESULT HrCfgAdvise(                        // RETURNS: HRESULT 
    IN  LPADVISEOBJ         lpAdviseObj,    // advise object 
    IN  LPSTR              lpszSection,    // name of extension data section 
    IN  LPADVISECALLBACK    lpfnCallback,   // function to call on changes 
    IN  LPVOID              lpvUserContext) // user-defined context 
{ 
    HRESULT                 hr              = NOERROR; 
    LPADVISENOTIFY          lpNotify        = NULL; 
    ULONG                   cchSection      = 0; 
    ULONG                   cchActual       = 0; 
 
    DEBUGPUBLIC("HrCfgAdvise()\n"); 
 
    hr = CHK_HrCfgAdvise( 
        lpAdviseObj,  
        lpszSection,  
        lpfnCallback,  
        lpvUserContext); 
    if (FAILED(hr)) 
        RETURN(hr); 
 
    // Allocate memory for the ADVISENOTIFY structure. 
 
    hr = MAPIAllocateBuffer( 
        sizeof(*lpNotify),  
        &lpNotify); 
    if (FAILED(hr)) 
        goto cleanup; 
    ZeroMemory(lpNotify, sizeof(*lpNotify)); 
 
    // Get the number of characters in the section name. 
 
    cchSection = lstrlen(lpszSection); 
 
    // Allocate memory for the LPSTR section name and copy the section  
    // name into it. 
 
    hr = MAPIAllocateMore( 
        cchSection + 1,  
        lpNotify,  
        &lpNotify->lpszOwnerTag); 
    if (FAILED(hr)) 
        goto cleanup; 
 
    lstrcpy(lpNotify->lpszOwnerTag, lpszSection); 
 
    // Allocate memory for the UNICODE section name, and copy the section  
    // name into it, converting to UNICODE. 
 
    hr = MAPIAllocateMore( 
        (cchSection + 1) * sizeof(WCHAR),  
        lpNotify,  
        &lpNotify->lpwszOwnerTag); 
    if (FAILED(hr)) 
        goto cleanup; 
 
SetLastError(ERROR_SUCCESS); 
 
    cchActual = wsprintfW(lpNotify->lpwszOwnerTag, L"%hs", lpszSection); 
if (GetLastError() != ERROR_SUCCESS || cchActual != cchSection) 
{ 
hr = HR_LOG(E_FAIL); 
goto cleanup; 
} 
 
    // Fill in the structure members. 
 
    lpNotify->lpfnCallback = lpfnCallback; 
    lpNotify->lpvUserContext = lpvUserContext; 
 
    // Lock the object against access by other threads. 
 
    EnterCriticalSection(&lpAdviseObj->csCriticalSection); 
 
    // Cause the callback routine to be called for the first time. 
 
    lpNotify->fFirstTime = TRUE; 
    hr = HrUpdateAdviseNotify(lpAdviseObj->lpExtensionData, lpNotify); 
 
    // NOTE: Don't check the return value for failure until after  
    // we leave the critical section. 
 
    // Link the structure into the object's list. 
 
    if (SUCCEEDED(hr)) 
    { 
        lpNotify->lpNext = lpAdviseObj->lpNotifyList; 
        lpAdviseObj->lpNotifyList = lpNotify; 
    } 
 
    // Unlock the object to allow access by other threads. 
 
    LeaveCriticalSection(&lpAdviseObj->csCriticalSection); 
 
cleanup: 
    if (FAILED(hr)) 
    { 
        MAPIFREEBUFFER(lpNotify); 
    } 
 
    RETURN(hr); 
} 
 
//$--HrCfgUnadvise-------------------------------------------------------------- 
//  End monitoring of an extension data section. 
// ----------------------------------------------------------------------------- 
HRESULT HrCfgUnadvise(                      // RETURNS: HRESULT 
    IN  LPADVISEOBJ         lpAdviseObj,    // advise object 
    IN  LPSTR              lpszSection)    // name of extension data section 
{ 
    HRESULT                 hr              = NOERROR; 
    LPADVISENOTIFY *        lppNotify       = NULL; 
    BOOL                    fFound          = FALSE; 
 
    DEBUGPUBLIC("HrCfgAdvise()\n"); 
 
    hr = CHK_HrCfgUnadvise( 
        lpAdviseObj,  
        lpszSection); 
    if (FAILED(hr)) 
        RETURN(hr); 
 
    // Lock the object against access by other threads. 
 
    EnterCriticalSection(&lpAdviseObj->csCriticalSection); 
 
    // Search the list of ADVISENOTIFY structures for the section name we want. 
 
    for ( 
        lppNotify = &lpAdviseObj->lpNotifyList;  
        *lppNotify;  
        lppNotify = &((*lppNotify)->lpNext)) 
    { 
        if (!lstrcmp(lpszSection, (*lppNotify)->lpszOwnerTag)) 
        { 
            // Found it, so destroy it. 
 
            hr = HrDestroyAdviseNotify(lppNotify); 
 
            fFound = TRUE; 
            break; 
        } 
    } 
 
    if (!fFound) 
    { 
        hr = HR_LOG(E_FAIL); 
    } 
 
    // Unlock the object to allow access by other threads. 
 
    LeaveCriticalSection(&lpAdviseObj->csCriticalSection); 
 
    RETURN(hr); 
} 
 
// 
// Local Helper Routines 
// 
 
//$--PollThread----------------------------------------------------------------- 
//  Function that is called as a seperate thread.  It wakes up periodically  
//  and polls the extension data sections.  If any have changed, it calls  
//  the routine registered for that section. 
// ----------------------------------------------------------------------------- 
static DWORD WINAPI PollThread(             // RETURNS: error code 
    IN  LPVOID          lpvThreadParam)     // pointer to advise object 
{ 
    HRESULT             hr                  = NOERROR; 
LPADVISEOBJ         lpAdviseObj         = lpvThreadParam; 
    BOOL                fDone               = FALSE; 
    DWORD               dwStatus            = 0; 
 
    DEBUGPRIVATE("PollThread()\n"); 
 
    hr = CHK_PollThread( 
        lpvThreadParam); 
    if (FAILED(hr)) 
        RETURN(hr); 
 
    // Main loop: sleep, poll object. 
 
    while (!fDone) 
    { 
        // Sleep and wait for the timer. 
 
        dwStatus = WaitForSingleObject( 
            lpAdviseObj->hPollThreadStop,  
            lpAdviseObj->nPollInterval); 
 
switch (dwStatus) 
{ 
case WAIT_TIMEOUT: 
                // Poll the object. 
                hr = HrCfgPollAdviseObj(lpAdviseObj); 
                if (FAILED(hr)) 
                    goto cleanup; 
                break; 
 
case WAIT_OBJECT_0: 
                // Thread terminate request. 
fDone = TRUE; 
break; 
 
default: 
// Some kind of error. 
                hr = HR_LOG(E_FAIL); 
                goto cleanup; 
} 
    } 
 
cleanup: 
    return(ERROR_SUCCESS); 
} 
 
//$--HrUpdateAdviseNotify------------------------------------------------------- 
//  Compare new extension data property with the old one for changes to the  
//  blob defined by the ADVISENOTIFY structure.  If it has changed, then call  
//  the callback routine.  In any case, point the ADVISENOTIFY structure at the  
//  new extension data blob, so that the old one may be freed. 
// ----------------------------------------------------------------------------- 
static HRESULT HrUpdateAdviseNotify(        // RETURNS: HRESULT 
    IN      LPSPropValue    lpNewData,      // new extension data 
    IN OUT  LPADVISENOTIFY  lpNotify)       // notification structure 
{ 
    HRESULT             hr                  = NOERROR; 
    LPSBinary           lpNewBlobArray      = NULL; 
    ULONG               cNewBlobArray       = 0; 
    ULONG               iBlob               = 0; 
    LPBYTE              lpbNewBlob          = NULL; 
    ULONG               cbNewBlob           = 0; 
    LPSPropValue        lpUnpackedProps     = NULL; 
    ULONG               cUnpackedProps      = 0; 
    LPSTR              lpszUnpackedName    = NULL; 
 
    DEBUGPRIVATE("HrUpdateAdviseNotify()\n"); 
 
    hr = CHK_HrUpdateAdviseNotify( 
        lpNewData,  
        lpNotify); 
    if (FAILED(hr)) 
        RETURN(hr); 
 
    // Find the owner tag in the new extension data property array. 
 
    if (lpNewData) 
    { 
        lpNewBlobArray = lpNewData->Value.MVbin.lpbin; 
        cNewBlobArray = lpNewData->Value.MVbin.cValues; 
 
        for (iBlob = 0; iBlob < cNewBlobArray; iBlob++) 
        { 
            if (!wcscmp( 
                lpNotify->lpwszOwnerTag,  
                (LPWSTR) lpNewBlobArray[iBlob].lpb)) 
            { 
                lpbNewBlob = lpNewBlobArray[iBlob].lpb; 
                cbNewBlob = lpNewBlobArray[iBlob].cb; 
            } 
        } 
    } 
 
    // If the blob has changed then unpack the properties and call the  
    // callback routine. 
 
    if (lpNotify->fFirstTime ||  
        lpNotify->cbBlob != cbNewBlob ||  
        (lpNotify->lpbBlob &&  
        lpbNewBlob &&  
        memcmp(lpNotify->lpbBlob, lpbNewBlob, cbNewBlob))) 
    { 
        // If the new blob exists then unpack the properties. 
        // Otherwise, use a NULL pointer for a non-existant blob. 
 
        if (lpbNewBlob) 
        { 
            hr = HrCfgUnpackData( 
                cbNewBlob,  
                lpbNewBlob,  
                &lpszUnpackedName,  
                &cUnpackedProps,  
                &lpUnpackedProps); 
            if (FAILED(hr)) 
                goto cleanup; 
        } 
 
        // Call the callback routine. 
 
        hr = (*lpNotify->lpfnCallback)( 
            lpNotify->lpvUserContext,  
            lpNotify->lpwszOwnerTag,  
            cUnpackedProps,  
            lpUnpackedProps); 
        if (FAILED(hr)) 
            goto cleanup; 
 
        // If the callback succeeded then zero out the unpacked property  
        // variables so we don't free them (that's the user's responsiblity). 
 
        cUnpackedProps = 0; 
        lpUnpackedProps = NULL; 
 
        // Callback has now been called at least once. 
 
        lpNotify->fFirstTime = FALSE; 
    } 
 
    // Replace the old blob with the new one in the ADVISENOTIFY structure. 
 
    lpNotify->lpbBlob = lpbNewBlob; 
    lpNotify->cbBlob = cbNewBlob; 
 
cleanup: 
    MAPIFREEBUFFER(lpszUnpackedName); 
    if (FAILED(hr)) 
    { 
        MAPIFREEBUFFER(lpUnpackedProps); 
    } 
 
    RETURN(hr); 
} 
 
//$--HrDestroyAdviseNotify------------------------------------------------------ 
//  Destroy an ADVISENOTIFY structure and remove it from the linked list. 
//  This routine takes as its argument the address of a pointer to the  
//  structure to be destroyed and returns with that pointer pointing to  
//  the next structure in the linked list. 
// ----------------------------------------------------------------------------- 
static HRESULT HrDestroyAdviseNotify(       // RETURNS: HRESULT 
    IN OUT  LPADVISENOTIFY * lppNotify)     // address of ptr to ADVISENOTIFY 
{ 
    HRESULT             hr                  = NOERROR; 
    LPADVISENOTIFY      lpNotify            = NULL; 
 
    DEBUGPRIVATE("HrDestroyAdviseNotify()\n"); 
 
    hr = CHK_HrDestroyAdviseNotify( 
        lppNotify); 
    if (FAILED(hr)) 
        RETURN(hr); 
 
    // Save pointer to the structure. 
 
    lpNotify = *lppNotify; 
 
    // Take structure out of list, by making whatever pointed to it point  
    // to the next thing on the list. 
 
    *lppNotify = lpNotify->lpNext; 
 
    // Free the structure. 
 
    MAPIFREEBUFFER(lpNotify); 
 
    RETURN(hr); 
}