os/persistentdata/persistentstorage/sqlite3api/TEST/TCL/tcldistribution/win/tclWinNotify.c
author sl@SLION-WIN7.fritz.box
Fri, 15 Jun 2012 03:10:57 +0200
changeset 0 bde4ae8d615e
permissions -rw-r--r--
First public contribution.
     1 /* 
     2  * tclWinNotify.c --
     3  *
     4  *	This file contains Windows-specific procedures for the notifier,
     5  *	which is the lowest-level part of the Tcl event loop.  This file
     6  *	works together with ../generic/tclNotify.c.
     7  *
     8  * Copyright (c) 1995-1997 Sun Microsystems, Inc.
     9  *
    10  * See the file "license.terms" for information on usage and redistribution
    11  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
    12  *
    13  * RCS: @(#) $Id: tclWinNotify.c,v 1.11.2.1 2003/03/21 03:24:09 dgp Exp $
    14  */
    15 
    16 #include "tclWinInt.h"
    17 
    18 /*
    19  * The follwing static indicates whether this module has been initialized.
    20  */
    21 
    22 #define INTERVAL_TIMER 1	/* Handle of interval timer. */
    23 
    24 #define WM_WAKEUP WM_USER	/* Message that is send by
    25 				 * Tcl_AlertNotifier. */
    26 /*
    27  * The following static structure contains the state information for the
    28  * Windows implementation of the Tcl notifier.  One of these structures
    29  * is created for each thread that is using the notifier.  
    30  */
    31 
    32 typedef struct ThreadSpecificData {
    33     CRITICAL_SECTION crit;	/* Monitor for this notifier. */
    34     DWORD thread;		/* Identifier for thread associated with this
    35 				 * notifier. */
    36     HANDLE event;		/* Event object used to wake up the notifier
    37 				 * thread. */
    38     int pending;		/* Alert message pending, this field is
    39 				 * locked by the notifierMutex. */
    40     HWND hwnd;			/* Messaging window. */
    41     int timeout;		/* Current timeout value. */
    42     int timerActive;		/* 1 if interval timer is running. */
    43 } ThreadSpecificData;
    44 
    45 static Tcl_ThreadDataKey dataKey;
    46 
    47 extern TclStubs tclStubs;
    48 extern Tcl_NotifierProcs tclOriginalNotifier;
    49 
    50 /*
    51  * The following static indicates the number of threads that have
    52  * initialized notifiers.  It controls the lifetime of the TclNotifier
    53  * window class.
    54  *
    55  * You must hold the notifierMutex lock before accessing this variable.
    56  */
    57 
    58 static int notifierCount = 0;
    59 TCL_DECLARE_MUTEX(notifierMutex)
    60 
    61 /*
    62  * Static routines defined in this file.
    63  */
    64 
    65 static LRESULT CALLBACK	NotifierProc(HWND hwnd, UINT message,
    66 			    WPARAM wParam, LPARAM lParam);
    67 
    68 
    69 /*
    70  *----------------------------------------------------------------------
    71  *
    72  * Tcl_InitNotifier --
    73  *
    74  *	Initializes the platform specific notifier state.
    75  *
    76  * Results:
    77  *	Returns a handle to the notifier state for this thread..
    78  *
    79  * Side effects:
    80  *	None.
    81  *
    82  *----------------------------------------------------------------------
    83  */
    84 
    85 ClientData
    86 Tcl_InitNotifier()
    87 {
    88     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
    89     WNDCLASS class;
    90 
    91     /*
    92      * Register Notifier window class if this is the first thread to
    93      * use this module.
    94      */
    95 
    96     Tcl_MutexLock(&notifierMutex);
    97     if (notifierCount == 0) {
    98 	class.style = 0;
    99 	class.cbClsExtra = 0;
   100 	class.cbWndExtra = 0;
   101 	class.hInstance = TclWinGetTclInstance();
   102 	class.hbrBackground = NULL;
   103 	class.lpszMenuName = NULL;
   104 	class.lpszClassName = "TclNotifier";
   105 	class.lpfnWndProc = NotifierProc;
   106 	class.hIcon = NULL;
   107 	class.hCursor = NULL;
   108 
   109 	if (!RegisterClassA(&class)) {
   110 	    panic("Unable to register TclNotifier window class");
   111 	}
   112     }
   113     notifierCount++;
   114     Tcl_MutexUnlock(&notifierMutex);
   115 
   116     tsdPtr->pending = 0;
   117     tsdPtr->timerActive = 0;
   118 
   119     InitializeCriticalSection(&tsdPtr->crit);
   120 
   121     tsdPtr->hwnd = NULL;
   122     tsdPtr->thread = GetCurrentThreadId();
   123     tsdPtr->event = CreateEvent(NULL, TRUE /* manual */,
   124 	    FALSE /* !signaled */, NULL);
   125 
   126     return (ClientData) tsdPtr;
   127 }
   128 
   129 /*
   130  *----------------------------------------------------------------------
   131  *
   132  * Tcl_FinalizeNotifier --
   133  *
   134  *	This function is called to cleanup the notifier state before
   135  *	a thread is terminated.
   136  *
   137  * Results:
   138  *	None.
   139  *
   140  * Side effects:
   141  *	May dispose of the notifier window and class.
   142  *
   143  *----------------------------------------------------------------------
   144  */
   145 
   146 void
   147 Tcl_FinalizeNotifier(clientData)
   148     ClientData clientData;	/* Pointer to notifier data. */
   149 {
   150     ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData;
   151 
   152     /*
   153      * Only finalize the notifier if a notifier was installed in the
   154      * current thread; there is a route in which this is not
   155      * guaranteed to be true (when tclWin32Dll.c:DllMain() is called
   156      * with the flag DLL_PROCESS_DETACH by the OS, which could be
   157      * doing so from a thread that's never previously been involved
   158      * with Tcl, e.g. the task manager) so this check is important.
   159      *
   160      * Fixes Bug #217982 reported by Hugh Vu and Gene Leache.
   161      */
   162     if (tsdPtr == NULL) {
   163 	return;
   164     }
   165 
   166     DeleteCriticalSection(&tsdPtr->crit);
   167     CloseHandle(tsdPtr->event);
   168 
   169     /*
   170      * Clean up the timer and messaging window for this thread.
   171      */
   172 
   173     if (tsdPtr->hwnd) {
   174 	KillTimer(tsdPtr->hwnd, INTERVAL_TIMER);
   175 	DestroyWindow(tsdPtr->hwnd);
   176     }
   177 
   178     /*
   179      * If this is the last thread to use the notifier, unregister
   180      * the notifier window class.
   181      */
   182 
   183     Tcl_MutexLock(&notifierMutex);
   184     notifierCount--;
   185     if (notifierCount == 0) {
   186 	UnregisterClassA("TclNotifier", TclWinGetTclInstance());
   187     }
   188     Tcl_MutexUnlock(&notifierMutex);
   189 }
   190 
   191 /*
   192  *----------------------------------------------------------------------
   193  *
   194  * Tcl_AlertNotifier --
   195  *
   196  *	Wake up the specified notifier from any thread. This routine
   197  *	is called by the platform independent notifier code whenever
   198  *	the Tcl_ThreadAlert routine is called.  This routine is
   199  *	guaranteed not to be called on a given notifier after
   200  *	Tcl_FinalizeNotifier is called for that notifier.  This routine
   201  *	is typically called from a thread other than the notifier's
   202  *	thread.
   203  *
   204  * Results:
   205  *	None.
   206  *
   207  * Side effects:
   208  *	Sends a message to the messaging window for the notifier
   209  *	if there isn't already one pending.
   210  *
   211  *----------------------------------------------------------------------
   212  */
   213 
   214 void
   215 Tcl_AlertNotifier(clientData)
   216     ClientData clientData;	/* Pointer to thread data. */
   217 {
   218     ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData;
   219 
   220     /*
   221      * Note that we do not need to lock around access to the hwnd
   222      * because the race condition has no effect since any race condition
   223      * implies that the notifier thread is already awake.
   224      */
   225 
   226     if (tsdPtr->hwnd) {
   227 	/*
   228 	 * We do need to lock around access to the pending flag.
   229 	 */
   230 
   231 	EnterCriticalSection(&tsdPtr->crit);
   232 	if (!tsdPtr->pending) {
   233 	    PostMessage(tsdPtr->hwnd, WM_WAKEUP, 0, 0);
   234 	}
   235 	tsdPtr->pending = 1;
   236 	LeaveCriticalSection(&tsdPtr->crit);
   237     } else {
   238 	SetEvent(tsdPtr->event);
   239     }
   240 }
   241 
   242 /*
   243  *----------------------------------------------------------------------
   244  *
   245  * Tcl_SetTimer --
   246  *
   247  *	This procedure sets the current notifier timer value.  The
   248  *	notifier will ensure that Tcl_ServiceAll() is called after
   249  *	the specified interval, even if no events have occurred.
   250  *
   251  * Results:
   252  *	None.
   253  *
   254  * Side effects:
   255  *	Replaces any previous timer.
   256  *
   257  *----------------------------------------------------------------------
   258  */
   259 
   260 void
   261 Tcl_SetTimer(
   262     Tcl_Time *timePtr)		/* Maximum block time, or NULL. */
   263 {
   264     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
   265     UINT timeout;
   266 
   267     /*
   268      * Allow the notifier to be hooked.  This may not make sense
   269      * on Windows, but mirrors the UNIX hook.
   270      */
   271 
   272     if (tclStubs.tcl_SetTimer != tclOriginalNotifier.setTimerProc) {
   273 	tclStubs.tcl_SetTimer(timePtr);
   274 	return;
   275     }
   276 
   277     /*
   278      * We only need to set up an interval timer if we're being called
   279      * from an external event loop.  If we don't have a window handle
   280      * then we just return immediately and let Tcl_WaitForEvent handle
   281      * timeouts.
   282      */
   283 
   284     if (!tsdPtr->hwnd) {
   285 	return;
   286     }
   287 
   288     if (!timePtr) {
   289 	timeout = 0;
   290     } else {
   291 	/*
   292 	 * Make sure we pass a non-zero value into the timeout argument.
   293 	 * Windows seems to get confused by zero length timers.
   294 	 */
   295 
   296 	timeout = timePtr->sec * 1000 + timePtr->usec / 1000;
   297 	if (timeout == 0) {
   298 	    timeout = 1;
   299 	}
   300     }
   301     tsdPtr->timeout = timeout;
   302     if (timeout != 0) {
   303 	tsdPtr->timerActive = 1;
   304 	SetTimer(tsdPtr->hwnd, INTERVAL_TIMER,
   305 		    (unsigned long) tsdPtr->timeout, NULL);
   306     } else {
   307 	tsdPtr->timerActive = 0;
   308 	KillTimer(tsdPtr->hwnd, INTERVAL_TIMER);
   309     }
   310 }
   311 
   312 /*
   313  *----------------------------------------------------------------------
   314  *
   315  * Tcl_ServiceModeHook --
   316  *
   317  *	This function is invoked whenever the service mode changes.
   318  *
   319  * Results:
   320  *	None.
   321  *
   322  * Side effects:
   323  *	If this is the first time the notifier is set into
   324  *	TCL_SERVICE_ALL, then the communication window is created.
   325  *
   326  *----------------------------------------------------------------------
   327  */
   328 
   329 void
   330 Tcl_ServiceModeHook(mode)
   331     int mode;			/* Either TCL_SERVICE_ALL, or
   332 				 * TCL_SERVICE_NONE. */
   333 {
   334     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
   335 
   336     /*
   337      * If this is the first time that the notifier has been used from a
   338      * modal loop, then create a communication window.  Note that after
   339      * this point, the application needs to service events in a timely
   340      * fashion or Windows will hang waiting for the window to respond
   341      * to synchronous system messages.  At some point, we may want to
   342      * consider destroying the window if we leave the modal loop, but
   343      * for now we'll leave it around.
   344      */
   345 
   346     if (mode == TCL_SERVICE_ALL && !tsdPtr->hwnd) {
   347 	tsdPtr->hwnd = CreateWindowA("TclNotifier", "TclNotifier", WS_TILED,
   348 		0, 0, 0, 0, NULL, NULL, TclWinGetTclInstance(), NULL);
   349 	/*
   350 	 * Send an initial message to the window to ensure that we wake up the
   351 	 * notifier once we get into the modal loop.  This will force the
   352 	 * notifier to recompute the timeout value and schedule a timer
   353 	 * if one is needed.
   354 	 */
   355 
   356 	Tcl_AlertNotifier((ClientData)tsdPtr);
   357     }
   358 }
   359 
   360 /*
   361  *----------------------------------------------------------------------
   362  *
   363  * NotifierProc --
   364  *
   365  *	This procedure is invoked by Windows to process events on
   366  *	the notifier window.  Messages will be sent to this window
   367  *	in response to external timer events or calls to
   368  *	TclpAlertTsdPtr->
   369  *
   370  * Results:
   371  *	A standard windows result.
   372  *
   373  * Side effects:
   374  *	Services any pending events.
   375  *
   376  *----------------------------------------------------------------------
   377  */
   378 
   379 static LRESULT CALLBACK
   380 NotifierProc(
   381     HWND hwnd,
   382     UINT message,
   383     WPARAM wParam,
   384     LPARAM lParam)
   385 {
   386     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
   387 
   388     if (message == WM_WAKEUP) {
   389 	EnterCriticalSection(&tsdPtr->crit);
   390 	tsdPtr->pending = 0;
   391 	LeaveCriticalSection(&tsdPtr->crit);
   392     } else if (message != WM_TIMER) {
   393 	return DefWindowProc(hwnd, message, wParam, lParam);
   394     }
   395 	
   396     /*
   397      * Process all of the runnable events.
   398      */
   399 
   400     Tcl_ServiceAll();
   401     return 0;
   402 }
   403 
   404 /*
   405  *----------------------------------------------------------------------
   406  *
   407  * Tcl_WaitForEvent --
   408  *
   409  *	This function is called by Tcl_DoOneEvent to wait for new
   410  *	events on the message queue.  If the block time is 0, then
   411  *	Tcl_WaitForEvent just polls the event queue without blocking.
   412  *
   413  * Results:
   414  *	Returns -1 if a WM_QUIT message is detected, returns 1 if
   415  *	a message was dispatched, otherwise returns 0.
   416  *
   417  * Side effects:
   418  *	Dispatches a message to a window procedure, which could do
   419  *	anything.
   420  *
   421  *----------------------------------------------------------------------
   422  */
   423 
   424 int
   425 Tcl_WaitForEvent(
   426     Tcl_Time *timePtr)		/* Maximum block time, or NULL. */
   427 {
   428     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
   429     MSG msg;
   430     DWORD timeout, result;
   431     int status;
   432 
   433     /*
   434      * Allow the notifier to be hooked.  This may not make
   435      * sense on windows, but mirrors the UNIX hook.
   436      */
   437 
   438     if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) {
   439 	return tclStubs.tcl_WaitForEvent(timePtr);
   440     }
   441 
   442     /*
   443      * Compute the timeout in milliseconds.
   444      */
   445 
   446     if (timePtr) {
   447 	timeout = timePtr->sec * 1000 + timePtr->usec / 1000;
   448     } else {
   449 	timeout = INFINITE;
   450     }
   451 
   452     /*
   453      * Check to see if there are any messages in the queue before waiting
   454      * because MsgWaitForMultipleObjects will not wake up if there are events
   455      * currently sitting in the queue.
   456      */
   457 
   458     if (!PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
   459 	/*
   460 	 * Wait for something to happen (a signal from another thread, a
   461 	 * message, or timeout).
   462 	 */
   463 
   464 	result = MsgWaitForMultipleObjects(1, &tsdPtr->event, FALSE, timeout,
   465 		QS_ALLINPUT);
   466     }
   467 
   468     /*
   469      * Check to see if there are any messages to process.
   470      */
   471 
   472     if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
   473 	/*
   474 	 * Retrieve and dispatch the first message.
   475 	 */
   476 
   477 	result = GetMessage(&msg, NULL, 0, 0);
   478 	if (result == 0) {
   479 	    /*
   480 	     * We received a request to exit this thread (WM_QUIT), so
   481 	     * propagate the quit message and start unwinding.
   482 	     */
   483 
   484 	    PostQuitMessage((int) msg.wParam);
   485 	    status = -1;
   486 	} else if (result == -1) {
   487 	    /*
   488 	     * We got an error from the system.  I have no idea why this would
   489 	     * happen, so we'll just unwind.
   490 	     */
   491 
   492 	    status = -1;
   493 	} else {
   494 	    TranslateMessage(&msg);
   495 	    DispatchMessage(&msg);
   496 	    status = 1;
   497 	}
   498     } else {
   499 	status = 0;
   500     }
   501 
   502     ResetEvent(tsdPtr->event);
   503     return status;
   504 }
   505 
   506 /*
   507  *----------------------------------------------------------------------
   508  *
   509  * Tcl_Sleep --
   510  *
   511  *	Delay execution for the specified number of milliseconds.
   512  *
   513  * Results:
   514  *	None.
   515  *
   516  * Side effects:
   517  *	Time passes.
   518  *
   519  *----------------------------------------------------------------------
   520  */
   521 
   522 void
   523 Tcl_Sleep(ms)
   524     int ms;			/* Number of milliseconds to sleep. */
   525 {
   526     /*
   527      * Simply calling 'Sleep' for the requisite number of milliseconds
   528      * can make the process appear to wake up early because it isn't
   529      * synchronized with the CPU performance counter that is used in
   530      * tclWinTime.c.  This behavior is probably benign, but messes
   531      * up some of the corner cases in the test suite.  We get around
   532      * this problem by repeating the 'Sleep' call as many times
   533      * as necessary to make the clock advance by the requisite amount.
   534      */
   535 
   536     Tcl_Time now;		/* Current wall clock time */
   537     Tcl_Time desired;		/* Desired wakeup time */
   538     DWORD sleepTime = ms;	/* Time to sleep */
   539 
   540     Tcl_GetTime( &now );
   541     desired.sec = now.sec + ( ms / 1000 );
   542     desired.usec = now.usec + 1000 * ( ms % 1000 );
   543     if ( desired.usec > 1000000 ) {
   544 	++desired.sec;
   545 	desired.usec -= 1000000;
   546     }
   547 	
   548     for ( ; ; ) {
   549 	Sleep( sleepTime );
   550 	Tcl_GetTime( &now );
   551 	if ( now.sec > desired.sec ) {
   552 	    break;
   553 	} else if ( ( now.sec == desired.sec )
   554 	     && ( now.usec >= desired.usec ) ) {
   555 	    break;
   556 	}
   557 	sleepTime = ( ( 1000 * ( desired.sec - now.sec ) )
   558 		      + ( ( desired.usec - now.usec ) / 1000 ) );
   559     }
   560 
   561 }