/**************************** Module Header ********************************\ * Module Name: winable.c * * This has the stuff for WinEvents: * NotifyWinEvent * _SetWinEventHook * UnhookWinEventHook * * All other additions to USER for Active Accessibility are in winable2.c. * * Copyright (c) 1985 - 1999, Microsoft Corporation * * History: * Based on snapshot taken from: * \\trango\slmro\proj\win\src\CORE\access\user_40\user32 on 8/29/96 * 08-30-96 IanJa Ported from Windows '95 \***************************************************************************/ #include "precomp.h" #pragma hdrstop #if DBG int gnNotifies; __inline VOID DBGVERIFYEVENTHOOK( PEVENTHOOK peh) { HMValidateCatHandleNoSecure(PtoH(peh), TYPE_WINEVENTHOOK); UserAssert(peh->eventMin <= peh->eventMax); } __inline VOID DBGVERIFYNOTIFY( PNOTIFY pNotify) { UserAssert(pNotify->spEventHook != NULL); UserAssert(pNotify->spEventHook->fSync || (pNotify->dwWEFlags & WEF_ASYNC)); } #else #define DBGVERIFYEVENTHOOK(peh) #define DBGVERIFYNOTIFY(pNotify) #endif /* * Pending Event Notifications (sync and async). */ static NOTIFY notifyCache; static BOOL fNotifyCacheInUse; /* * Local to this module. */ WINEVENTPROC xxxGetEventProc( PEVENTHOOK pEventOrg); PNOTIFY CreateNotify( PEVENTHOOK peh, DWORD event, PWND pwnd, LONG idObject, LONG idChild, PTHREADINFO ptiEvent, DWORD dwTime); /*****************************************************************************\ * xxxProcessNotifyWinEvent * * Posts or Sends a WinEvent notification. * * Post: uses PostEventMesage - does not leave the critical section. * Send: makes a callback to user-mode - does leave the critical section. * * If this is a system thread (RIT, Desktop or Console) then synchronously * hooked (WINEVENT_INCONTEXT) events are forced to be asynchronous. * * We return the next win event hook in the list. \*****************************************************************************/ PEVENTHOOK xxxProcessNotifyWinEvent( PNOTIFY pNotify) { WINEVENTPROC pfn; PEVENTHOOK pEventHook; TL tlpEventHook; PTHREADINFO ptiCurrent = PtiCurrent(); pEventHook = pNotify->spEventHook; DBGVERIFYEVENTHOOK(pEventHook); UserAssert(pEventHook->head.cLockObj); if (((pNotify->dwWEFlags & (WEF_ASYNC | WEF_POSTED)) == WEF_ASYNC) || (ptiCurrent->TIF_flags & (TIF_SYSTEMTHREAD | TIF_CSRSSTHREAD | TIF_INCLEANUP)) || (!RtlEqualLuid(&GETPTI(pEventHook)->ppi->luidSession, &ptiCurrent->ppi->luidSession) && !(ptiCurrent->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK)) || (GETPTI(pEventHook)->ppi != ptiCurrent->ppi && IsRestricted(GETPTI(pEventHook)->pEThread)) #if defined(_WIN64) || ((GETPTI(pEventHook)->TIF_flags & TIF_WOW64) != (ptiCurrent->TIF_flags & TIF_WOW64)) #endif ) { /* * POST * * WinEvent Hook set without WINEVENT_INCONTEXT flag are posted; * Events from system threads are posted because there is no user-mode * part to callback to; * Console is not permitted to load DLLs, so we must post back to the * hooking application; * DLLs can not be loaded cross bit type(32bit to 64bit) on 64bit NT * so we must post(It may be usefull to let the app be aware and * even supply both a 32bit and a 64bit DLL that are aware of each other); * Threads in cleanup can't get called back, so turn their * notifications into async ones. (Better late than never). * * If forcing these events ASYNC is unacceptable, we might consider * doing system/console SYNC events like low-level hooks (sync with * timeout: but may have to post it if the timeout expires) - IanJa */ PQ pqReceiver = GETPTI(pEventHook)->pq; PEVENTHOOK pEventHookNext = pEventHook->pehNext; BEGINATOMICCHECK(); DBGVERIFYNOTIFY(pNotify); pNotify->dwWEFlags |= WEF_POSTED | WEF_ASYNC; if (!pqReceiver || (GETPTI(pEventHook) == gptiRit) || pEventHook->fDestroyed || !PostEventMessage(GETPTI(pEventHook), pqReceiver, QEVENT_NOTIFYWINEVENT, NULL, 0, 0, (LPARAM)pNotify)) { /* * If the receiver doesn't have a queue or the * post failed (low memory), cleanup what we just * created. * Note: destroying the notification may destroy pEventHook too. */ RIPMSG2(RIP_WARNING, "Failed to post NOTIFY at 0x%p, time %lx", pNotify, pNotify->dwEventTime); DestroyNotify(pNotify); } ENDATOMICCHECK(); if (pEventHookNext) { DBGVERIFYEVENTHOOK(pEventHookNext); } return pEventHookNext; } /* * Don't call back if the hook has been destroyed (unhooked). */ if (pEventHook->fDestroyed) { /* * Save the next hook since DestroyNotify may cause pEventHook to * be freed by unlocking it. */ pEventHook = pEventHook->pehNext; DestroyNotify(pNotify); return pEventHook; } /* * CALLBACK * * This leaves the critical section. * We return the next Event Hook in the list so that the caller doesn't * have to lock pEventHook. */ UserAssert((pNotify->dwWEFlags & WEF_DEFERNOTIFY) == 0); ThreadLockAlways(pEventHook, &tlpEventHook); UserAssertMsg1(pNotify->ptiReceiver == NULL, "pNotify %#p is already in callback! Reentrant?", pNotify); pNotify->ptiReceiver = ptiCurrent; if (!pEventHook->fSync) { UserAssert(pEventHook->ihmod == -1); pfn = (WINEVENTPROC)pEventHook->offPfn; } else { pfn = xxxGetEventProc(pEventHook); } if (pfn) { xxxClientCallWinEventProc(pfn, pEventHook, pNotify); DBGVERIFYNOTIFY(pNotify); DBGVERIFYEVENTHOOK(pEventHook); UserAssert(pEventHook->head.cLockObj); } pNotify->ptiReceiver = NULL; /* * Save the next item in the list, ThreadUnlock() may destroy pEventHook. * DestroyNotify() may also kill the event if it is a zombie (destroyed * but being used, waiting for use count to go to 0 before being freed). */ pEventHook = pEventHook->pehNext; ThreadUnlock(&tlpEventHook); /* * We are done with the notification. Kill it. * * NOTE that DestroyNotify does not yield, which is why we can hang on * to the pehNext field above around this call. * * NOTE ALSO that DestroyNotify will kill the event it references if the * ref count goes down to zero and it was zombied earlier. */ DestroyNotify(pNotify); return pEventHook; } /****************************************************************************\ * xxxFlushDeferredWindowEvents * * Process notifications that were queued up during DeferWinEventNotify. \****************************************************************************/ VOID xxxFlushDeferredWindowEvents( VOID) { PNOTIFY pNotify; DWORD idCurrentThread = W32GetCurrentTID(); /* * If idCurrentThread is 0 we're not in danger of faulting, but we'll * needlessly walk the list of pending notifications (since all will be * ignored). */ UserAssert(idCurrentThread != 0); UserAssert(IsWinEventNotifyDeferredOK()); pNotify = gpPendingNotifies; while (pNotify) { if (((pNotify->dwWEFlags & WEF_DEFERNOTIFY) == 0) || (pNotify->idSenderThread != idCurrentThread)) { pNotify = pNotify->pNotifyNext; } else { /* * Clear WEF_DEFERNOTIFY so that if we recurse in the callback * we won't try to send this notification again. */ pNotify->dwWEFlags &= ~WEF_DEFERNOTIFY; #if DBG gnDeferredWinEvents--; #endif /* * We shouldn't have deferred ASYNC notifications: we should have * posted them immediately. */ UserAssert((pNotify->dwWEFlags & WEF_ASYNC) == 0); xxxProcessNotifyWinEvent(pNotify); /* * Start again at the head of the list, in case it munged during * the callback. */ pNotify = gpPendingNotifies; } } } /*****************************************************************************\ * * xxxWindowEvent * * Send, Post or Defer a Win Event notification, depending on what Win Event * hooks are installed and what the context of the caller is. * * The caller should test FWINABLE() and only call xxxWindowEvent if it is TRUE, * that way only costs a few clocks if no Win Event hooks are set. * * Caller shouldn't lock pwnd, because xxxWindowEvent() will do it. * \*****************************************************************************/ VOID xxxWindowEvent( DWORD event, PWND pwnd, LONG idObject, LONG idChild, DWORD dwFlags) { PEVENTHOOK peh; PEVENTHOOK pehNext; PTHREADINFO ptiCurrent, ptiEvent; DWORD dwTime; PPROCESSINFO ppiEvent; DWORD idEventThread; HANDLE hEventProcess; PNOTIFY pNotify; TL tlpwnd; TL tlpti; /* * Do not bother with CheckLock(pwnd) - we ThreadLock it below. */ if (!FEVENTHOOKED(event)) { return; } /* * This thread is in startup, and has not yet had it's pti set up * This is pretty rare, but sometimes encountered in stress. * Test gptiCurrent to avoid the UserAssert(gptiCurrent) in PtiCurrent() */ if (gptiCurrent == NULL) { RIPMSG3(RIP_WARNING, "Ignore WinEvent %lx %#p %lx... no PtiCurrent yet", event, pwnd, idObject); return; } ptiCurrent = PtiCurrent(); /* * Don't bother with destroyed windows */ if (pwnd && TestWF(pwnd, WFDESTROYED)) { RIPMSG3(RIP_WARNING, "Ignore WinEvent %lx %#p %lx... pwnd already destroyed", event, pwnd, idObject); return; } /* * Under some special circumstances we have to defer */ if (ptiCurrent->TIF_flags & (TIF_DISABLEHOOKS | TIF_INCLEANUP)) { dwFlags |= WEF_DEFERNOTIFY; } /* * Determine process and thread issuing the event notification */ if ((dwFlags & WEF_USEPWNDTHREAD) && pwnd) { ptiEvent = GETPTI(pwnd); } else { ptiEvent = ptiCurrent; } idEventThread = TIDq(ptiEvent); ppiEvent = ptiEvent->ppi; hEventProcess = PsGetThreadProcessId(ptiEvent->pEThread); dwTime = NtGetTickCount(); ThreadLockWithPti(ptiCurrent, pwnd, &tlpwnd); ThreadLockPti(ptiCurrent, ptiEvent, &tlpti); /* * If we're not deferring the current notification process any pending * deferred notifications before proceeding with the current notification */ if (!(dwFlags & WEF_DEFERNOTIFY)) { xxxFlushDeferredWindowEvents(); } for (peh = gpWinEventHooks; peh; peh = pehNext) { DBGVERIFYEVENTHOOK(peh); pehNext = peh->pehNext; // // Is event in the right range? And is it for this process/thread? // Note that we skip destroyed events. They will be freed any // second now, it's just that yielding may have caused reentrancy. // // If the caller said to ignore events on his own thread, make sure // we skip them. // if (!peh->fDestroyed && (peh->eventMin <= event) && (event <= peh->eventMax) && (!peh->hEventProcess || (peh->hEventProcess == hEventProcess)) && (!peh->fIgnoreOwnProcess || (ppiEvent != GETPTI(peh)->ppi)) && (!peh->idEventThread || (peh->idEventThread == idEventThread)) && (!peh->fIgnoreOwnThread || (ptiEvent != GETPTI(peh))) && // temp fix from SP3 - best to architect events on a per-desktop // basis, with a separate pWinEventHook list per desktop. (IanJa) (peh->head.pti->rpdesk == ptiCurrent->rpdesk)) { /* * Don't create new notifications for zombie event hooks. * When an event is destroyed, it stays as a zombie until the in-use * count goes to zero (all it's async and deferred notifies gone) */ if (HMIsMarkDestroy(peh)) { break; } UserAssert(peh->fDestroyed == 0); if ((pNotify = CreateNotify(peh, event, pwnd, idObject, idChild, ptiEvent, dwTime)) == NULL) { break; } pNotify->dwWEFlags |= dwFlags; /* * If it's async, don't defer it: post it straight away. */ if (pNotify->dwWEFlags & WEF_ASYNC) { pNotify->dwWEFlags &= ~WEF_DEFERNOTIFY; } if (pNotify->dwWEFlags & WEF_DEFERNOTIFY) { #if DBG gnDeferredWinEvents++; #endif DBGVERIFYNOTIFY(pNotify); } else { pehNext = xxxProcessNotifyWinEvent(pNotify); } } } ThreadUnlockPti(ptiCurrent, &tlpti); ThreadUnlock(&tlpwnd); } /****************************************************************************\ * * CreateNotify() * * Gets a pointer to a NOTIFY struct that we can then propagate to our * event window via Send/PostMessage. We have to do this since we want to * (pass on a lot more data then can be packed in the parameters. * * We have one cached struct so we avoid lots of allocs and frees in the * most common case of just one outstanding notification. \****************************************************************************/ PNOTIFY CreateNotify(PEVENTHOOK pEvent, DWORD event, PWND pwnd, LONG idObject, LONG idChild, PTHREADINFO ptiSender, DWORD dwTime) { PNOTIFY pNotify; UserAssert(pEvent != NULL); // // Get a pointer. From cache if available. // IanJa - change this to allocate from zone a la AllocQEntry?? // if (!fNotifyCacheInUse) { fNotifyCacheInUse = TRUE; pNotify = ¬ifyCache; #if DBG // // Make sure we aren't forgetting to set any fields. // // DebugFillBuffer(pNotify, sizeof(NOTIFY)); #endif } else { pNotify = (PNOTIFY)UserAllocPool(sizeof(NOTIFY), TAG_NOTIFY); if (!pNotify) return NULL; } /* * Fill in the notify block. */ pNotify->spEventHook = NULL; Lock(&pNotify->spEventHook, pEvent); pNotify->hwnd = HW(pwnd); pNotify->event = event; pNotify->idObject = idObject; pNotify->idChild = idChild; pNotify->idSenderThread = TIDq(ptiSender); UserAssert(pNotify->idSenderThread != 0); pNotify->dwEventTime = dwTime; pNotify->dwWEFlags = pEvent->fSync ? 0 : WEF_ASYNC; pNotify->pNotifyNext = NULL; pNotify->ptiReceiver = NULL; #if DBG gnNotifies++; #endif /* * The order of non-deferred notifications doesn't matter; they are here * simply for cleanup/in-use tracking. However, deferred notifications must * be ordered with most recent at the end, so just order them all that way. */ if (gpPendingNotifies) { UserAssert(gpLastPendingNotify); UserAssert(gpLastPendingNotify->pNotifyNext == NULL); gpLastPendingNotify->pNotifyNext = pNotify; } else { gpPendingNotifies = pNotify; } gpLastPendingNotify = pNotify; return pNotify; } /****************************************************************************\ * RemoveNotify \****************************************************************************/ VOID RemoveNotify( PNOTIFY *ppNotify) { PNOTIFY pNotifyRemove; pNotifyRemove = *ppNotify; /* * First, get it out of the pending list. */ *ppNotify = pNotifyRemove->pNotifyNext; #if DBG if (pNotifyRemove->dwWEFlags & WEF_DEFERNOTIFY) { UserAssert(gnDeferredWinEvents > 0); gnDeferredWinEvents--; } #endif if (*ppNotify == NULL) { /* * Removing last notify, so fix up gpLastPendingNotify: * If list now empty, there is no last item. */ if (gpPendingNotifies == NULL) { gpLastPendingNotify = NULL; } else { gpLastPendingNotify = CONTAINING_RECORD(ppNotify, NOTIFY, pNotifyNext); } } UserAssert(gpPendingNotifies == 0 || gpPendingNotifies > (PNOTIFY)100); DBGVERIFYEVENTHOOK(pNotifyRemove->spEventHook); /* * This may cause the win event hook to be freed. */ Unlock(&pNotifyRemove->spEventHook); /* * Now free it. Either put it back in the cache (if it is the cache) * or really free it. */ if (pNotifyRemove == ¬ifyCache) { UserAssert(fNotifyCacheInUse); fNotifyCacheInUse = FALSE; } else { UserFreePool(pNotifyRemove); } #if DBG UserAssert(gnNotifies > 0); gnNotifies--; #endif } /*****************************************************************************\ * DestroyNotify * * This gets the notification out of our pending list and frees the local * memory it uses. * * This function is called * (1) NORMALLY: After returning from calling the notify proc * (2) CLEANUP: When a thread goes away, we cleanup async notifies it * hasn't received, and sync notifies it was in the middle of trying * to call (i.e. the event proc faulted). \*****************************************************************************/ VOID DestroyNotify( PNOTIFY pNotifyDestroy) { PNOTIFY *ppNotify; PNOTIFY pNotifyT; DBGVERIFYNOTIFY(pNotifyDestroy); /* * Either this notify isn't currently in the process of calling back * (which means ptiReceiver is NULL) or the thread destroying it * must be the one that was calling back (which means this thread * was destroyed during the callback and is cleaning up). */ UserAssert(pNotifyDestroy->ptiReceiver == NULL || pNotifyDestroy->ptiReceiver == PtiCurrent()); ppNotify = &gpPendingNotifies; while (pNotifyT = *ppNotify) { if (pNotifyT == pNotifyDestroy) { RemoveNotify(ppNotify); return; } else { ppNotify = &pNotifyT->pNotifyNext; } } RIPMSG1(RIP_ERROR, "DestroyNotify 0x%p - not found", pNotifyDestroy); } /***************************************************************************\ * FreeThreadsWinEvents * * During 'exit-list' processing this function is called to free any WinEvent * notifications and WinEvent hooks created by the current thread. * * Notifications that remain may be: * o Posted notifications (async) * o Notifications in xxxClientCallWinEventProc (sync) * o Deferred notifications (should be sync only) * Destroy the sync notifications, because we cannot do callbacks * while in thread cleanup. * Leave the posted (async) notifications alone: they're on their way already. * * History: * 11-11-96 IanJa Created. \***************************************************************************/ VOID FreeThreadsWinEvents( PTHREADINFO pti) { PEVENTHOOK peh, pehNext; PNOTIFY pn, pnNext; DWORD idCurrentThread = W32GetCurrentTID(); /* * Loop through all the notifications. */ for (pn = gpPendingNotifies; pn; pn = pnNext) { pnNext = pn->pNotifyNext; /* * Only destroy sync notifications that belong to this thread * and are not currently calling back i.e. ptiReceiver must be NULL. * Otherwise, when we come back from the callback in * xxxProcessNotifyWinEvent we will operate on a freed notify. * Also destroy the notification if the receiver is going away * or else it gets leaked as long as the sender is alive. */ if ((pn->idSenderThread == idCurrentThread && pn->ptiReceiver == NULL) || pn->ptiReceiver == pti) { if ((pn->dwWEFlags & WEF_ASYNC) == 0) { UserAssert((pn->dwWEFlags & WEF_POSTED) == 0); DestroyNotify(pn); } } } peh = gpWinEventHooks; while (peh) { pehNext = peh->pehNext; if (GETPTI(peh) == pti) { DestroyEventHook(peh); } peh = pehNext; } } /***************************************************************************\ * _SetWinEventHook() * * This installs a win event hook. * * If hEventProcess set but idEventThread = 0, hook all threads in process. * If idEventThread set but hEventProcess = NULL, hook single thread only. * If neither are set, hook everything. * If both are set ?? * \***************************************************************************/ PEVENTHOOK _SetWinEventHook( DWORD eventMin, DWORD eventMax, HMODULE hmodWinEventProc, PUNICODE_STRING pstrLib, WINEVENTPROC pfnWinEventProc, HANDLE hEventProcess, DWORD idEventThread, DWORD dwFlags) { PEVENTHOOK pEventNew; PTHREADINFO ptiCurrent; int ihmod; ptiCurrent = PtiCurrent(); // // If exiting, fail the call. // if (ptiCurrent->TIF_flags & TIF_INCLEANUP) { RIPMSG1(RIP_ERROR, "SetWinEventHook: Fail call - thread 0x%p in cleanup", ptiCurrent); return NULL; } /* * Check to see if filter proc is valid. */ if (pfnWinEventProc == NULL) { RIPERR0(ERROR_INVALID_FILTER_PROC, RIP_WARNING, "pfnWinEventProc == NULL"); return NULL; } if (eventMin > eventMax) { RIPERR0(ERROR_INVALID_HOOK_FILTER, RIP_WARNING, "eventMin > eventMax"); return NULL; } if (dwFlags & WINEVENT_INCONTEXT) { /* * WinEventProc to be called in context of hooked thread, so needs a DLL. */ if (hmodWinEventProc == NULL) { RIPERR0(ERROR_HOOK_NEEDS_HMOD, RIP_WARNING, "In context hook w/o DLL!"); return NULL; } else if (pstrLib == NULL) { /* * If we got an hmod, we should get a DLL name too! */ RIPERR1(ERROR_DLL_NOT_FOUND, RIP_WARNING, "hmod 0x%p, but no lib name", hmodWinEventProc); return NULL; } ihmod = GetHmodTableIndex(pstrLib); if (ihmod == -1) { RIPERR0(ERROR_MOD_NOT_FOUND, RIP_WARNING, ""); return NULL; } } else { ihmod = -1; // means no DLL is required hmodWinEventProc = 0; } /* * Check the thread id, check it is a GUI thread. */ if (idEventThread != 0) { PTHREADINFO ptiT; ptiT = PtiFromThreadId(idEventThread); if (ptiT == NULL || !(ptiT->TIF_flags & TIF_GUITHREADINITIALIZED)) { RIPERR1(ERROR_INVALID_THREAD_ID, RIP_VERBOSE, "pti %#p", ptiT); return NULL; } } /* * Create the window for async events first. * * NOTE that USER itself will not pass on window creation/destruction * notifications for * * IME windows * * OLE windows * * RPC windows * * Event windows */ /* * Get a new event. */ pEventNew = (PEVENTHOOK)HMAllocObject(ptiCurrent, NULL, TYPE_WINEVENTHOOK, sizeof(EVENTHOOK)); if (!pEventNew) { return NULL; } /* * Fill in the new event. */ pEventNew->eventMin = (UINT)eventMin; pEventNew->eventMax = (UINT)eventMax; pEventNew->fIgnoreOwnThread = ((dwFlags & WINEVENT_SKIPOWNTHREAD) != 0); pEventNew->fIgnoreOwnProcess = ((dwFlags & WINEVENT_SKIPOWNPROCESS) != 0); pEventNew->fDestroyed = FALSE; pEventNew->fSync = ((dwFlags & WINEVENT_INCONTEXT) != 0); pEventNew->hEventProcess = hEventProcess; pEventNew->idEventThread = idEventThread; pEventNew->ihmod = ihmod; /* * Add a dependency on this module - meaning, increment a count * that simply counts the number of hooks set into this module. */ if (pEventNew->ihmod >= 0) { AddHmodDependency(pEventNew->ihmod); } /* * If pfnWinEventProc is in caller's process and no DLL is involved, * then pEventNew->offPfn is the actual address. */ pEventNew->offPfn = ((ULONG_PTR)pfnWinEventProc) - ((ULONG_PTR)hmodWinEventProc); /* * Link our event into the master list. * * Note that we count on USER to not generate any events when installing * our hook. The caller can't handle it yet since he hasn't got back * his event handle from this call. */ pEventNew->pehNext = gpWinEventHooks; gpWinEventHooks = pEventNew; /* * Update the flags that indicate what event hooks are installed. These * flags are accessable from user mode without a kernel transition since * they are in shared memory. */ SET_FLAG(gpsi->dwInstalledEventHooks, CategoryMaskFromEventRange(eventMin, eventMax)); return pEventNew; } /****************************************************************************\ * UnhookWinEvent * * Unhooks a win event hook. We of course sanity check that this thread is * the one which installed the hook. We have to: We are going to destroy * the IPC window and that must be in context. \****************************************************************************/ BOOL _UnhookWinEvent( PEVENTHOOK pEventUnhook) { DBGVERIFYEVENTHOOK(pEventUnhook); if (HMIsMarkDestroy(pEventUnhook) || (GETPTI(pEventUnhook) != PtiCurrent())) { /* * We do this to avoid someone calling UnhookWinEvent() the first * time, then somehow getting control again and calling it a second * time before we've managed to free up the event since someone was * in the middle of using it at the first UWE call. */ RIPERR1(ERROR_INVALID_HANDLE, RIP_WARNING, "_UnhookWinEvent: Invalid event hook 0x%p", PtoHq(pEventUnhook)); return FALSE; } /* * Purge this baby if all notifications are done. * * If there are SYNC ones pending, the caller will clean this up upon * the return from calling the event. * * If there are ASYNC ones pending, the receiver will not call the event * and clean it up when he gets it. */ DestroyEventHook(pEventUnhook); return TRUE; } /*****************************************************************************\ * DestroyEventHook * * Destroys an event when the ref count has gone down to zero. It may * happen: * * In the event generator's context, after returning from a callback * and the ref count dropped to zero, if sync. * * In the event installer's context, after returning from a callback * and the ref count dropped to zero if async. * * In the event installer's context, if on _UnhookWinEvent() the event * was not in use at all. \*****************************************************************************/ VOID DestroyEventHook( PEVENTHOOK pEventDestroy) { PEVENTHOOK *ppEvent; PEVENTHOOK pEventT; DWORD dwCategoryMask = 0; DBGVERIFYEVENTHOOK(pEventDestroy); UserAssert(gpWinEventHooks); /* * Mark this event as destroyed, but don't remove it from the event list * until its lock count goes to 0 - we may be traversing the list within * xxxWindowEvent, so we mustn't break the link to the next hook. */ pEventDestroy->fDestroyed = TRUE; /* * If the object is locked, mark it for destroy but don't free it yet. */ if (!HMMarkObjectDestroy(pEventDestroy)) { return; } /* * Remove this from our event list. */ for (ppEvent = &gpWinEventHooks; pEventT = *ppEvent; ppEvent = &pEventT->pehNext) { if (pEventT == pEventDestroy) { *ppEvent = pEventDestroy->pehNext; break; } } UserAssert(pEventT); /* * Update the flags that indicate what event hooks are installed. These * flags are accessable from user mode without a kernel transition since * they are in shared memory. Note that a user could check the shared * memory at any time, so they may get a false-positive during this * processing. A false-positive would mean that we claim there is a * listener, when there really isn't. We never want the user to get * a false negative - meaning that we claim there aren't any listeners * but there really is. That could mean bad things for accessability. */ for (pEventT = gpWinEventHooks; pEventT != NULL; pEventT = pEventT->pehNext) { SET_FLAG(dwCategoryMask, CategoryMaskFromEventRange(pEventT->eventMin, pEventT->eventMax)); } gpsi->dwInstalledEventHooks = dwCategoryMask; /* * Make sure each hooked thread will unload the hook proc DLL. */ if (pEventDestroy->ihmod >= 0) { RemoveHmodDependency(pEventDestroy->ihmod); } /* * Free this pointer. */ HMFreeObject(pEventDestroy); } /***************************************************************************\ * xxxGetEventProc * * For sync events, this gets the address to call. If 16-bits, then just * return the installed address. If 32-bits, we need to load the library * if not in the same process as the installer. \***************************************************************************/ WINEVENTPROC xxxGetEventProc( PEVENTHOOK pEventOrg) { PTHREADINFO ptiCurrent; UserAssert(pEventOrg); UserAssert(pEventOrg->fSync); UserAssert(pEventOrg->ihmod >= 0); UserAssert(pEventOrg->offPfn != 0); CheckLock(pEventOrg); /* * Make sure the hook is still around before we try and call it. */ if (HMIsMarkDestroy(pEventOrg)) { return NULL; } ptiCurrent = PtiCurrent(); /* * Make sure the DLL for this hook, if any, has been loaded for the * current process. */ if (pEventOrg->ihmod != -1 && TESTHMODLOADED(ptiCurrent, pEventOrg->ihmod) == 0) { /* * Try loading the library, since it isn't loaded in this processes * context. The hook is alrerady locked, so it won't go away while * we're loading this library. */ if (xxxLoadHmodIndex(pEventOrg->ihmod) == NULL) { return NULL; } } /* * While we're still inside the critical section make sure the hook * hasn't been 'freed'. If so just return NULL. Since WinEvent has * already been called, you might think that we should pass the event * on, but the hooker may not be expecting this after having * cancelled the hook! In any case, we have two ways of detecting that * this hook has been removed (see the following code). */ /* * Make sure the hook is still around before we try and call it. */ if (HMIsMarkDestroy(pEventOrg)) { return NULL; } return (WINEVENTPROC)PFNHOOK(pEventOrg); }