// // idletimr.c // // This file contains an internal handling process for monitoring // idled "wait for text" threads, and dispatching callback messages // to the front-end application regarding the current state. This is // very helpful when debugging scripts, by seeing exactly what // the script is waiting on. // // Copyright (C) 2001 Microsoft Corporation // // Author: a-devjen (Devin Jenson) // #include #include #include "idletimr.h" #include "connlist.h" #include "apihandl.h" // These are the internal messages used for the thread message queue // to indicate what action to take. #define WM_SETTIMER WM_USER + 1 #define WM_KILLTIMER WM_USER + 2 // Internal helper function prototypes DWORD WINAPI T2WaitTimerThread(LPVOID lpParameter); void CALLBACK T2WaitTimerProc(HWND Window, UINT Message, UINT_PTR TimerId, DWORD TimePassed); // These are the callback routines PFNPRINTMESSAGE pfnPrintMessage = NULL; PFNIDLEMESSAGE pfnIdleCallback = NULL; // Timer and thread data, only one queue thread is needed // for all handles in the current process. HANDLE ThreadHandle = NULL; DWORD ThreadId = 0; BOOL ThreadIsOn = FALSE; BOOL ThreadIsStopping = FALSE; // CreateTimerThread // // Initializes the thread message queue required for monitoring timers. // // Returns true if the thread was successfully created, FALSE otherwise. BOOL T2CreateTimerThread(PFNPRINTMESSAGE PrintMessage, PFNIDLEMESSAGE IdleCallback) { // Return TRUE if the thread is already created... if (ThreadIsOn == TRUE) return TRUE; // Indicate the thread is on (for multithreaded apps) ThreadIsOn = TRUE; // Record the callback functions pfnPrintMessage = PrintMessage; pfnIdleCallback = IdleCallback; // Initialize thread creation ThreadHandle = CreateThread(NULL, 0, T2WaitTimerThread, NULL, 0, &ThreadId); // Check if we did OK if (ThreadHandle == NULL) { // We failed, reset all the global variables ThreadHandle = NULL; ThreadId = 0; pfnPrintMessage = NULL; pfnIdleCallback = NULL; // Turn the thread off and return failure ThreadIsOn = FALSE; return FALSE; } return TRUE; } // DestroyTimerThread // // Destroys the thread created by CreateTimerThread. // // Returns TRUE on success, FALSE on failure. BOOL T2DestroyTimerThread(void) { // Record the current thread handle locally because // the global value could possibly be changed. HANDLE LocalThreadHandle = ThreadHandle; // If the thread is already stopped, return success if (ThreadIsOn == FALSE) return TRUE; // If the thread is already trying to stop, return failure if (ThreadIsStopping == TRUE) return FALSE; // Indicate the thread is now trying to stop ThreadIsStopping = TRUE; // First send the thread a quit message PostThreadMessage(ThreadId, WM_QUIT, 0, 0); // Wait for the thread (5 seconds) if (WaitForSingleObject(LocalThreadHandle, 5000) == WAIT_TIMEOUT) { // Why is our idle timer waiting?? _ASSERT(FALSE); // No more waiting, we now use brute force TerminateThread(LocalThreadHandle, -1); } // Close the thread handle CloseHandle(LocalThreadHandle); // Clear out all the global variables ThreadHandle = NULL; ThreadId = 0; pfnPrintMessage = NULL; pfnIdleCallback = NULL; // And of course, release the thread switches ThreadIsOn = FALSE; ThreadIsStopping = FALSE; return TRUE; } // StartTimer // // Call this before going into a wait state for the thread on // "wait for text". This will record the current time, and start // a timer which will execute in exactly WAIT_TIME milliseconds. // When this occurs, the callback routines are notified. // // No return value. void T2StartTimer(HANDLE Connection, LPCWSTR Label) { TSAPIHANDLE *Handle = (TSAPIHANDLE *)Connection; // Make sure the message queue is on first if (ThreadIsOn == FALSE || ThreadIsStopping == TRUE) return; // Record the label for this timer if (Label == NULL) *(Handle->WaitStr) = '\0'; else wcstombs(Handle->WaitStr, Label, MAX_PATH); // Post a message to the thread regarding a new timer for the handle. PostThreadMessage(ThreadId, WM_SETTIMER, WAIT_TIME, (LPARAM)Connection); } // StopTimer // // Call this after text is received in a "wait for text" thread state. // It will stop the timer created by StartTimer preventing further // messages with the recorded label. // // No return value. void T2StopTimer(HANDLE Connection) { // Make sure the message queue is on first if (ThreadIsOn == FALSE || ThreadIsStopping == TRUE) return; // Post a message to the thread to tell it to stop the timer PostThreadMessage(ThreadId, WM_KILLTIMER, 0, (LPARAM)Connection); } // WaitTimerProc // // When a timer exceeds it maximum time, this function is called. // This will stop the timer, and start it back up for an additionaly // interval of WAIT_TIME_STEP. This is after it sends the notifications // back to the user callback functions. // // This function is a valid format for use as a callback function in // use with the SetTimer Win32 API function. // // No return value. /* This recieves notifications when a timer actually elapses */ void CALLBACK T2WaitTimerProc(HWND Window, UINT Message, UINT_PTR TimerId, DWORD TickCount) { DWORD IdleSecs = 0; DWORD TimeStarted = 0; // First get the the handle for the specified timer id HANDLE Connection = T2ConnList_FindHandleByTimerId(TimerId); // Stop the current running timer KillTimer(NULL, TimerId); // Do a sanity check to make sure we have a handle for this timer if (Connection == NULL) { _ASSERT(FALSE); return; } // Clear the time id parameter in the linked list T2ConnList_SetTimerId(Connection, 0); // Get the time this timer began T2ConnList_GetData(Connection, NULL, &TimeStarted); // Calculate number of seconds this timer has been running // (from its millisecond value) IdleSecs = (TickCount - TimeStarted) / 1000; // Call the PrintMessage callback function first if (pfnPrintMessage != NULL) pfnPrintMessage(IDLE_MESSAGE, "(Idle %u Secs) %s [%X]\n", IdleSecs, ((TSAPIHANDLE *)Connection)->WaitStr, Connection); // Secondly call the IdleCallback callback function if (pfnIdleCallback != NULL) pfnIdleCallback(Connection, ((TSAPIHANDLE *)Connection)->WaitStr, IdleSecs); // Reestablish a new timer with WAIT_TIME STEP to do this process again TimerId = SetTimer(NULL, 0, WAIT_TIME_STEP, T2WaitTimerProc); // Record the new timer id T2ConnList_SetTimerId(Connection, TimerId); } // WaitTimerThread // // This is a valid thread message queue. It is created using the // CreateTimerThread function, and killed using the DestroyTimerThread // function. It is more-or-less a worker thread to create SetTimer // callback functions which cannot be used in the main thread because // if the thread goes into wait state, SetTimer callbacks will not be // called. When you need to add/remove a thread, use the following // thread posting form: // // UINT Message = WM_SETTIMER or WM_KILLTIMER // WPARAM wParam = Initial wait time (usually WAIT_TIME) // LPARAM lParam = (HANDLE)Connection // // The return value is always 0. DWORD WINAPI T2WaitTimerThread(LPVOID lpParameter) { UINT_PTR TimerId; MSG Message; UINT WaitTime; // This is the message queue function for the thread while(GetMessage(&Message, NULL, 0, 0) > 0) { TimerId = 0; // SetTimer uses a UINT timeout value, while a WPARAM is a // pointer-sized value. WaitTime = Message.wParam > UINT_MAX ? UINT_MAX : (UINT)Message.wParam; // Enumerate the retreived message switch(Message.message) { // Create a new timer for a specified handle case WM_SETTIMER: // Create the timer and record its new timer id TimerId = SetTimer(NULL, 0, WaitTime, T2WaitTimerProc); T2ConnList_SetData((HANDLE)(Message.lParam), TimerId, GetTickCount()); break; // Stop a running timer for the specified handle case WM_KILLTIMER: // Get the timer id for the handle T2ConnList_GetData((HANDLE)(Message.lParam), &TimerId, NULL); // Validate and clear the timer if valid if (TimerId != 0 && TimerId != -1) KillTimer(NULL, TimerId); // Clear the linked last data for the handle T2ConnList_SetData((HANDLE)Message.lParam, 0, 0); break; // Indicates a timer has elapsed its time, call the // procedure that handles these messages. case WM_TIMER: T2WaitTimerProc(NULL, WM_TIMER, WaitTime, GetTickCount()); break; } } // Clear out the thread values ThreadHandle = NULL; ThreadId = 0; return 0; }