tclWinNotify.c
上传用户:rrhhcc
上传日期:2015-12-11
资源大小:54129k
文件大小:14k
源码类别:

通讯编程

开发平台:

Visual C++

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