internal WispTabletDevice(TabletDeviceInfo tabletInfo, PenThread penThread) : base(tabletInfo) { _penThread = penThread; int count = tabletInfo.StylusDevicesInfo.Length; WispStylusDevice[] styluses = new WispStylusDevice[count]; for (int i = 0; i < count; i++) { StylusDeviceInfo cursorInfo = tabletInfo.StylusDevicesInfo[i]; styluses[i] = new WispStylusDevice( this, cursorInfo.CursorName, cursorInfo.CursorId, cursorInfo.CursorInverted, cursorInfo.ButtonCollection); } _stylusDeviceCollection = new StylusDeviceCollection(styluses); // We only create a TabletDevice when one is connected (physically or virtually). // So we can log the connection in the constructor. StylusTraceLogger.LogDeviceConnect( new StylusTraceLogger.StylusDeviceInfo( Id, Name, ProductId, TabletHardwareCapabilities, TabletSize, ScreenSize, _tabletInfo.DeviceType, StylusDevices.Count)); }
internal override void OnShutDown(object target, object sender, EventArgs e) { StylusLogic stylusLogic = (StylusLogic)target; StylusTraceLogger.LogStatistics(stylusLogic.Statistics); StylusTraceLogger.LogShutdown(); }
internal void DisposeOrDeferDisposal() { // Only dispose when no input events are left in the queue if (CanDispose) { // Make sure this device is not the current one. if (Tablet.CurrentTabletDevice == this.TabletDevice) { StylusLogic.GetCurrentStylusLogicAs <WispLogic>().SelectStylusDevice(null, null, true); } // A disconnect will be logged in the dispose as WPF will have gotten rid of the tablet. StylusTraceLogger.LogDeviceDisconnect(_tabletInfo.Id); // DDVSO:174153 // Force tablets to clean up as soon as they are disposed. This helps to reduce // COM references that might be waiting for RCWs to finalize. IPimcTablet2 tablet = _tabletInfo.PimcTablet?.Value; _tabletInfo.PimcTablet = null; if (tablet != null) { // DDVSO:514949 // Balance calls in PenThreadWorker.GetTabletInfoHelper and CPimcTablet::Init. PenThread.WorkerReleaseTabletLocks(tablet, _tabletInfo.WispTabletKey); Marshal.ReleaseComObject(tablet); } StylusDeviceCollection styluses = _stylusDeviceCollection; _stylusDeviceCollection = null; if (styluses != null) { styluses.Dispose(); } _penThread = null; _isDisposalPending = false; // DDVSO:614343 // Ensure that we are marked disposed and no longer attempt to finalize. _disposed = true; GC.SuppressFinalize(this); } else { _isDisposalPending = true; } }
internal void DisposeOrDeferDisposal() { // Only dispose when no input events are left in the queue if (CanDispose) { // Make sure this device is not the current one. if (Tablet.CurrentTabletDevice == this.TabletDevice) { StylusLogic.GetCurrentStylusLogicAs <WispLogic>().SelectStylusDevice(null, null, true); } // A disconnect will be logged in the dispose as WPF will have gotten rid of the tablet. StylusTraceLogger.LogDeviceDisconnect(_tabletInfo.Id); // DDVSO:174153 // Force tablets to clean up as soon as they are disposed. This helps to reduce // COM references that might be waiting for RCWs to finalize. IPimcTablet2 tablet = _tabletInfo.PimcTablet?.Value; _tabletInfo.PimcTablet = null; if (tablet != null) { Marshal.ReleaseComObject(tablet); } StylusDeviceCollection styluses = _stylusDeviceCollection; _stylusDeviceCollection = null; if (styluses != null) { styluses.Dispose(); } _penThread = null; _isDisposalPending = false; } else { _isDisposalPending = true; } }
///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// internal WispTabletDevice(TabletDeviceInfo tabletInfo, PenThread penThread) : base(tabletInfo) { _penThread = penThread; // Constructing a WispTabletDevice means we will actually use this tablet for input purposes. // Lock the tablet and underlying WISP tablet at this point. // This is balanced in DisposeOrDeferDisposal. _penThread.WorkerAcquireTabletLocks(tabletInfo.PimcTablet.Value, tabletInfo.WispTabletKey); int count = tabletInfo.StylusDevicesInfo.Length; WispStylusDevice[] styluses = new WispStylusDevice[count]; for (int i = 0; i < count; i++) { StylusDeviceInfo cursorInfo = tabletInfo.StylusDevicesInfo[i]; styluses[i] = new WispStylusDevice( this, cursorInfo.CursorName, cursorInfo.CursorId, cursorInfo.CursorInverted, cursorInfo.ButtonCollection); } _stylusDeviceCollection = new StylusDeviceCollection(styluses); // We only create a TabletDevice when one is connected (physically or virtually). // So we can log the connection in the constructor. StylusTraceLogger.LogDeviceConnect( new StylusTraceLogger.StylusDeviceInfo( Id, Name, ProductId, TabletHardwareCapabilities, TabletSize, ScreenSize, _tabletInfo.DeviceType, StylusDevices.Count)); }
/// <summary> /// DevDiv:1192272 /// /// This function has been changed to avoid re-entrancy issues. Previously, the /// PenThread selection depended on calls to AddPenContext in the selection mechanism /// or when creating a new PenThread. Since AddPenContext will wait on operations done /// on a PenThread, this would allow re-entrant calls to occur. These calls had the /// potential to generate redundant PenThreads that could cause performance and functional /// issues in touch-enabled applications. /// /// By removing calls to AddPenContext from the selection loops, we can be certain that there is /// no re-entrancy possible during this part of the code. The call to AddPenContext is now done /// post thread selection/creation. While this is still re-entrant, we handle the possible issues /// from that case by retrying thread selection for any failed AddPenContext calls, ignoring the /// specific thread that failed. After MAX_PENTHREAD_RETRIES, we exit and log an error. /// </summary> private PenThread GetPenThreadForPenContextHelper(PenContext penContext) { // A list of full PenThreads that we should ignore when attempting to select a thread // for this context. List <PenThread> ignoredThreads = new List <PenThread>(); PenThread selectedPenThread = null; // We have gone over the max retries, something is forcing a huge amount // of re-entrancy. In this case, break the loop and exit even if we might // have some issues with missing touch contexts and bad touch behavior. while (ignoredThreads.Count < MAX_PENTHREAD_RETRIES) { // Scan existing penthreads to find one to add the context to // We scan back to front to enable list cleanup. for (int i = _penThreadWeakRefList.Count - 1; i >= 0; i--) { PenThread candidatePenThread = null; // Select a thread if it's a valid WeakReference and we're not ignoring it // Allow selection to happen multiple times so we get the first valid candidate // in forward order. if (_penThreadWeakRefList[i].TryGetTarget(out candidatePenThread) && !ignoredThreads.Contains(candidatePenThread)) { selectedPenThread = candidatePenThread; } // This is an invalid WeakReference and should be removed else if (candidatePenThread == null) { _penThreadWeakRefList.RemoveAt(i); } } // If no valid thread was found, create a new one and add to the pool if (selectedPenThread == null) { selectedPenThread = new PenThread(); _penThreadWeakRefList.Add(new WeakReference <PenThread>(selectedPenThread)); } // If we have no context or we can successfully add to it, then end with this thread if (penContext == null || selectedPenThread.AddPenContext(penContext)) { break; } // If the add wasn't successful, this thread is full, so try again and ignore it else { ignoredThreads.Add(selectedPenThread); selectedPenThread = null; // Log re-entrant calls StylusTraceLogger.LogReentrancy(); } } // If we're here due to max retries, log errors appropriately if (selectedPenThread == null) { StylusTraceLogger.LogReentrancyRetryLimitReached(); Debug.Assert(false, "Retry limit reached when acquiring PenThread"); } return(selectedPenThread); }
/// <summary> /// Retrieves the latest device information from connected touch devices. /// </summary> internal void Refresh() { try { // Keep track of old tablets so that we can properly log connects/disconnects Dictionary <IntPtr, PointerTabletDevice> oldTablets = _tabletDeviceMap; _tabletDeviceMap = new Dictionary <IntPtr, PointerTabletDevice>(); TabletDevices.Clear(); uint deviceCount = 0; // Pattern is to first get the count, then declare an array of that size // which is then marshaled via the second call with the proper data. IsValid = UnsafeNativeMethods.GetPointerDevices(ref deviceCount, null); if (IsValid) { UnsafeNativeMethods.POINTER_DEVICE_INFO[] deviceInfos = new UnsafeNativeMethods.POINTER_DEVICE_INFO[deviceCount]; IsValid = UnsafeNativeMethods.GetPointerDevices(ref deviceCount, deviceInfos); if (IsValid) { foreach (var deviceInfo in deviceInfos) { // Old PenIMC code gets this id via a straight cast from COM pointer address // into an int32. This does a very similar thing semantically using the pointer // to the tablet from the WM_POINTER stack. While it may have similar issues // (chopping the upper bits, duplicate ids) we don't use this id internally // and have never received complaints about this in the WISP stack. int id = MS.Win32.NativeMethods.IntPtrToInt32(deviceInfo.device); PointerTabletDeviceInfo ptdi = new PointerTabletDeviceInfo(id, deviceInfo); // Don't add a device that fails initialization. This means we will try a refresh // next time around if we receive stylus input and the device is not available. // <see cref="HwndPointerInputProvider.UpdateCurrentTabletAndStylus"> if (ptdi.TryInitialize()) { PointerTabletDevice tablet = new PointerTabletDevice(ptdi); if (!oldTablets.Remove(tablet.Device)) { // We only create a TabletDevice when one is connected (physically or virtually). // As such we have to log when there is no corresponding old tablet being refreshed. StylusTraceLogger.LogDeviceConnect( new StylusTraceLogger.StylusDeviceInfo( tablet.Id, tablet.Name, tablet.ProductId, tablet.TabletHardwareCapabilities, tablet.TabletSize, tablet.ScreenSize, tablet.Type, tablet.StylusDevices.Count)); } _tabletDeviceMap[tablet.Device] = tablet; TabletDevices.Add(tablet.TabletDevice); } } } // Any tablet leftover here was not refreshed from the previous set of tablets // and should be logged as disconnected. foreach (var oldTablet in oldTablets.Values) { StylusTraceLogger.LogDeviceDisconnect(oldTablet.Id); } } } catch (Win32Exception) { IsValid = false; } }
internal void UpdateTablets() { if (_tablets == null) { throw new ObjectDisposedException("TabletDeviceCollection"); } // This method can be re-entered in a way that can cause deadlock // (Dev11 960656). This can happen if multiple WM_DEVICECHANGE // messages are pending and we have not yet built the tablet collection. // Here's how: // 1. First WM_DEVICECHANGE message enters here, starts to launch pen thread // 2. Penimc.UnsafeNativeMethods used for the first time, start its // static constructor. // 3. The static cctor starts to create a PimcManager via COM CLSID. // 4. COM pumps message, a second WM_DEVICECHANGE re-enters here // and starts to launch another pen thread. // 5. The static cctor is skipped (although step 3 hasn't finished), // and the pen thread is started // 6. The PenThreadWorker (on the UI thread) waits for the // PenThread to respond. // 7. Meanwhile the pen thread's code refers to Pimc.UnsafeNativeMethods. // It's on a separate thread, so it waits for the static cctor to finish. // Deadlock: UI thread is waiting for PenThread, but holding the CLR's // lock on the static cctor. The Pen thread is waiting for the static cctor. // // Multiple WM_DEVICECHANGE messages also cause re-entrancy even if the // static cctor has already finished. There's no deadlock in that case, // but we end up with multiple pen threads (steps 1 and 4). Besides being // redundant, that can cause problems when the threads collide with each // other or do work twice. // // In any case, the re-entrancy is harmful. So we avoid it in the usual // way - setting a flag on entry and early-exit if the flag is set. // // Usually the outermost call will leave the tablet collection in the // right state, but there's a small chance that the OS or pen-input service // will change something while the outermost call is in progress, so that // the inner (re-entrant) call really would have picked up new information. // To handle that case, we'll simply re-run the outermost call if any // re-entrancy is detected. Usually that will have no real effect, as // the code here detects when no change is made to the tablet collection. if (_inUpdateTablets) { // Log re-entrant calls StylusTraceLogger.LogReentrancy(); // this is a re-entrant call. Note that it happened, but do no work. _hasUpdateTabletsBeenCalledReentrantly = true; return; } try { _inUpdateTablets = true; do { _hasUpdateTabletsBeenCalledReentrantly = false; // do the real work UpdateTabletsImpl(); // if re-entrancy happened, start over // This could loop forever, but only if we get an unbounded // number of re-entrant events; that would have looped // forever even without this re-entrancy logic. } while (_hasUpdateTabletsBeenCalledReentrantly); } finally { // when we're done (either normally or via exception) // reset the re-entrancy state _inUpdateTablets = false; _hasUpdateTabletsBeenCalledReentrantly = false; } }