File: System\Windows\Input\Stylus\Wisp\PenThreadWorker.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//#define TRACEPTW
 
using System.Runtime.InteropServices;
using System.Threading;
using System.Collections.ObjectModel;
using MS.Win32.Penimc;
 
namespace System.Windows.Input
{
    /////////////////////////////////////////////////////////////////////////
    internal sealed class PenThreadWorker
    {
         /// <summary>List of constants for PenImc</summary>
        const int PenEventNone           = 0;
        const int PenEventTimeout       = 1;
        const int PenEventPenInRange    = 707;
        const int PenEventPenOutOfRange = 708;
        const int PenEventPenDown       = 709;
        const int PenEventPenUp         = 710;
        const int PenEventPackets       = 711;
        const int PenEventSystem        = 714;
        
        const int MaxContextPerThread  = 31;  // (64 - 1) / 2 = 31.  Max handle limit for MsgWaitForMultipleMessageEx()
        const int EventsFrequency       = 8;
 
        IntPtr []             _handles = Array.Empty<IntPtr>();
 
        WeakReference []      _penContexts = Array.Empty<WeakReference>();
 
        IPimcContext3 []       _pimcContexts = Array.Empty<IPimcContext3>();
 
        /// <summary>
        /// A list of all WISP context COM object GIT keys that are locked via this thread.
        /// </summary>
        UInt32[] _wispContextKeys = Array.Empty<UInt32>();
 
        private readonly IntPtr                _pimcResetHandle;
        private volatile bool                  __disposed;
        private List <WorkerOperation>         _workerOperation = new List<WorkerOperation>();
        private object                         _workerOperationLock = new Object();
 
        // For caching move events.
        
        private PenContext                      _cachedMovePenContext;
        
        private int                             _cachedMoveStylusPointerId;
        private int                             _cachedMoveStartTimestamp;
        private int []                          _cachedMoveData;
 
 
        /////////////////////////////////////////////////////////////////////
        //
        // Here's a bunch of helper classes to manage marshalling the calls
        // over to the worker thread to be executed synchronously.
        //
        /////////////////////////////////////////////////////////////////////
 
        // Base class for all worker operations
        private abstract class WorkerOperation
        {
            AutoResetEvent  _doneEvent;
 
            internal WorkerOperation()
            {
                _doneEvent = new AutoResetEvent(false);
            }
 
            /// <summary>
            /// Critical - Calls SecurityCritical code OnDoWork which is differred based on the various derived class.
            ///             Called by PenThreadWorker.ThreadProc().
            /// </summary>
            internal void DoWork()
            {
                try
                {
                    OnDoWork();
                }
                finally
                {
                    _doneEvent.Set();
                }
}
 
            /// <summary>
            /// Critical - Calls SecurityCritical code OnDoWork which is differred based on the various derived class.
            ///             Called by WorkerOperation.DoWork().
            /// </summary>
            protected abstract void OnDoWork();
 
            internal AutoResetEvent DoneEvent
            {
                get { return _doneEvent;}
            }
        }
 
 
        // Class that handles getting the current rect for a tablet device.
        private class WorkerOperationThreadStart : WorkerOperation
        {
            /////////////////////////////////////////////////////////////////////////
            /// <summary>
            ///     Used to signal when the thread has started up.
            /// </summary>
            protected override void OnDoWork()
            {
                // We don't need to do anything.  Just have event signal we've executed.
            }
        }
 
 
        // Class that handles getting the tablet device info for all tablets on the system.
        private class WorkerOperationGetTabletsInfo : WorkerOperation
        {
            internal TabletDeviceInfo[] TabletDevicesInfo
            {
                get { return _tabletDevicesInfo;}
            }
 
            /////////////////////////////////////////////////////////////////////////
            /// <summary>
            ///     Returns the list of TabletDeviceInfo structs that contain information
            ///     about all of the TabletDevices on the system.
            /// </summary>
            protected override void OnDoWork()
            {
                try
                {
                    // create new collection of tablets
                    MS.Win32.Penimc.IPimcManager3 pimcManager = MS.Win32.Penimc.UnsafeNativeMethods.PimcManager;
                    uint cTablets;
                    pimcManager.GetTabletCount(out cTablets);
 
                    TabletDeviceInfo[] tablets = new TabletDeviceInfo[cTablets];
 
                    for ( uint iTablet = 0; iTablet < cTablets; iTablet++ )
                    {
                        MS.Win32.Penimc.IPimcTablet3 pimcTablet;
                        pimcManager.GetTablet(iTablet, out pimcTablet);
 
                        tablets[iTablet] = PenThreadWorker.GetTabletInfoHelper(pimcTablet);
                    }
 
                    // Set result data and signal we are done.
                    _tabletDevicesInfo = tablets;
                }
                catch (Exception e) when (PenThreadWorker.IsKnownException(e))
                {
                    Debug.WriteLine("WorkerOperationGetTabletsInfo.OnDoWork failed due to: {0}{1}", Environment.NewLine, e.ToString());
                }
            }
 
            TabletDeviceInfo[] _tabletDevicesInfo = Array.Empty<TabletDeviceInfo>();
        }
 
        // Class that handles creating a context for a particular tablet device.        
        private class WorkerOperationCreateContext : WorkerOperation
        {
            internal WorkerOperationCreateContext(IntPtr hwnd, IPimcTablet3 pimcTablet)
            {
                _hwnd = hwnd;
                _pimcTablet = pimcTablet;
            }
 
            internal PenContextInfo Result
            {
                get { return _result;}
            }
 
            /////////////////////////////////////////////////////////////////////////
            /// <summary>
            ///     Creates a new context for this a window and given tablet device and
            ///     returns a new PenContext in the workOperation class.
            /// </summary>
            protected override void OnDoWork()
            {
                IPimcContext3 pimcContext;
                int id;
                Int64 commHandle;
 
                try
                {
                    _pimcTablet.CreateContext(_hwnd, true, 250, out pimcContext, out id, out commHandle);
                    // Set result data and signal we are done.
                    PenContextInfo result;
                    result.ContextId = id;
                    result.PimcContext = pimcContext;
 
                    // commHandle cannot be a IntPtr by itself because its native counterpart cannot be a
                    // INT_PTR. The reason being that INT_PTR (__int3264) always gets marshalled as a
                    // 32 bit value, which means in a 64 bit process we would lose the first half of the pointer.
                    // Instead with this we always get a 64 bit value and then instantiate the IntPtr appropriately
                    // so that nothing gets lost during marshalling. The cast from Int64 to Int32 below
                    // should be lossless cast because both COM server and client are expected
                    // to be of same bitness (they are in the same process).
                    result.CommHandle = Environment.Is64BitProcess ? (nint)commHandle : (int)commHandle;
 
                    result.WispContextKey = MS.Win32.Penimc.UnsafeNativeMethods.QueryWispContextKey(pimcContext);
 
                    _result = result;
                }
                catch (Exception e) when (PenThreadWorker.IsKnownException(e))
                {
                    // result will not be initialized if we fail due to a COM exception.
                    Debug.WriteLine("WorkerOperationCreateContext.OnDoWork failed due to a {0}{1}", Environment.NewLine, e.ToString());
                }
            }
 
            IntPtr _hwnd;
            IPimcTablet3 _pimcTablet;
            PenContextInfo _result = new PenContextInfo();
        }
 
        /// <summary>
        /// Class to handle acquiring WISP/PenIMC tablet locks on the PenThread.
        /// </summary>
        private class WorkerOperationAcquireTabletLocks : WorkerOperation
        {
            internal WorkerOperationAcquireTabletLocks(IPimcTablet3 tablet, UInt32 wispTabletKey)
            {
                _tablet = tablet;
                _wispTabletKey = wispTabletKey;
            }
 
            internal bool Result { get; private set; }
 
            /// <summary>
            /// Releases the lock on the PenThread.
            /// </summary>
            protected override void OnDoWork()
            {
                MS.Win32.Penimc.UnsafeNativeMethods.AcquireTabletExternalLock(_tablet);
                MS.Win32.Penimc.UnsafeNativeMethods.CheckedLockWispObjectFromGit(_wispTabletKey);
                Result = true;
            }
 
            /// <summary>
            /// The PenIMC tablet
            /// </summary>
            IPimcTablet3 _tablet;
 
            /// <summary>
            /// The GIT key for the WISP COM object.
            /// </summary>
            UInt32 _wispTabletKey;
        }
 
        /// <summary>
        /// Class to handle releasing WISP/PenIMC tablet locks on the PenThread.
        /// </summary>
        private class WorkerOperationReleaseTabletLocks : WorkerOperation
        {
            internal WorkerOperationReleaseTabletLocks(IPimcTablet3 tablet, UInt32 wispTabletKey)
            {
                _tablet = tablet;
                _wispTabletKey = wispTabletKey;
            }
 
            internal bool Result { get; private set; }
 
            /// <summary>
            /// Releases the lock on the PenThread.
            /// </summary>
            protected override void OnDoWork()
            {
                MS.Win32.Penimc.UnsafeNativeMethods.CheckedUnlockWispObjectFromGit(_wispTabletKey);
                MS.Win32.Penimc.UnsafeNativeMethods.ReleaseTabletExternalLock(_tablet);
                Result = true;
            }
 
            /// <summary>
            /// The PenIMC tablet
            /// </summary>
            IPimcTablet3 _tablet;
            
            /// <summary>
            /// The GIT key for the WISP COM object.
            /// </summary>
            UInt32 _wispTabletKey;
        }
 
        // Class that handles refreshing the cursor devices for a particular tablet device.        
        private class WorkerOperationRefreshCursorInfo : WorkerOperation
        {
            internal WorkerOperationRefreshCursorInfo(IPimcTablet3 pimcTablet)
            {
                _pimcTablet = pimcTablet;
            }
 
            internal StylusDeviceInfo[] StylusDevicesInfo
            {
                get
                {
                    return _stylusDevicesInfo;
                }
            }
 
            /////////////////////////////////////////////////////////////////////////
            /// <summary>
            ///     Causes the stylus devices info (cursors) in penimc to be refreshed 
            ///     for the passed in IPimcTablet3. 
            /// </summary>
            protected override void OnDoWork()
            {
                try
                {
                    _pimcTablet.RefreshCursorInfo();
                    _stylusDevicesInfo = PenThreadWorker.GetStylusDevicesInfo(_pimcTablet);
                }
                catch (Exception e) when (PenThreadWorker.IsKnownException(e))
                {
                    Debug.WriteLine("WorkerOperationRefreshCursorInfo.OnDoWork failed due to a {0}{1}", Environment.NewLine, e.ToString());
                }
            }
 
            IPimcTablet3 _pimcTablet;
 
            StylusDeviceInfo[]  _stylusDevicesInfo = Array.Empty<StylusDeviceInfo>();
        }
 
        // Class that handles getting info about a specific tablet device.
        private class WorkerOperationGetTabletInfo : WorkerOperation
        {
            internal WorkerOperationGetTabletInfo(uint index)
            {
                _index = index;
            }
 
            internal TabletDeviceInfo TabletDeviceInfo
            {
                get { return _tabletDeviceInfo;}
            }
 
            /////////////////////////////////////////////////////////////////////////
            /// <summary>
            ///     Fills in a struct containing the list of TabletDevice properties for
            ///     a given tablet device index.
            /// </summary>
            protected override void OnDoWork()
            {
                try
                {
                    // create new collection of tablets
                    MS.Win32.Penimc.IPimcManager3 pimcManager = MS.Win32.Penimc.UnsafeNativeMethods.PimcManager;
                    MS.Win32.Penimc.IPimcTablet3 pimcTablet;
                    pimcManager.GetTablet(_index, out pimcTablet);
 
                    // Set result data and signal we are done.
                    _tabletDeviceInfo = PenThreadWorker.GetTabletInfoHelper(pimcTablet);
                }
                catch (Exception e) when (PenThreadWorker.IsKnownException(e))
                {
                    // result will not be initialized if we fail due to a COM exception.
                    Debug.WriteLine("WorkerOperationGetTabletInfo.OnDoWork failed due to {0}{1}", Environment.NewLine, e.ToString());
                }
            }
 
            uint             _index;
            TabletDeviceInfo _tabletDeviceInfo = new TabletDeviceInfo();
        }
        
        // Class that handles getting the current rect for a tablet device.
        private class WorkerOperationWorkerGetUpdatedSizes : WorkerOperation
        {
            internal WorkerOperationWorkerGetUpdatedSizes(IPimcTablet3 pimcTablet)
            {
                _pimcTablet = pimcTablet;
            }
 
            internal TabletDeviceSizeInfo TabletDeviceSizeInfo
            {
                get { return _tabletDeviceSizeInfo;}
            }
 
 
            /////////////////////////////////////////////////////////////////////////
            /// <summary>
            ///     Gets the current rectangle for a tablet device and returns in workOperation class.
            /// </summary>
            protected override void OnDoWork()
            {
                try
                {
                    int displayWidth, displayHeight, tabletWidth, tabletHeight;
                    _pimcTablet.GetTabletAndDisplaySize(out tabletWidth, out tabletHeight, out displayWidth, out displayHeight);
 
                    // Set result data and signal we are done.
                    _tabletDeviceSizeInfo = new TabletDeviceSizeInfo(
                                        new Size( tabletWidth, tabletHeight), 
                                        new Size( displayWidth, displayHeight));
                }
                catch (Exception e) when (PenThreadWorker.IsKnownException(e))
                {
                    Debug.WriteLine("WorkerOperationWorkerGetUpdatedSizes.OnDoWork failed due to a {0}{1}", Environment.NewLine, e.ToString());
                }
            }
 
            IPimcTablet3          _pimcTablet;
            TabletDeviceSizeInfo _tabletDeviceSizeInfo = new TabletDeviceSizeInfo(new Size( 1, 1), new Size( 1, 1));
        }
 
 
        // Class that handles getting the current rect for a tablet device.
        private class WorkerOperationAddContext : WorkerOperation
        {
            internal WorkerOperationAddContext(PenContext penContext, PenThreadWorker penThreadWorker)
            {
                _newPenContext = penContext;
                _penThreadWorker = penThreadWorker;
            }
 
            internal bool Result
            {
                get { return _result;}
            }
 
            /////////////////////////////////////////////////////////////////////////
            /// <summary>
            ///     Adds a PenContext to the list of contexts that events can be received
            ///     from and returns whether it was successful in workOperation class.
            /// </summary>
            protected override void OnDoWork()
            {
                _result = _penThreadWorker.AddPenContext(_newPenContext);
            }
                    
            PenContext      _newPenContext;
            PenThreadWorker _penThreadWorker;
 
            bool _result;
        }
 
        // Class that handles getting the current rect for a tablet device.
        private class WorkerOperationRemoveContext : WorkerOperation
        {
            internal WorkerOperationRemoveContext(PenContext penContext, PenThreadWorker penThreadWorker)
            {
                _penContextToRemove = penContext;
                _penThreadWorker = penThreadWorker;
            }
 
            internal bool Result
            {
                get { return _result;}
            }
 
            /////////////////////////////////////////////////////////////////////////
            /// <summary>
            ///     Adds a PenContext to the list of contexts that events can be received
            ///     from and returns whether it was successful in workOperation class.
            /// </summary>
            protected override void OnDoWork()
            {
                _result = _penThreadWorker.RemovePenContext(_penContextToRemove);
            }
                    
            PenContext  _penContextToRemove;
            PenThreadWorker _penThreadWorker;
 
            bool _result;
        }
 
 
        /////////////////////////////////////////////////////////////////////
 
        internal PenThreadWorker()
        {
            IntPtr resetHandle;
            // Consider: We could use a AutoResetEvent handle instead and avoid the penimc.dll call.
            MS.Win32.Penimc.UnsafeNativeMethods.CreateResetEvent(out resetHandle);
            _pimcResetHandle = resetHandle;
 
            WorkerOperationThreadStart started = new WorkerOperationThreadStart();
            lock(_workerOperationLock)
            {
                _workerOperation.Add((WorkerOperation)started);
            }
 
            Thread thread = new Thread(new ThreadStart(ThreadProc));
            thread.IsBackground = true; // don't hold process open due to this thread.
            thread.Start();
            
            // Wait for this work to be completed (ie thread is started up).
            started.DoneEvent.WaitOne();
            started.DoneEvent.Close();
        }
 
        internal void Dispose()
        {
            if(!__disposed)
            {
                __disposed = true;
                
                // Kick thread to wake up and see we are disposed.
                MS.Win32.Penimc.UnsafeNativeMethods.RaiseResetEvent(_pimcResetHandle);
                // Let it destroy the reset event.
            }
            GC.KeepAlive(this);
        }
 
        /////////////////////////////////////////////////////////////////////
 
        internal bool WorkerAddPenContext(PenContext penContext)
        {
            if (__disposed)
            {
                throw new ObjectDisposedException(null, SR.Penservice_Disposed);
            }
 
            Debug.Assert(penContext != null);
            
            WorkerOperationAddContext addContextOperation = new WorkerOperationAddContext(penContext, this);
 
            lock(_workerOperationLock)
            {
                _workerOperation.Add(addContextOperation);
            }
 
            // Kick thread to do this work.
            MS.Win32.Penimc.UnsafeNativeMethods.RaiseResetEvent(_pimcResetHandle);
 
            // Wait for this work to be completed.
            addContextOperation.DoneEvent.WaitOne();
            addContextOperation.DoneEvent.Close();
 
            return addContextOperation.Result;
        }
 
 
        internal bool WorkerRemovePenContext(PenContext penContext)
        {
            if (__disposed)
            {
                return true;
            }
 
            Debug.Assert(penContext != null);
            
            WorkerOperationRemoveContext removeContextOperation = new WorkerOperationRemoveContext(penContext, this);
 
            lock(_workerOperationLock)
            {
                _workerOperation.Add(removeContextOperation);
            }
 
            // Kick thread to do this work.
            MS.Win32.Penimc.UnsafeNativeMethods.RaiseResetEvent(_pimcResetHandle);
 
            // Wait for this work to be completed.
            removeContextOperation.DoneEvent.WaitOne();
            removeContextOperation.DoneEvent.Close();
 
            return removeContextOperation.Result;
        }
 
        /////////////////////////////////////////////////////////////////////
 
        internal TabletDeviceInfo[] WorkerGetTabletsInfo()
        {
            // Set data up for this call
            WorkerOperationGetTabletsInfo getTablets = new WorkerOperationGetTabletsInfo();
            
            lock(_workerOperationLock)
            {
                _workerOperation.Add(getTablets);
            }
 
            // Kick thread to do this work.
            MS.Win32.Penimc.UnsafeNativeMethods.RaiseResetEvent(_pimcResetHandle);
 
            // Wait for this work to be completed.
            getTablets.DoneEvent.WaitOne();
            getTablets.DoneEvent.Close();
        
            return getTablets.TabletDevicesInfo;
        }
 
 
        internal PenContextInfo WorkerCreateContext(IntPtr hwnd, IPimcTablet3 pimcTablet)
        {
            WorkerOperationCreateContext createContextOperation = new WorkerOperationCreateContext(
                                                                    hwnd,
                                                                    pimcTablet);
            lock(_workerOperationLock)
            {
                _workerOperation.Add(createContextOperation);
            }
 
            // Kick thread to do this work.
            MS.Win32.Penimc.UnsafeNativeMethods.RaiseResetEvent(_pimcResetHandle);
 
            // Wait for this work to be completed.
            createContextOperation.DoneEvent.WaitOne();
            createContextOperation.DoneEvent.Close();
 
            return createContextOperation.Result;
        }
 
        /// <summary>
        /// Instantiates a worker to acquire a WISP/PenIMC tablet object's lock on the PenThread and waits on the operation.
        /// </summary>
        /// <param name="gitKey">The GIT key for the WISP COM object.</param>
        /// <returns>True if successful, false otherwise.</returns>
        internal bool WorkerAcquireTabletLocks(IPimcTablet3 tablet, UInt32 wispTabletKey)
        {
            WorkerOperationAcquireTabletLocks acquireOperation =
                new WorkerOperationAcquireTabletLocks(tablet, wispTabletKey);
 
            lock (_workerOperationLock)
            {
                _workerOperation.Add(acquireOperation);
            }
 
            // Kick thread to do this work.
            MS.Win32.Penimc.UnsafeNativeMethods.RaiseResetEvent(_pimcResetHandle);
 
            // Wait for this work to be completed.
            acquireOperation.DoneEvent.WaitOne();
            acquireOperation.DoneEvent.Close();
 
            return acquireOperation.Result;
        }
 
        /// <summary>
        /// Instantiates a worker to release a WISP/PenIMC tablet object's lock on the PenThread and waits on the operation.
        /// </summary>
        /// <param name="gitKey">The GIT key for the WISP COM object.</param>
        /// <returns>True if successful, false otherwise.</returns>
        internal bool WorkerReleaseTabletLocks(IPimcTablet3 tablet, UInt32 wispTabletKey)
        {
            WorkerOperationReleaseTabletLocks releaseOperation = 
                new WorkerOperationReleaseTabletLocks(tablet, wispTabletKey);
 
            lock (_workerOperationLock)
            {
                _workerOperation.Add(releaseOperation);
            }
 
            // Kick thread to do this work.
            MS.Win32.Penimc.UnsafeNativeMethods.RaiseResetEvent(_pimcResetHandle);
 
            // Wait for this work to be completed.
            releaseOperation.DoneEvent.WaitOne();
            releaseOperation.DoneEvent.Close();
 
            return releaseOperation.Result;
        }
 
        internal StylusDeviceInfo[] WorkerRefreshCursorInfo(IPimcTablet3 pimcTablet)
        {
            WorkerOperationRefreshCursorInfo refreshCursorInfo = new WorkerOperationRefreshCursorInfo(
                                                                 pimcTablet);
            lock(_workerOperationLock)
            {
                _workerOperation.Add(refreshCursorInfo);
            }
 
            // Kick thread to do this work.
            MS.Win32.Penimc.UnsafeNativeMethods.RaiseResetEvent(_pimcResetHandle);
 
            // Wait for this work to be completed.
            refreshCursorInfo.DoneEvent.WaitOne();
            refreshCursorInfo.DoneEvent.Close();
 
            return refreshCursorInfo.StylusDevicesInfo;
        }
 
        internal TabletDeviceInfo WorkerGetTabletInfo(uint index)
        {
            // Set up data for call
            WorkerOperationGetTabletInfo getTabletInfo = new WorkerOperationGetTabletInfo(
                                                             index);
            lock(_workerOperationLock)
            {
                _workerOperation.Add(getTabletInfo);
            }
 
            // Kick thread to do this work.
            MS.Win32.Penimc.UnsafeNativeMethods.RaiseResetEvent(_pimcResetHandle);
 
            // Wait for this work to be completed.
            getTabletInfo.DoneEvent.WaitOne();
            getTabletInfo.DoneEvent.Close();
 
            return getTabletInfo.TabletDeviceInfo;
        }
 
        internal TabletDeviceSizeInfo WorkerGetUpdatedSizes(IPimcTablet3 pimcTablet)
        {           
            // Set data up for call
            WorkerOperationWorkerGetUpdatedSizes getUpdatedSizes = new WorkerOperationWorkerGetUpdatedSizes(pimcTablet);
            lock(_workerOperationLock)
            {
                _workerOperation.Add(getUpdatedSizes);
            }
 
            // Kick thread to do this work.
            MS.Win32.Penimc.UnsafeNativeMethods.RaiseResetEvent(_pimcResetHandle);
 
            // Wait for this work to be completed.
            getUpdatedSizes.DoneEvent.WaitOne();
            getUpdatedSizes.DoneEvent.Close();
            
            return getUpdatedSizes.TabletDeviceSizeInfo;
        }
 
        /////////////////////////////////////////////////////////////////////
        void FlushCache(bool goingOutOfRange)
        {
            // Force any cached move/inairmove data to be flushed if we have any.
            if (_cachedMoveData != null)
            {
                // If we are going out of range and this stylus id is not currently in range
                // then eat these cached events (keeps from going in and out of range quickly)
                if (!goingOutOfRange || _cachedMovePenContext.IsInRange(_cachedMoveStylusPointerId))
                {
                    _cachedMovePenContext.FirePenInRange(_cachedMoveStylusPointerId, _cachedMoveData, _cachedMoveStartTimestamp);
                    _cachedMovePenContext.FirePackets(_cachedMoveStylusPointerId, _cachedMoveData, _cachedMoveStartTimestamp);
                }
 
                _cachedMoveData = null;
                _cachedMovePenContext = null;
                _cachedMoveStylusPointerId = 0;
            }
        }
 
        /////////////////////////////////////////////////////////////////////
 
        bool DoCacheEvent(int evt, PenContext penContext, int stylusPointerId, int [] data, int timestamp)
        {
            // NOTE: Big assumption is that we always get other events between packets (ie don't get move
            // down position followed by move in up position).  We don't account for that here but it should
            // never happen.
            if (evt == PenEventPackets)
            {
                // If no cache then just cache it.
                if (_cachedMoveData == null)
                {
                    _cachedMovePenContext = penContext;
                    _cachedMoveStylusPointerId = stylusPointerId;
                    _cachedMoveStartTimestamp = timestamp;
                    _cachedMoveData = data;
                    return true;
                }
                else if (_cachedMovePenContext == penContext && stylusPointerId == _cachedMoveStylusPointerId)
                {
                    int sinceBeginning = timestamp - _cachedMoveStartTimestamp;
                    if (timestamp < _cachedMoveStartTimestamp)
                        sinceBeginning = (Int32.MaxValue - _cachedMoveStartTimestamp) + timestamp;
 
                    if (EventsFrequency > sinceBeginning)
                    {
                        // Add to cache data
                        int[] data0 = _cachedMoveData;
                        _cachedMoveData = new int [data0.Length + data.Length];
                        data0.CopyTo(_cachedMoveData, 0);
                        data.CopyTo(_cachedMoveData, data0.Length);
                        return true;
                    }
                }
            }
 
            return false;
        }
 
        /////////////////////////////////////////////////////////////////////
 
        internal void FireEvent(PenContext penContext, int evt, int stylusPointerId, int cPackets, int cbPacket, IntPtr pPackets)
        {
            // disposed?
            if (__disposed)
            {
                return;  // Don't process this event if we're in the process of shutting down.
            }
 
            // marshal the data to our cache
            if (cbPacket % 4 != 0)
            {
                throw new InvalidOperationException(SR.PenService_InvalidPacketData);
            }
 
            int cItems = cPackets * (cbPacket / 4);
            int[] data = null;
            if (0 < cItems)
            {
                data = new int [cItems]; // GetDataArray(cItems); // see comment on GetDataArray
                Marshal.Copy(pPackets, data, 0, cItems);
                penContext.CheckForRectMappingChanged(data, cPackets);
            }
            else
            {
                data = null;
            }
 
            int timestamp = Environment.TickCount;
            
            // Deal with caching packet data.
            if (DoCacheEvent(evt, penContext, stylusPointerId, data, timestamp))
            {
                return;
            }
            else
            {
                FlushCache(false);  // make sure we flush cache if not caching.
            }
 
            //
            // fire it
            //
            switch (evt)
            {
                case PenEventPenDown:
                    penContext.FirePenInRange(stylusPointerId, data, timestamp);
                    penContext.FirePenDown(stylusPointerId, data, timestamp);
                    break;
 
                case PenEventPenUp:
                    penContext.FirePenInRange(stylusPointerId, data, timestamp);
                    penContext.FirePenUp(stylusPointerId, data, timestamp);
                    break;
 
                case PenEventPackets:
                    penContext.FirePenInRange(stylusPointerId, data, timestamp);
                    penContext.FirePackets(stylusPointerId, data, timestamp);
                    break;
 
                case PenEventPenInRange:
                    // We fire this special event just to give the app thread an early peak at
                    // the inrange to filter out mouse moves before we get our first stylus event.
                    penContext.FirePenInRange(stylusPointerId, null, timestamp);
                    break;
 
                case PenEventPenOutOfRange:
                    penContext.FirePenOutOfRange(stylusPointerId, timestamp);
                    break;
 
                case PenEventSystem:
                    penContext.FireSystemGesture(stylusPointerId, timestamp);
                    break;
            }
        }
 
        /////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Returns a struct containing the list of TabletDevice properties for
        ///     a given tablet device (pimcTablet).
        /// </summary>
        private static TabletDeviceInfo GetTabletInfoHelper(IPimcTablet3 pimcTablet)
        {
            TabletDeviceInfo tabletInfo = new TabletDeviceInfo();
 
            tabletInfo.PimcTablet = pimcTablet;
            pimcTablet.GetKey(out tabletInfo.Id);
            pimcTablet.GetName(out tabletInfo.Name);
            pimcTablet.GetPlugAndPlayId(out tabletInfo.PlugAndPlayId);
            int iTabletWidth, iTabletHeight, iDisplayWidth, iDisplayHeight;
            pimcTablet.GetTabletAndDisplaySize(out iTabletWidth, out iTabletHeight, out iDisplayWidth, out iDisplayHeight);
            tabletInfo.SizeInfo = new TabletDeviceSizeInfo(new Size(iTabletWidth, iTabletHeight),
                                                           new Size(iDisplayWidth, iDisplayHeight));
            int caps;
            pimcTablet.GetHardwareCaps(out caps);
            tabletInfo.HardwareCapabilities = (TabletHardwareCapabilities)caps;
            int deviceType;
            pimcTablet.GetDeviceType(out deviceType);
            tabletInfo.DeviceType = (TabletDeviceType)(deviceType -1);
 
            // 
            // REENTRANCY NOTE: Let a PenThread do this work to avoid reentrancy!
            //                  The IPimcTablet3 object is created in the pen thread. If we access it from the UI thread,
            //                  COM will set up message pumping which will cause reentrancy here.
            InitializeSupportedStylusPointProperties(pimcTablet, tabletInfo);
            tabletInfo.StylusDevicesInfo = GetStylusDevicesInfo(pimcTablet);
 
            
            // Obtain the WispTabletKey for future use in locking the WISP tablet.
            tabletInfo.WispTabletKey = MS.Win32.Penimc.UnsafeNativeMethods.QueryWispTabletKey(pimcTablet);
 
            
            // If the manager has not already been created and locked, we will lock it here.  This is the first opportunity
            // we will have to lock the manager as it will have been created on the thread to instantiate the first tablet.
            MS.Win32.Penimc.UnsafeNativeMethods.SetWispManagerKey(pimcTablet);
 
            MS.Win32.Penimc.UnsafeNativeMethods.LockWispManager();
 
            return tabletInfo;
        }
 
        /////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Initializing the supported stylus point properties. and returns in workOperation class.
        /// </summary>
        private static void InitializeSupportedStylusPointProperties(IPimcTablet3 pimcTablet, TabletDeviceInfo tabletInfo)
        {
            int cProps;
            int cButtons;
            int pressureIndex = -1;
 
            pimcTablet.GetPacketDescriptionInfo(out cProps, out cButtons); // Calls Unmanaged code - SecurityCritical with SUC.
            List<StylusPointProperty> properties = new List<StylusPointProperty>(cProps + cButtons + 3);
            for ( int i = 0; i < cProps; i++ )
            {
                Guid guid;
                int min, max;
                int units;
                float res;
                pimcTablet.GetPacketPropertyInfo(i, out guid, out min, out max, out units, out res); // Calls Unmanaged code - SecurityCritical with SUC.
 
                if ( pressureIndex == -1 && guid == StylusPointPropertyIds.NormalPressure )
                {
                    pressureIndex = i;
                }
 
                StylusPointProperty property = new StylusPointProperty(guid, false);
                properties.Add(property);
            }
 
            for ( int i = 0; i < cButtons; i++ )
            {
                Guid buttonGuid;
                pimcTablet.GetPacketButtonInfo(i, out buttonGuid); // Calls Unmanaged code - SecurityCritical with SUC.
 
                StylusPointProperty buttonProperty = new StylusPointProperty(buttonGuid, true);
                properties.Add(buttonProperty);
            }
 
            //validate we can never get X, Y at index != 0, 1
            Debug.Assert(properties[StylusPointDescription.RequiredXIndex /*0*/].Id == StylusPointPropertyIds.X, "X isn't where we expect it! Fix PenImc to ask for X at index 0");
            Debug.Assert(properties[StylusPointDescription.RequiredYIndex /*1*/].Id == StylusPointPropertyIds.Y, "Y isn't where we expect it! Fix PenImc to ask for Y at index 1");
            // NOTE: We can't force pressure since touch digitizers may not provide this info.  The following assert is bogus.
            //Debug.Assert(pressureIndex == -1 || pressureIndex == StylusPointDescription.RequiredPressureIndex /*2*/,
            //    "Fix PenImc to ask for NormalPressure at index 2!");
 
            if ( pressureIndex == -1 )
            {
                //pressure wasn't found.  Add it
                properties.Insert(StylusPointDescription.RequiredPressureIndex /*2*/, System.Windows.Input.StylusPointProperties.NormalPressure);
            }
            else
            {
                //this device supports pressure
                tabletInfo.HardwareCapabilities |= TabletHardwareCapabilities.SupportsPressure;
            }
 
            tabletInfo.StylusPointProperties = new ReadOnlyCollection<StylusPointProperty>(properties);
            tabletInfo.PressureIndex = pressureIndex;
        }
 
        /////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Getting the cursor info of the stylus devices.
        /// </summary>
        private static StylusDeviceInfo[] GetStylusDevicesInfo(IPimcTablet3 pimcTablet)
        {
            int cCursors;
 
            pimcTablet.GetCursorCount(out cCursors); // Calls Unmanaged code - SecurityCritical with SUC.
 
            StylusDeviceInfo[] stylusDevicesInfo = new StylusDeviceInfo[cCursors];
 
            for ( int iCursor = 0; iCursor < cCursors; iCursor++ )
            {
                string sCursorName;
                int cursorId;
                bool fCursorInverted;
                pimcTablet.GetCursorInfo(iCursor, out sCursorName, out cursorId, out fCursorInverted); // Calls Unmanaged code - SecurityCritical with SUC.
 
                int cButtons;
 
                pimcTablet.GetCursorButtonCount(iCursor, out cButtons); // Calls Unmanaged code - SecurityCritical with SUC.
                StylusButton[] buttons = new StylusButton[cButtons];
                for ( int iButton = 0; iButton < cButtons; iButton++ )
                {
                    string sButtonName;
                    Guid buttonGuid;
                    pimcTablet.GetCursorButtonInfo(iCursor, iButton, out sButtonName, out buttonGuid); // Calls Unmanaged code - SecurityCritical with SUC.
                    buttons[iButton] = new StylusButton(sButtonName, buttonGuid);
                }
                StylusButtonCollection buttonCollection = new StylusButtonCollection(buttons);
 
                stylusDevicesInfo[iCursor].CursorName = sCursorName;
                stylusDevicesInfo[iCursor].CursorId = cursorId;
                stylusDevicesInfo[iCursor].CursorInverted = fCursorInverted;
                stylusDevicesInfo[iCursor].ButtonCollection = buttonCollection;
            }
 
            return stylusDevicesInfo;
        }
 
 
        internal bool AddPenContext(PenContext penContext)
        {
            List <PenContext> penContextRefs = new List<PenContext>(); // keep them alive while processing!
            int i;
            bool result = false;
 
            // Now go through and figure out the good entries
            // Need to clean up the list for gc'd references.
            for (i=0; i<_penContexts.Length; i++)
            {
                if (_penContexts[i].IsAlive)
                {
                    PenContext pc = _penContexts[i].Target as PenContext;
                    // We only need to ref if we have a penContext.
                    if (pc != null)
                    {
                        penContextRefs.Add(pc);
                    }
                }
            }
 
            // Now try again to see if we have room.
            if (penContextRefs.Count < MaxContextPerThread)
            {
                penContextRefs.Add(penContext); // add the new one to our list.
 
                
                // Lock the WISP Context to protect against COM rundown
                MS.Win32.Penimc.UnsafeNativeMethods.CheckedLockWispObjectFromGit(penContext.WispContextKey);
 
                // Now build up the handle array and PimcContext ref array.
                _pimcContexts = new IPimcContext3[penContextRefs.Count];
                _penContexts = new WeakReference[penContextRefs.Count];
                _handles = new IntPtr[penContextRefs.Count];
                _wispContextKeys = new UInt32[penContextRefs.Count];
 
                for (i=0; i < penContextRefs.Count; i++)
                {
                    PenContext pc = penContextRefs[i];
                    // We'd have hole in our array if this ever happened.
                    Debug.Assert(pc != null && pc.CommHandle != IntPtr.Zero);
                    _handles[i] = pc.CommHandle; // Add to array.
                    _pimcContexts[i] = pc._pimcContext;
                    _penContexts[i] = new WeakReference(pc);
                    _wispContextKeys[i] = pc.WispContextKey;
                    pc = null;
                }
 
                result = true;
            }
 
            // Now clean up old refs and assign new array.
            penContextRefs.Clear(); // Make sure we remove refs!
            penContextRefs = null;
 
            return result;
        }
 
 
        internal bool RemovePenContext(PenContext penContext)
        {
            List <PenContext> penContextRefs = new List<PenContext>(); // keep them alive while processing!
            int i;
            bool removed = false;
 
            // Now go through and figure out the good entries
            // Need to clean up the list for gc'd references.
            for (i=0; i<_penContexts.Length; i++)
            {
                if (_penContexts[i].IsAlive)
                {
                    PenContext pc = _penContexts[i].Target as PenContext;
                    // See if we should keep this PenContext.  
                    // We keep if not GC'd and not the removing one (except if it is 
                    // in range where we need to wait till it goes out of range).
                    if (pc != null && (pc != penContext || pc.IsInRange(0)))
                    {
                        penContextRefs.Add(pc);
                    }
                }
            }
 
            removed = !penContextRefs.Contains(penContext);
 
            // Now build up the handle array and PimcContext ref array.
            _pimcContexts = new IPimcContext3[penContextRefs.Count];
            _penContexts = new WeakReference[penContextRefs.Count];
            _handles = new IntPtr[penContextRefs.Count];
            _wispContextKeys = new UInt32[penContextRefs.Count];
 
            for (i=0; i < penContextRefs.Count; i++)
            {
                PenContext pc = penContextRefs[i];
                // We'd have hole in our array if this ever happened.
                Debug.Assert(pc != null && pc.CommHandle != IntPtr.Zero);
                _handles[i] = pc.CommHandle; // Add to array.
                _pimcContexts[i] = pc._pimcContext;
                _penContexts[i] = new WeakReference(pc);
                _wispContextKeys[i] = pc.WispContextKey;
                pc = null;
            }
 
            // Now clean up old refs and assign new array.
            penContextRefs.Clear(); // Make sure we remove refs!
            penContextRefs = null;
 
            if (removed)
            {
                
                // Unlock the WISP Context balancing the call in AddPenContext
                MS.Win32.Penimc.UnsafeNativeMethods.CheckedUnlockWispObjectFromGit(penContext.WispContextKey);
 
                
                // Since we are no longer using this PenContext, ensure it's released in the
                // native layer.
                // This is needed due to a COM rundown issue in the OS(OSGVSO:10779198).  If 
                // the COM proxy that the IPimcContext RCW holds is disconnected, the RCW will 
                // release the references to the underlying CPimcContext.  WPF will then use the
                // raw pointer to this object without realizing it has been destructed, leading
                // to AVs.
                penContext._pimcContext.ShutdownComm();
 
                
                // Release the PenIMC object only when we are assured that the
                // context was removed from the list of waiting handles.
                Marshal.ReleaseComObject(penContext._pimcContext);
            }
 
            return removed;
        }
 
        /// <summary>
        /// Filters exceptions that we know could potentially throw in calls down into PenIMC
        /// </summary>
        /// <param name="e">The exception to filter</param>
        /// <returns>True if we should handle the exception, false otherwise.</returns>
        private static bool IsKnownException(Exception e)
        {
            return (e is COMException
                    || e is ArgumentException
                    || e is UnauthorizedAccessException
                    || e is InvalidCastException);
        }
 
        /////////////////////////////////////////////////////////////////////
 
        internal void ThreadProc()
        {
            Thread.CurrentThread.Name = "Stylus Input";
 
            try
            {
                //
                // the rarely iterated loop
                //
                while (!__disposed)
                {
#if TRACEPTW
                    Debug.WriteLine(String.Format("PenThreadWorker::ThreadProc():  Update __penContextWeakRefList loop"));
#endif
 
                    // We need to ensure that the PenIMC COM objects can be used from this thread.
                    // Try this every outer loop since we're, generally, about to do management
                    // operations.
                    MS.Win32.Penimc.UnsafeNativeMethods.EnsurePenImcClassesActivated();
 
                    WorkerOperation[] workerOps = null;
 
                    lock(_workerOperationLock)
                    {
                        if (_workerOperation.Count > 0)
                        {
                            workerOps = _workerOperation.ToArray();
                            _workerOperation.Clear();
                        }
                    }
 
                    if (workerOps != null)
                    {
                        for (int j=0; j<workerOps.Length; j++)
                        {
                            workerOps[j].DoWork();
                        }
                        workerOps = null;
                    }
 
                    //
                    // the intense loop of dispatching events
                    //
 
                    while (true)
                    {
#if TRACEPTW
                        Debug.WriteLine (String.Format("PenThreadWorker::ThreadProc - handle event loop"));
#endif
                        // get next event
                        int     evt;
                        int     stylusPointerId;
                        int     cPackets, cbPacket;
                        IntPtr  pPackets;
                        int     iHandleEvt;
                        
                        if (_handles.Length == 1)
                        {
                            if (!MS.Win32.Penimc.UnsafeNativeMethods.GetPenEvent(
                                _handles[0], _pimcResetHandle,
                                out evt, out stylusPointerId,
                                out cPackets, out cbPacket, out pPackets))
                            {
                                break;
                            }
                            iHandleEvt = 0;
                        }
                        else
                        {
                            if (!MS.Win32.Penimc.UnsafeNativeMethods.GetPenEventMultiple(
                                _handles.Length, _handles, _pimcResetHandle,
                                out iHandleEvt, out evt, out stylusPointerId,
                                out cPackets, out cbPacket, out pPackets))
                            {
                                break;
                            }
                        }
                        if (evt != PenEventTimeout)
                        {
                            // dispatch the event
#if TRACEPTW
                            Debug.WriteLine (String.Format("PenThreadWorker::ThreadProc - FireEvent [evt={0}, stylusId={1}]", evt, stylusPointerId));
#endif
                            
                            // This comment addresses and IndexOutOfRangeException in PenThreadWorker which is related and likely caused by the above.
                            // This index is safe as long as there are no corruption issues within PenIMC.  There have been
                            // instances of IndexOutOfRangeExceptions from this code but this should not occur in practice.
                            // If this throws, check that the handles list generated in CPimcContext::GetPenEventMultiple
                            // is not corrupted (it has appropriate wait handles and does not point to invalid memory).
                            PenContext penContext = _penContexts[iHandleEvt].Target as PenContext;
                            // If we get an event from a GC'd PenContext then just ignore.
                            if (penContext != null)
                            {
                                FireEvent(penContext, evt, stylusPointerId, cPackets, cbPacket, pPackets);
                                penContext = null;
                            }
                        }
                        else
                        {
#if TRACEPTW
                            Debug.WriteLine (String.Format("PenThreadWorker::ThreadProc - FlushInput"));
#endif
                            FlushCache(true);
 
                            // we hit the timeout, make sure that all our devices are in the correct out-of-range state
                            // we are doing this to compinsate for drivers that send a move after they send a outofrange
                            for (int i = 0; i < _penContexts.Length; i++)
                            {
                                PenContext penContext = _penContexts[i].Target as PenContext;
                                if (penContext != null)
                                {
                                    // we send 0 as the stulyspointerId to trigger code in PenContext::FirePenOutOfRange
                                    penContext.FirePenOutOfRange(0, Environment.TickCount);
                                    penContext = null;
                                }
                            }
                        }
                    }
                }
            }
 
            finally
            {
                // Make sure we are marked as disposed now.  This keeps the
                // Dispose() method from doing any work.
                __disposed = true;
 
                // We've been disposed or hit thread abort.  Release this handle before exiting.
                MS.Win32.Penimc.UnsafeNativeMethods.DestroyResetEvent(_pimcResetHandle);
 
                
                // Release the manager locks, both PenIMC and WISP here to balance lock calls.
                MS.Win32.Penimc.UnsafeNativeMethods.UnlockWispManager();
                MS.Win32.Penimc.UnsafeNativeMethods.ReleaseManagerExternalLock();
 
                for (int i = 0; i < _pimcContexts.Length; i++)
                {
                    
                    // Unlock the WISP Context, balancing the call in AddPenContext.
                    MS.Win32.Penimc.UnsafeNativeMethods.CheckedUnlockWispObjectFromGit(_wispContextKeys[i]);
 
                    // Ensure that all native references are released.
                    _pimcContexts[i].ShutdownComm();
                }
 
                // Ensure that any activation contexts used on this thread are cleaned.
                MS.Win32.Penimc.UnsafeNativeMethods.DeactivatePenImcClasses();
 
                // Make sure the _pimcResetHandle is still valid after Dispose is called and before
                // our thread exits.
                GC.KeepAlive(this);
            }
        }
    }
}