|
// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
//
// <OWNER>Microsoft</OWNER>
// <OWNER>Microsoft</OWNER>
// <OWNER>Microsoft</OWNER>
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Security;
namespace System.Runtime.InteropServices.WindowsRuntime
{
// Helper functions to manually marshal data between .NET and WinRT
public static class WindowsRuntimeMarshal
{
// Add an event handler to a Windows Runtime style event, such that it can be removed via a delegate
// lookup at a later time. This method adds the handler to the add method using the supplied
// delegate. It then stores the corresponding token in a dictionary for easy access by RemoveEventHandler
// later. Note that the dictionary is indexed by the remove method that will be used for RemoveEventHandler
// so the removeMethod given here must match the remove method supplied there exactly.
[SecurityCritical]
public static void AddEventHandler<T>(Func<T, EventRegistrationToken> addMethod,
Action<EventRegistrationToken> removeMethod,
T handler)
{
if (addMethod == null)
throw new ArgumentNullException("addMethod");
if (removeMethod == null)
throw new ArgumentNullException("removeMethod");
Contract.EndContractBlock();
// Managed code allows adding a null event handler, the effect is a no-op. To match this behavior
// for WinRT events, we simply ignore attempts to add null.
if (handler == null)
{
return;
}
// Delegate to managed event registration implementation or native event registration implementation
// They have completely different implementation because native side has its own unique problem to solve -
// there could be more than one RCW for the same COM object
// it would be more confusing and less-performant if we were to merge them together
object target = removeMethod.Target;
if (target == null || Marshal.IsComObject(target))
NativeOrStaticEventRegistrationImpl.AddEventHandler<T>(addMethod, removeMethod, handler);
else
ManagedEventRegistrationImpl.AddEventHandler<T>(addMethod, removeMethod, handler);
}
// Remove the delegate handler from the Windows Runtime style event registration by looking for
// its token, previously stored via AddEventHandler<T>
[SecurityCritical]
public static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler)
{
if (removeMethod == null)
throw new ArgumentNullException("removeMethod");
Contract.EndContractBlock();
// Managed code allows removing a null event handler, the effect is a no-op. To match this behavior
// for WinRT events, we simply ignore attempts to remove null.
if (handler == null)
{
return;
}
// Delegate to managed event registration implementation or native event registration implementation
// They have completely different implementation because native side has its own unique problem to solve -
// there could be more than one RCW for the same COM object
// it would be more confusing and less-performant if we were to merge them together
object target = removeMethod.Target;
if (target == null || Marshal.IsComObject(target))
NativeOrStaticEventRegistrationImpl.RemoveEventHandler<T>(removeMethod, handler);
else
ManagedEventRegistrationImpl.RemoveEventHandler<T>(removeMethod, handler);
}
[SecurityCritical]
public static void RemoveAllEventHandlers(Action<EventRegistrationToken> removeMethod)
{
if (removeMethod == null)
throw new ArgumentNullException("removeMethod");
Contract.EndContractBlock();
// Delegate to managed event registration implementation or native event registration implementation
// They have completely different implementation because native side has its own unique problem to solve -
// there could be more than one RCW for the same COM object
// it would be more confusing and less-performant if we were to merge them together
object target = removeMethod.Target;
if (target == null || Marshal.IsComObject(target))
NativeOrStaticEventRegistrationImpl.RemoveAllEventHandlers(removeMethod);
else
ManagedEventRegistrationImpl.RemoveAllEventHandlers(removeMethod);
}
// Returns the total cache size
// Used by test only to verify we don't leak event cache
internal static int GetRegistrationTokenCacheSize()
{
int count = 0;
if (ManagedEventRegistrationImpl.s_eventRegistrations != null)
{
lock (ManagedEventRegistrationImpl.s_eventRegistrations)
{
count += ManagedEventRegistrationImpl.s_eventRegistrations.Keys.Count;
}
}
if (NativeOrStaticEventRegistrationImpl.s_eventRegistrations != null)
{
lock (NativeOrStaticEventRegistrationImpl.s_eventRegistrations)
{
count += NativeOrStaticEventRegistrationImpl.s_eventRegistrations.Count;
}
}
return count;
}
//
// Optimized version of List of EventRegistrationToken
// It is made a struct to reduce overhead
//
internal struct EventRegistrationTokenList
{
private EventRegistrationToken firstToken; // Optimization for common case where there is only one token
private List<EventRegistrationToken> restTokens; // Rest of the tokens
internal EventRegistrationTokenList(EventRegistrationToken token)
{
firstToken = token;
restTokens = null;
}
internal EventRegistrationTokenList(EventRegistrationTokenList list)
{
firstToken = list.firstToken;
restTokens = list.restTokens;
}
// Push a new token into this list
// Returns true if you need to copy back this list into the dictionary (so that you
// don't lose change outside the dictionary). false otherwise.
public bool Push(EventRegistrationToken token)
{
bool needCopy = false;
if (restTokens == null)
{
restTokens = new List<EventRegistrationToken>();
needCopy = true;
}
restTokens.Add(token);
return needCopy;
}
// Pops the last token
// Returns false if no more tokens left, true otherwise
public bool Pop(out EventRegistrationToken token)
{
// Only 1 token in this list and we just removed the last token
if (restTokens == null || restTokens.Count == 0)
{
token = firstToken;
return false;
}
int last = restTokens.Count - 1;
token = restTokens[last];
restTokens.RemoveAt(last);
return true;
}
public void CopyTo(List<EventRegistrationToken> tokens)
{
tokens.Add(firstToken);
if (restTokens != null)
tokens.AddRange(restTokens);
}
}
//
// Event registration support for managed objects events & static events
//
internal static class ManagedEventRegistrationImpl
{
// Mappings of delegates registered for events -> their registration tokens.
// These mappings are stored indexed by the remove method which can be used to undo the registrations.
//
// The full structure of this table is:
// object the event is being registered on ->
// Table [RemoveMethod] ->
// Table [Handler] -> Token
//
// Note: There are a couple of optimizations I didn't do here because they don't make sense for managed events:
// 1. Flatten the event cache (see EventCacheKey in native WinRT event implementation below)
//
// This is because managed events use ConditionalWeakTable to hold Objects->(Event->(Handler->Tokens)),
// and when object goes away everything else will be nicely cleaned up. If I flatten it like native WinRT events,
// I'll have to use Dictionary (as ConditionalWeakTable won't work - nobody will hold the new key alive anymore)
// instead, and that means I'll have to add more code from native WinRT events into managed WinRT event to support
// self-cleanup in the finalization, as well as reader/writer lock to protect against ----s in the finalization,
// which adds a lot more complexity and doesn't really worth it.
//
// 2. Use conditionalWeakTable to hold Handler->Tokens.
//
// The reason is very simple - managed object use dictionary (see EventRegistrationTokenTable) to hold delegates alive.
// If the delegates aren't alive, it means either they have been unsubscribed, or the object itself is gone,
// and in either case, they've been already taken care of.
//
internal volatile static
ConditionalWeakTable<object, Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>>> s_eventRegistrations =
new ConditionalWeakTable<object, Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>>>();
[SecurityCritical]
internal static void AddEventHandler<T>(Func<T, EventRegistrationToken> addMethod,
Action<EventRegistrationToken> removeMethod,
T handler)
{
Contract.Requires(addMethod != null);
Contract.Requires(removeMethod != null);
// Add the method, and make a note of the token -> delegate mapping.
object instance = removeMethod.Target;
Dictionary<object, EventRegistrationTokenList> registrationTokens = GetEventRegistrationTokenTable(instance, removeMethod);
EventRegistrationToken token = addMethod(handler);
lock (registrationTokens)
{
EventRegistrationTokenList tokens;
if (!registrationTokens.TryGetValue(handler, out tokens))
{
tokens = new EventRegistrationTokenList(token);
registrationTokens[handler] = tokens;
}
else
{
bool needCopy = tokens.Push(token);
// You need to copy back this list into the dictionary (so that you don't lose change outside dictionary)
if (needCopy)
registrationTokens[handler] = tokens;
}
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event subscribed for managed instance = " + instance + ", handler = " + handler + "\n");
}
}
// Get the event registration token table for an event. These are indexed by the remove method of the event.
private static Dictionary<object, EventRegistrationTokenList> GetEventRegistrationTokenTable(object instance, Action<EventRegistrationToken> removeMethod)
{
Contract.Requires(instance != null);
Contract.Requires(removeMethod != null);
Contract.Requires(s_eventRegistrations != null);
lock (s_eventRegistrations)
{
Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>> instanceMap = null;
if (!s_eventRegistrations.TryGetValue(instance, out instanceMap))
{
instanceMap = new Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>>();
s_eventRegistrations.Add(instance, instanceMap);
}
Dictionary<object, EventRegistrationTokenList> tokens = null;
if (!instanceMap.TryGetValue(removeMethod.Method, out tokens))
{
tokens = new Dictionary<object, EventRegistrationTokenList>();
instanceMap.Add(removeMethod.Method, tokens);
}
return tokens;
}
}
[SecurityCritical]
internal static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler)
{
Contract.Requires(removeMethod != null);
object instance = removeMethod.Target;
Dictionary<object, EventRegistrationTokenList> registrationTokens = GetEventRegistrationTokenTable(instance, removeMethod);
EventRegistrationToken token;
lock (registrationTokens)
{
EventRegistrationTokenList tokens;
// Failure to find a registration for a token is not an error - it's simply a no-op.
if (!registrationTokens.TryGetValue(handler, out tokens))
{
BCLDebug.Log("INTEROP", "[WinRT_Eventing] no registrationTokens found for instance=" + instance + ", handler= " + handler + "\n");
return;
}
// Select a registration token to unregister
// We don't care which one but I'm returning the last registered token to be consistent
// with native event registration implementation
bool moreItems = tokens.Pop(out token);
if (!moreItems)
{
// Remove it from cache if this list become empty
// This must be done because EventRegistrationTokenList now becomes invalid
// (mostly because there is no safe default value for EventRegistrationToken to express 'no token')
// NOTE: We should try to remove registrationTokens itself from cache if it is empty, otherwise
// we could run into a race condition where one thread removes it from cache and another thread adds
// into the empty registrationToken table
registrationTokens.Remove(handler);
}
}
removeMethod(token);
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event unsubscribed for managed instance = " + instance + ", handler = " + handler + ", token = " + token.m_value + "\n");
}
[SecurityCritical]
internal static void RemoveAllEventHandlers(Action<EventRegistrationToken> removeMethod)
{
Contract.Requires(removeMethod != null);
object instance = removeMethod.Target;
Dictionary<object, EventRegistrationTokenList> registrationTokens = GetEventRegistrationTokenTable(instance, removeMethod);
List<EventRegistrationToken> tokensToRemove = new List<EventRegistrationToken>();
lock (registrationTokens)
{
// Copy all tokens to tokensToRemove array which later we'll call removeMethod on
// outside this lock
foreach (EventRegistrationTokenList tokens in registrationTokens.Values)
{
tokens.CopyTo(tokensToRemove);
}
// Clear the dictionary - at this point all event handlers are no longer in the cache
// but they are not removed yet
registrationTokens.Clear();
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Cache cleared for managed instance = " + instance + "\n");
}
//
// Remove all handlers outside the lock
//
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Start removing all events for instance = " + instance + "\n");
CallRemoveMethods(removeMethod, tokensToRemove);
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Finished removing all events for instance = " + instance + "\n");
}
}
//
// WinRT event registration implementation code
//
internal static class NativeOrStaticEventRegistrationImpl
{
//
// Key = (target object, event)
// We use a key of object+event to save an extra dictionary
//
internal struct EventCacheKey
{
internal object target;
internal MethodInfo method;
public override string ToString()
{
return "(" + target + ", " + method + ")";
}
}
internal class EventCacheKeyEqualityComparer : IEqualityComparer<EventCacheKey>
{
public bool Equals(EventCacheKey lhs, EventCacheKey rhs)
{
return (Object.Equals(lhs.target, rhs.target) && Object.Equals(lhs.method, rhs.method));
}
public int GetHashCode(EventCacheKey key)
{
return key.target.GetHashCode() ^ key.method.GetHashCode();
}
}
//
// EventRegistrationTokenListWithCount
//
// A list of EventRegistrationTokens that maintains a count
//
// The reason this needs to be a separate class is that we need a finalizer for this class
// If the delegate is collected, it will take this list away with it (due to dependent handles),
// and we need to remove the PerInstancEntry from cache
// See ~EventRegistrationTokenListWithCount for more details
//
internal class EventRegistrationTokenListWithCount
{
private TokenListCount _tokenListCount;
EventRegistrationTokenList _tokenList;
internal EventRegistrationTokenListWithCount(TokenListCount tokenListCount, EventRegistrationToken token)
{
_tokenListCount = tokenListCount;
_tokenListCount.Inc();
_tokenList = new EventRegistrationTokenList(token);
}
~EventRegistrationTokenListWithCount()
{
// Decrement token list count
// This is need to correctly keep trace of number of tokens for EventCacheKey
// and remove it from cache when the token count drop to 0
// we don't need to take locks for decrement the count - we only need to take a global
// lock when we decide to destroy cache for the IUnknown */type instance
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Finalizing EventRegistrationTokenList for " + _tokenListCount.Key + "\n");
_tokenListCount.Dec();
}
public void Push(EventRegistrationToken token)
{
// Since EventRegistrationTokenListWithCount is a reference type, there is no need
// to copy back. Ignore the return value
_tokenList.Push(token);
}
public bool Pop(out EventRegistrationToken token)
{
return _tokenList.Pop(out token);
}
public void CopyTo(List<EventRegistrationToken> tokens)
{
_tokenList.CopyTo(tokens);
}
}
//
// Maintains the number of tokens for a particular EventCacheKey
// TokenListCount is a class for two reasons:
// 1. Efficient update in the Dictionary to avoid lookup twice to update the value
// 2. Update token count without taking a global lock. Only takes a global lock when drop to 0
//
internal class TokenListCount
{
private int _count;
private EventCacheKey _key;
internal TokenListCount(EventCacheKey key)
{
_key = key;
}
internal EventCacheKey Key
{
get { return _key; }
}
internal void Inc()
{
int newCount = Interlocked.Increment(ref _count);
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Incremented TokenListCount for " + _key + ", Value = " + newCount + "\n");
}
internal void Dec()
{
// Avoid racing with Add/Remove event entries into the cache
// You don't want this removing the key in the middle of a Add/Remove
s_eventCacheRWLock.AcquireWriterLock(Timeout.Infinite);
try
{
int newCount = Interlocked.Decrement(ref _count);
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Decremented TokenListCount for " + _key + ", Value = " + newCount + "\n");
if (newCount == 0)
CleanupCache();
}
finally
{
s_eventCacheRWLock.ReleaseWriterLock();
}
}
private void CleanupCache()
{
// Time to destroy cache for this IUnknown */type instance
// because the total token list count has dropped to 0 and we don't have any events subscribed
Contract.Requires(s_eventRegistrations != null);
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Removing " + _key + " from cache" + "\n");
s_eventRegistrations.Remove(_key);
BCLDebug.Log("INTEROP", "[WinRT_Eventing] s_eventRegistrations size = " + s_eventRegistrations.Count + "\n");
}
}
internal struct EventCacheEntry
{
// [Handler] -> Token
internal ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTable;
// Maintains current total count for the EventRegistrationTokenListWithCount for this event cache key
internal TokenListCount tokenListCount;
}
// Mappings of delegates registered for events -> their registration tokens.
// These mappings are stored indexed by the remove method which can be used to undo the registrations.
//
// The full structure of this table is:
// EventCacheKey (instanceKey, eventMethod) -> EventCacheEntry (Handler->tokens)
//
// A InstanceKey is the IUnknown * or static type instance
//
// Couple of things to note:
// 1. We need to use IUnknown* because we want to be able to unscribe to the event for another RCW
// based on the same COM object. For example:
// m_canvas.GetAt(0).Event += Func;
// m_canvas.GetAt(0).Event -= Func; // GetAt(0) might create a new RCW
//
// 2. Handler->Token is a ConditionalWeakTable because we don't want to keep the delegate alive
// and we want EventRegistrationTokenListWithCount to be finalized after the delegate is no longer alive
// 3. It is possible another COM object is created at the same address
// before the entry in cache is destroyed. More specifically,
// a. The same delegate is being unsubscribed. In this case we'll give them a
// stale token - unlikely to be a problem
// b. The same delegate is subscribed then unsubscribed. We need to make sure give
// them the latest token in this case. This is guaranteed by always giving the last token and always use equality to
// add/remove event handlers
internal volatile static Dictionary<EventCacheKey, EventCacheEntry> s_eventRegistrations =
new Dictionary<EventCacheKey, EventCacheEntry>(new EventCacheKeyEqualityComparer());
// Prevent add/remove handler code to run at the same with with cache cleanup code
private volatile static MyReaderWriterLock s_eventCacheRWLock = new MyReaderWriterLock();
// Get InstanceKey to use in the cache
[SecuritySafeCritical]
private static object GetInstanceKey(Action<EventRegistrationToken> removeMethod)
{
object target = removeMethod.Target;
Contract.Assert(target == null || Marshal.IsComObject(target), "Must be null or a RCW");
if (target == null)
return removeMethod.Method.DeclaringType;
// Need the "Raw" IUnknown pointer for the RCW that is not bound to the current context
return (object) Marshal.GetRawIUnknownForComObjectNoAddRef(target);
}
[SecurityCritical]
internal static void AddEventHandler<T>(Func<T, EventRegistrationToken> addMethod,
Action<EventRegistrationToken> removeMethod,
T handler)
{
// The instanceKey will be IUnknown * of the target object
object instanceKey = GetInstanceKey(removeMethod);
// Call addMethod outside of RW lock
// At this point we don't need to worry about race conditions and we can avoid deadlocks
// if addMethod waits on finalizer thread
// If we later throw we need to remove the method
EventRegistrationToken token = addMethod(handler);
bool tokenAdded = false;
try
{
EventRegistrationTokenListWithCount tokens;
//
// The whole add/remove code has to be protected by a reader/writer lock
// Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
//
s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
try
{
// Add the method, and make a note of the delegate -> token mapping.
TokenListCount tokenListCount;
ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetOrCreateEventRegistrationTokenTable(instanceKey, removeMethod, out tokenListCount);
lock (registrationTokens)
{
//
// We need to find the key that equals to this handler
// Suppose we have 3 handlers A, B, C that are equal (refer to the same object and method),
// the first handler (let's say A) will be used as the key and holds all the tokens.
// We don't need to hold onto B and C, because the COM object itself will keep them alive,
// and they won't die anyway unless the COM object dies or they get unsubscribed.
// It may appear that it is fine to hold A, B, C, and add them and their corresponding tokens
// into registrationTokens table. However, this is very dangerous, because this COM object
// may die, but A, B, C might not get collected yet, and another COM object comes into life
// with the same IUnknown address, and we subscribe event B. In this case, the right token
// will be added into B's token list, but once we unsubscribe B, we might end up removing
// the last token in C, and that may lead to crash.
//
object key = registrationTokens.FindEquivalentKeyUnsafe(handler, out tokens);
if (key == null)
{
tokens = new EventRegistrationTokenListWithCount(tokenListCount, token);
registrationTokens.Add(handler, tokens);
}
else
{
tokens.Push(token);
}
tokenAdded = true;
}
}
finally
{
s_eventCacheRWLock.ReleaseReaderLock();
}
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event subscribed for instance = " + instanceKey + ", handler = " + handler + "\n");
}
catch(Exception)
{
// If we've already added the token and go there, we don't need to "UNDO" anything
if (!tokenAdded)
{
// Otherwise, "Undo" addMethod if any exception occurs
// There is no need to cleanup our data structure as we haven't added the token yet
removeMethod(token);
}
throw;
}
}
private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetEventRegistrationTokenTableNoCreate(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount)
{
Contract.Requires(instance != null);
Contract.Requires(removeMethod != null);
return GetEventRegistrationTokenTableInternal(instance, removeMethod, out tokenListCount, /* createIfNotFound = */ false);
}
private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetOrCreateEventRegistrationTokenTable(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount)
{
Contract.Requires(instance != null);
Contract.Requires(removeMethod != null);
return GetEventRegistrationTokenTableInternal(instance, removeMethod, out tokenListCount, /* createIfNotFound = */ true);
}
// Get the event registration token table for an event. These are indexed by the remove method of the event.
private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetEventRegistrationTokenTableInternal(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount, bool createIfNotFound)
{
Contract.Requires(instance != null);
Contract.Requires(removeMethod != null);
Contract.Requires(s_eventRegistrations != null);
EventCacheKey eventCacheKey;
eventCacheKey.target = instance;
eventCacheKey.method = removeMethod.Method;
lock (s_eventRegistrations)
{
EventCacheEntry eventCacheEntry;
if (!s_eventRegistrations.TryGetValue(eventCacheKey, out eventCacheEntry))
{
if (!createIfNotFound)
{
// No need to create an entry in this case
tokenListCount = null;
return null;
}
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Adding (" + instance + "," + removeMethod.Method + ") into cache" + "\n");
eventCacheEntry = new EventCacheEntry();
eventCacheEntry.registrationTable = new ConditionalWeakTable<object, EventRegistrationTokenListWithCount>();
eventCacheEntry.tokenListCount = new TokenListCount(eventCacheKey);
s_eventRegistrations.Add(eventCacheKey, eventCacheEntry);
}
tokenListCount = eventCacheEntry.tokenListCount;
return eventCacheEntry.registrationTable;
}
}
[SecurityCritical]
internal static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler)
{
object instanceKey = GetInstanceKey(removeMethod);
EventRegistrationToken token;
//
// The whole add/remove code has to be protected by a reader/writer lock
// Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
//
s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
try
{
TokenListCount tokenListCount;
ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetEventRegistrationTokenTableNoCreate(instanceKey, removeMethod, out tokenListCount);
if (registrationTokens == null)
{
// We have no information regarding this particular instance (IUnknown*/type) - just return
// This is necessary to avoid leaking empty dictionary/conditionalWeakTables for this instance
BCLDebug.Log("INTEROP", "[WinRT_Eventing] no registrationTokens found for instance=" + instanceKey + ", handler= " + handler + "\n");
return;
}
lock (registrationTokens)
{
EventRegistrationTokenListWithCount tokens;
// Note:
// When unsubscribing events, we allow subscribing the event using a different delegate
// (but with the same object/method), so we need to find the first delegate that matches
// and unsubscribe it
// It actually doesn't matter which delegate - as long as it matches
// Note that inside TryGetValueWithValueEquality we assumes that any delegate
// with the same value equality would have the same hash code
object key = registrationTokens.FindEquivalentKeyUnsafe(handler, out tokens);
Contract.Assert((key != null && tokens != null) || (key == null && tokens == null),
"key and tokens must be both null or non-null");
if (tokens == null)
{
// Failure to find a registration for a token is not an error - it's simply a no-op.
BCLDebug.Log("INTEROP", "[WinRT_Eventing] no token list found for instance=" + instanceKey + ", handler= " + handler + "\n");
return;
}
// Select a registration token to unregister
// Note that we need to always get the last token just in case another COM object
// is created at the same address before the entry for the old one goes away.
// See comments above s_eventRegistrations for more details
bool moreItems = tokens.Pop(out token);
// If the last token is removed from token list, we need to remove it from the cache
// otherwise FindEquivalentKeyUnsafe may found this empty token list even though there could be other
// equivalent keys in there with non-0 token list
if (!moreItems)
{
// Remove it from (handler)->(tokens)
// NOTE: We should not check whether registrationTokens has 0 entries and remove it from the cache
// (just like managed event implementation), because this might ---- with the finalizer of
// EventRegistrationTokenList
registrationTokens.Remove(key);
}
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event unsubscribed for managed instance = " + instanceKey + ", handler = " + handler + ", token = " + token.m_value + "\n");
}
}
finally
{
s_eventCacheRWLock.ReleaseReaderLock();
}
// Call removeMethod outside of RW lock
// At this point we don't need to worry about race conditions and we can avoid deadlocks
// if removeMethod waits on finalizer thread
removeMethod(token);
}
[SecurityCritical]
internal static void RemoveAllEventHandlers(Action<EventRegistrationToken> removeMethod)
{
object instanceKey = GetInstanceKey(removeMethod);
List<EventRegistrationToken> tokensToRemove = new List<EventRegistrationToken>();
//
// The whole add/remove code has to be protected by a reader/writer lock
// Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
//
s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
try
{
TokenListCount tokenListCount;
ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetEventRegistrationTokenTableNoCreate(instanceKey, removeMethod, out tokenListCount);
if (registrationTokens == null)
{
// We have no information regarding this particular instance (IUnknown*/type) - just return
// This is necessary to avoid leaking empty dictionary/conditionalWeakTables for this instance
return;
}
lock (registrationTokens)
{
// Copy all tokens to tokensToRemove array which later we'll call removeMethod on
// outside this lock
foreach (EventRegistrationTokenListWithCount tokens in registrationTokens.Values)
{
tokens.CopyTo(tokensToRemove);
}
// Clear the table - at this point all event handlers are no longer in the cache
// but they are not removed yet
registrationTokens.Clear();
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Cache cleared for managed instance = " + instanceKey + "\n");
}
}
finally
{
s_eventCacheRWLock.ReleaseReaderLock();
}
//
// Remove all handlers outside the lock
//
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Start removing all events for instance = " + instanceKey + "\n");
CallRemoveMethods(removeMethod, tokensToRemove);
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Finished removing all events for instance = " + instanceKey + "\n");
}
internal class ReaderWriterLockTimedOutException : ApplicationException
{
}
/// I borrowed Vance's reader writer lock implementation from his blog as ReaderWriterLockSlim is
/// available in System.Core.dll!
///
/// <summary>
/// A reader-writer lock implementation that is intended to be simple, yet very
/// efficient. In particular only 1 interlocked operation is taken for any lock
/// operation (we use spin locks to achieve this). The spin lock is never held
/// for more than a few instructions (in particular, we never call event APIs
/// or in fact any non-trivial API while holding the spin lock).
///
/// Currently this ReaderWriterLock does not support recurision, however it is
/// not hard to add
/// </summary>
internal class MyReaderWriterLock
{
// Lock specifiation for myLock: This lock protects exactly the local fields associted
// instance of MyReaderWriterLock. It does NOT protect the memory associted with the
// the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
int myLock;
// Who owns the lock owners > 0 => readers
// owners = -1 means there is one writer. Owners must be >= -1.
int owners;
// These variables allow use to avoid Setting events (which is expensive) if we don't have to.
uint numWriteWaiters; // maximum number of threads that can be doing a WaitOne on the writeEvent
uint numReadWaiters; // maximum number of threads that can be doing a WaitOne on the readEvent
// conditions we wait on.
EventWaitHandle writeEvent; // threads waiting to aquire a write lock go here.
EventWaitHandle readEvent; // threads waiting to aquire a read lock go here (will be released in bulk)
internal MyReaderWriterLock()
{
// All state can start out zeroed.
}
internal void AcquireReaderLock(int millisecondsTimeout)
{
EnterMyLock();
for (; ; )
{
// We can enter a read lock if there are only read-locks have been given out
// and a writer is not trying to get in.
if (owners >= 0 && numWriteWaiters == 0)
{
// Good case, there is no contention, we are basically done
owners++; // Indicate we have another reader
break;
}
// Drat, we need to wait. Mark that we have waiters and wait.
if (readEvent == null) // Create the needed event
{
LazyCreateEvent(ref readEvent, false);
continue; // since we left the lock, start over.
}
WaitOnEvent(readEvent, ref numReadWaiters, millisecondsTimeout);
}
ExitMyLock();
}
internal void AcquireWriterLock(int millisecondsTimeout)
{
EnterMyLock();
for (; ; )
{
if (owners == 0)
{
// Good case, there is no contention, we are basically done
owners = -1; // indicate we have a writer.
break;
}
// Drat, we need to wait. Mark that we have waiters and wait.
if (writeEvent == null) // create the needed event.
{
LazyCreateEvent(ref writeEvent, true);
continue; // since we left the lock, start over.
}
WaitOnEvent(writeEvent, ref numWriteWaiters, millisecondsTimeout);
}
ExitMyLock();
}
internal void ReleaseReaderLock()
{
EnterMyLock();
Contract.Assert(owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
--owners;
ExitAndWakeUpAppropriateWaiters();
}
internal void ReleaseWriterLock()
{
EnterMyLock();
Contract.Assert(owners == -1, "Calling ReleaseWriterLock when no write lock is held");
owners++;
ExitAndWakeUpAppropriateWaiters();
}
/// <summary>
/// A routine for lazily creating a event outside the lock (so if errors
/// happen they are outside the lock and that we don't do much work
/// while holding a spin lock). If all goes well, reenter the lock and
/// set 'waitEvent'
/// </summary>
private void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent) {
Contract.Assert(myLock != 0, "Lock must be held");
Contract.Assert(waitEvent == null, "Wait event must be null");
ExitMyLock();
EventWaitHandle newEvent;
if (makeAutoResetEvent)
newEvent = new AutoResetEvent(false);
else
newEvent = new ManualResetEvent(false);
EnterMyLock();
if (waitEvent == null) // maybe someone snuck in.
waitEvent = newEvent;
}
/// <summary>
/// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.
/// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
/// </summary>
private void WaitOnEvent(EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
{
Contract.Assert(myLock != 0, "Lock must be held");
waitEvent.Reset();
numWaiters++;
bool waitSuccessful = false;
ExitMyLock(); // Do the wait outside of any lock
try
{
if (!waitEvent.WaitOne(millisecondsTimeout, false))
throw new ReaderWriterLockTimedOutException();
waitSuccessful = true;
}
finally
{
EnterMyLock();
--numWaiters;
if (!waitSuccessful) // We are going to throw for some reason. Exit myLock.
ExitMyLock();
}
}
/// <summary>
/// Determines the appropriate events to set, leaves the locks, and sets the events.
/// </summary>
private void ExitAndWakeUpAppropriateWaiters()
{
Contract.Assert(myLock != 0, "Lock must be held");
if (owners == 0 && numWriteWaiters > 0)
{
ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock)
writeEvent.Set(); // release one writer.
}
else if (owners >= 0 && numReadWaiters != 0)
{
ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock)
readEvent.Set(); // release all readers.
}
else
ExitMyLock();
}
private void EnterMyLock() {
if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
EnterMyLockSpin();
}
private void EnterMyLockSpin()
{
for (int i = 0; ;i++)
{
if (i < 3 && Environment.ProcessorCount > 1)
Thread.SpinWait(20); // Wait a few dozen instructions to let another processor release lock.
else
Thread.Sleep(0); // Give up my quantum.
if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
return;
}
}
private void ExitMyLock()
{
Contract.Assert(myLock != 0, "Exiting spin lock that is not held");
myLock = 0;
}
};
}
//
// Call removeMethod on each token and aggregate all exceptions thrown from removeMethod into one in case of failure
//
internal static void CallRemoveMethods(Action<EventRegistrationToken> removeMethod, List<EventRegistrationToken> tokensToRemove)
{
List<Exception> exceptions = new List<Exception>();
foreach (EventRegistrationToken token in tokensToRemove)
{
try
{
removeMethod(token);
}
catch(Exception ex)
{
exceptions.Add(ex);
}
BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event unsubscribed for token = " + token.m_value + "\n");
}
if (exceptions.Count > 0)
throw new AggregateException(exceptions.ToArray());
}
[SecurityCritical]
internal static unsafe string HStringToString(IntPtr hstring)
{
Contract.Requires(Environment.IsWinRTSupported);
// There is no difference between a null and empty HSTRING
if (hstring == IntPtr.Zero)
{
return String.Empty;
}
unsafe
{
uint length;
char* rawBuffer = UnsafeNativeMethods.WindowsGetStringRawBuffer(hstring, &length);
return new String(rawBuffer, 0, checked((int)length));
}
}
internal static Exception GetExceptionForHR(int hresult, Exception innerException, string messageResource)
{
Exception e = null;
if (innerException != null)
{
string message = innerException.Message;
if (message == null && messageResource != null)
{
message = Environment.GetResourceString(messageResource);
}
e = new Exception(message, innerException);
}
else
{
string message = (messageResource != null ? Environment.GetResourceString(messageResource) : null);
e = new Exception(message);
}
e.SetErrorCode(hresult);
return e;
}
internal static Exception GetExceptionForHR(int hresult, Exception innerException)
{
return GetExceptionForHR(hresult, innerException, null);
}
private static bool s_haveBlueErrorApis = true;
[SecurityCritical]
private static bool RoOriginateLanguageException(int error, string message, IntPtr languageException)
{
if (s_haveBlueErrorApis)
{
try
{
return UnsafeNativeMethods.RoOriginateLanguageException(error, message, languageException);
}
catch (EntryPointNotFoundException)
{
s_haveBlueErrorApis = false;
}
}
return false;
}
[SecurityCritical]
private static void RoReportUnhandledError(IRestrictedErrorInfo error)
{
if (s_haveBlueErrorApis)
{
try
{
UnsafeNativeMethods.RoReportUnhandledError(error);
}
catch (EntryPointNotFoundException)
{
s_haveBlueErrorApis = false;
}
}
}
private static Guid s_iidIErrorInfo = new Guid(0x1CF2B120, 0x547D, 0x101B, 0x8E, 0x65, 0x08, 0x00, 0x2B, 0x2B, 0xD1, 0x19);
/// <summary>
/// Report that an exception has occured which went user unhandled. This allows the global error handler
/// for the application to be invoked to process the error.
/// </summary>
/// <returns>true if the error was reported, false if not (ie running on Win8)</returns>
[FriendAccessAllowed]
[SecuritySafeCritical]
internal static bool ReportUnhandledError(Exception e)
{
// Only report to the WinRT global exception handler in modern apps
if (!AppDomain.IsAppXModel())
{
return false;
}
// If we don't have the capability to report to the global error handler, early out
if (!s_haveBlueErrorApis)
{
return false;
}
if (e != null)
{
IntPtr exceptionIUnknown = IntPtr.Zero;
IntPtr exceptionIErrorInfo = IntPtr.Zero;
try
{
// Get an IErrorInfo for the current exception and originate it as a langauge error in order to have
// Windows generate an IRestrictedErrorInfo corresponding to the exception object. We can then
// notify the global error handler that this IRestrictedErrorInfo instance represents an exception that
// went unhandled in managed code.
//
// Note that we need to get an IUnknown for the exception object and then QI for IErrorInfo since Exception
// doesn't implement IErrorInfo in managed code - only its CCW does.
exceptionIUnknown = Marshal.GetIUnknownForObject(e);
if (exceptionIUnknown != IntPtr.Zero)
{
Marshal.QueryInterface(exceptionIUnknown, ref s_iidIErrorInfo, out exceptionIErrorInfo);
if (exceptionIErrorInfo != IntPtr.Zero)
{
if (RoOriginateLanguageException(Marshal.GetHRForException_WinRT(e), e.Message, exceptionIErrorInfo))
{
IRestrictedErrorInfo restrictedError = UnsafeNativeMethods.GetRestrictedErrorInfo();
if (restrictedError != null)
{
RoReportUnhandledError(restrictedError);
return true;
}
}
}
}
}
finally
{
if (exceptionIErrorInfo != IntPtr.Zero)
{
Marshal.Release(exceptionIErrorInfo);
}
if (exceptionIUnknown != IntPtr.Zero)
{
Marshal.Release(exceptionIUnknown);
}
}
}
// If we got here, then some step of the marshaling failed, which means the GEH was not invoked
return false;
}
#if FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
// Get an IActivationFactory * for a managed type
[SecurityCritical]
internal static IntPtr GetActivationFactoryForType(Type type)
{
ManagedActivationFactory activationFactory = GetManagedActivationFactory(type);
return Marshal.GetComInterfaceForObject(activationFactory, typeof(IActivationFactory));
}
[SecurityCritical]
internal static ManagedActivationFactory GetManagedActivationFactory(Type type)
{
ManagedActivationFactory activationFactory = new ManagedActivationFactory(type);
// If the type has any associated factory interfaces (i.e. supports non-default activation
// or has statics), the CCW for this instance of ManagedActivationFactory must support them.
Marshal.InitializeManagedWinRTFactoryObject(activationFactory, (RuntimeType)type);
return activationFactory;
}
#if FEATURE_COMINTEROP_WINRT_DESKTOP_HOST
// Currently we use only a single class activator since we have a requirement that all class activations come from the same
// app base and we haven't sorted through the various code sharing implications of spinning up multiple AppDomains. This
// holds the IWinRTClassActivator* that is used for the process
private static IntPtr s_pClassActivator = IntPtr.Zero;
[SecurityCritical]
internal static IntPtr GetClassActivatorForApplication(string appBase)
{
if (s_pClassActivator == IntPtr.Zero)
{
AppDomainSetup hostDomainSetup = new AppDomainSetup()
{
ApplicationBase = appBase,
};
AppDomain hostDomain = AppDomain.CreateDomain(Environment.GetResourceString("WinRTHostDomainName", appBase), null, hostDomainSetup);
WinRTClassActivator activator = (WinRTClassActivator)hostDomain.CreateInstanceAndUnwrap(typeof(WinRTClassActivator).Assembly.FullName, typeof(WinRTClassActivator).FullName);
IntPtr pActivator = activator.GetIWinRTClassActivator();
if (Interlocked.CompareExchange(ref s_pClassActivator, pActivator, IntPtr.Zero) != IntPtr.Zero)
{
Marshal.Release(pActivator);
activator = null;
try
{
AppDomain.Unload(hostDomain);
}
catch (CannotUnloadAppDomainException) { }
}
}
Marshal.AddRef(s_pClassActivator);
return s_pClassActivator;
}
#endif // FEATURE_COMINTEROP_WINRT_DESKTOP_HOST
#endif // FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
//
// Get activation factory object for a specified WinRT type
// If the WinRT type is a native type, we'll always create a unique RCW for it,
// This is necessary because WinRT factories are often implemented as a singleton,
// and getting back a RCW for such WinRT factory would usually get back a RCW from
// another apartment, even if the interface pointe returned from GetActivationFactory
// is a raw pointer. As a result, user would randomly get back RCWs for activation
// factories from other apartments and make transiton to those apartments and cause
// deadlocks and create objects in incorrect apartments
//
[SecurityCritical]
public static IActivationFactory GetActivationFactory(Type type)
{
if (type == null)
throw new ArgumentNullException("type");
if (type.IsWindowsRuntimeObject && type.IsImport)
{
return (IActivationFactory)Marshal.GetNativeActivationFactory(type);
}
else
{
#if FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
return GetManagedActivationFactory(type);
#else
// Managed factories are not supported so as to minimize public surface (and test effort)
throw new NotSupportedException();
#endif
}
}
// HSTRING marshaling methods:
[SecurityCritical]
public static IntPtr StringToHString(String s)
{
if (!Environment.IsWinRTSupported)
throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_WinRT"));
if (s == null)
throw new ArgumentNullException("s");
unsafe
{
IntPtr hstring;
int hrCreate = UnsafeNativeMethods.WindowsCreateString(s, s.Length, &hstring);
Marshal.ThrowExceptionForHR(hrCreate, new IntPtr(-1));
return hstring;
}
}
[SecurityCritical]
public static String PtrToStringHString(IntPtr ptr)
{
if (!Environment.IsWinRTSupported)
{
throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_WinRT"));
}
return HStringToString(ptr);
}
[SecurityCritical]
public static void FreeHString(IntPtr ptr)
{
if (!Environment.IsWinRTSupported)
throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_WinRT"));
if (ptr != IntPtr.Zero)
{
UnsafeNativeMethods.WindowsDeleteString(ptr);
}
}
}
}
|