Skip to content

Commit

Permalink
Fix PenIMC Activation Context Handling (#2890)
Browse files Browse the repository at this point in the history
* Ensuring COM activation contexts for PenIMC are created per PenThread and cleaned up when the PenThread dies.
  • Loading branch information
rladuca authored Jun 11, 2020
1 parent 5d75a0f commit 4a71eba
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,3 @@ extern "C" ULONG_PTR WINAPI RegisterDllForSxSCOM()
// Return the context cookie : caller is responsible for deactivating the context.
return activationContextCookie;
}



Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ internal static class UnsafeNativeMethods
[ThreadStatic]
private static IPimcManager3 _pimcManagerThreadStatic;

/// <summary>
/// The cookie for the PenIMC activation context.
/// </summary>
[ThreadStatic]
private static IntPtr _pimcActCtxCookie = IntPtr.Zero;

#endregion

#region PenIMC
Expand All @@ -86,26 +92,50 @@ internal static class UnsafeNativeMethods

/// <summary>
/// Make sure we load penimc.dll from WPF's installed location to avoid two instances of it.
/// </summary>
static UnsafeNativeMethods()
///
/// Add an activation context to the thread's stack to ensure the registration-free COM objects
/// are available.
/// </summary>
/// <remarks>
/// PenIMC COM objects are only directly used (functions called on their interfaces) from inside the
/// PenThread. As such, the PenThreads need to create the activation context. The various Dispatcher
/// threads need not do so as they merely pass the RCWs around in manged objects and will only use
/// them via operations queued on their associated PenThread.
/// </remarks>
internal static void EnsurePenImcClassesActivated()
{
// Register PenIMC for SxS COM for the lifetime of the process. No need to store the
// cookie; PenIMC's ActivationContext will never be removed from the ActivationContext
// stack.
//
// RegisterDllForSxSCOM returns a non-zero ActivationContextCookie if SxS registration
// succeeds, or IntPtr.Zero if SxS registration fails.
if (IntPtr.Zero == RegisterDllForSxSCOM())
if (_pimcActCtxCookie == IntPtr.Zero)
{
throw new InvalidOperationException(SR.Get(SRID.PenImcSxSRegistrationFailed, ExternDll.Penimc));
}
// Register PenIMC for SxS COM for the lifetime of the thread.
//
// RegisterDllForSxSCOM returns a non-zero ActivationContextCookie if SxS registration
// succeeds, or IntPtr.Zero if SxS registration fails.
if ((_pimcActCtxCookie = RegisterDllForSxSCOM()) == IntPtr.Zero)
{
throw new InvalidOperationException(SR.Get(SRID.PenImcSxSRegistrationFailed, ExternDll.Penimc));
}

// Ensure PenIMC loaded from the correct location.
var uncheckedDlls = WpfDllVerifier.VerifyWpfDllSet(ExternDll.Penimc);
// Ensure PenIMC loaded from the correct location.
var uncheckedDlls = WpfDllVerifier.VerifyWpfDllSet(ExternDll.Penimc);

if (uncheckedDlls.Contains(ExternDll.Penimc))
if (uncheckedDlls.Contains(ExternDll.Penimc))
{
throw new DllNotFoundException(SR.Get(SRID.PenImcDllVerificationFailed, ExternDll.Penimc));
}
}
}

/// <summary>
/// Deactivates the activation context for PenIMC objects.
/// </summary>
internal static void DeactivatePenImcClasses()
{
if (_pimcActCtxCookie != IntPtr.Zero)
{
throw new DllNotFoundException(SR.Get(SRID.PenImcDllVerificationFailed, ExternDll.Penimc));
if(DeactivateActCtx(0, _pimcActCtxCookie))
{
_pimcActCtxCookie = IntPtr.Zero;
}
}
}

Expand Down Expand Up @@ -586,7 +616,8 @@ internal static extern bool GetLastSystemEventData(
/// <param name="context">Context in which the newly created object will run</param>
/// <param name="iid">Identifier of the Interface</param>
/// <returns>Returns the COM object created by CoCreateInstance</returns>
[return: MarshalAs(UnmanagedType.Interface)][DllImport(ExternDll.Ole32, ExactSpelling=true, PreserveSig=false)]
[return: MarshalAs(UnmanagedType.Interface)]
[DllImport(ExternDll.Ole32, ExactSpelling = true, PreserveSig = false)]
private static extern object CoCreateInstance(
[In]
ref Guid clsid,
Expand All @@ -595,6 +626,20 @@ private static extern object CoCreateInstance(
int context,
[In]
ref Guid iid);

/// <summary>
/// Deactivates the specified Activation Context.
/// </summary>
/// <remarks>
/// See: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-deactivateactctx
/// </remarks>
/// <param name="flags">Flags that indicate how the deactivation is to occur.</param>
/// <param name="activationCtxCookie">The ULONG_PTR that was passed into the call to ActivateActCtx.
/// This value is used as a cookie to identify a specific activated activation context.</param>
/// <returns>True on success, false otherwise.</returns>
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport(ExternDll.Kernel32, ExactSpelling = true, PreserveSig = false)]
private static extern bool DeactivateActCtx(int flags, IntPtr activationCtxCookie);
}

#if OLD_ISF
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1164,7 +1164,12 @@ internal void ThreadProc()
Debug.WriteLine(String.Format("PenThreadWorker::ThreadProc(): Update __penContextWeakRefList loop"));
#endif

WorkerOperation [] workerOps = null;
// 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)
{
Expand Down Expand Up @@ -1289,6 +1294,9 @@ internal void ThreadProc()
_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);
Expand Down

0 comments on commit 4a71eba

Please sign in to comment.