3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // <OWNER>[....]</OWNER>
8 // <OWNER>[....]</OWNER>
9 // <OWNER>[....]</OWNER>
12 using System.Collections.Generic;
13 using System.Diagnostics.Contracts;
14 using System.Reflection;
15 using System.Runtime.CompilerServices;
16 using System.Runtime.InteropServices;
17 using System.Threading;
18 using System.Security;
20 namespace System.Runtime.InteropServices.WindowsRuntime
22 // Helper functions to manually marshal data between .NET and WinRT
23 public static class WindowsRuntimeMarshal
25 // Add an event handler to a Windows Runtime style event, such that it can be removed via a delegate
26 // lookup at a later time. This method adds the handler to the add method using the supplied
27 // delegate. It then stores the corresponding token in a dictionary for easy access by RemoveEventHandler
28 // later. Note that the dictionary is indexed by the remove method that will be used for RemoveEventHandler
29 // so the removeMethod given here must match the remove method supplied there exactly.
31 public static void AddEventHandler<T>(Func<T, EventRegistrationToken> addMethod,
32 Action<EventRegistrationToken> removeMethod,
35 if (addMethod == null)
36 throw new ArgumentNullException("addMethod");
37 if (removeMethod == null)
38 throw new ArgumentNullException("removeMethod");
39 Contract.EndContractBlock();
41 // Managed code allows adding a null event handler, the effect is a no-op. To match this behavior
42 // for WinRT events, we simply ignore attempts to add null.
48 // Delegate to managed event registration implementation or native event registration implementation
49 // They have completely different implementation because native side has its own unique problem to solve -
50 // there could be more than one RCW for the same COM object
51 // it would be more confusing and less-performant if we were to merge them together
52 object target = removeMethod.Target;
53 if (target == null || Marshal.IsComObject(target))
54 NativeOrStaticEventRegistrationImpl.AddEventHandler<T>(addMethod, removeMethod, handler);
56 ManagedEventRegistrationImpl.AddEventHandler<T>(addMethod, removeMethod, handler);
59 // Remove the delegate handler from the Windows Runtime style event registration by looking for
60 // its token, previously stored via AddEventHandler<T>
62 public static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler)
64 if (removeMethod == null)
65 throw new ArgumentNullException("removeMethod");
66 Contract.EndContractBlock();
68 // Managed code allows removing a null event handler, the effect is a no-op. To match this behavior
69 // for WinRT events, we simply ignore attempts to remove null.
75 // Delegate to managed event registration implementation or native event registration implementation
76 // They have completely different implementation because native side has its own unique problem to solve -
77 // there could be more than one RCW for the same COM object
78 // it would be more confusing and less-performant if we were to merge them together
79 object target = removeMethod.Target;
80 if (target == null || Marshal.IsComObject(target))
81 NativeOrStaticEventRegistrationImpl.RemoveEventHandler<T>(removeMethod, handler);
83 ManagedEventRegistrationImpl.RemoveEventHandler<T>(removeMethod, handler);
87 public static void RemoveAllEventHandlers(Action<EventRegistrationToken> removeMethod)
89 if (removeMethod == null)
90 throw new ArgumentNullException("removeMethod");
91 Contract.EndContractBlock();
93 // Delegate to managed event registration implementation or native event registration implementation
94 // They have completely different implementation because native side has its own unique problem to solve -
95 // there could be more than one RCW for the same COM object
96 // it would be more confusing and less-performant if we were to merge them together
97 object target = removeMethod.Target;
98 if (target == null || Marshal.IsComObject(target))
99 NativeOrStaticEventRegistrationImpl.RemoveAllEventHandlers(removeMethod);
101 ManagedEventRegistrationImpl.RemoveAllEventHandlers(removeMethod);
104 // Returns the total cache size
105 // Used by test only to verify we don't leak event cache
106 internal static int GetRegistrationTokenCacheSize()
110 if (ManagedEventRegistrationImpl.s_eventRegistrations != null)
112 lock (ManagedEventRegistrationImpl.s_eventRegistrations)
114 count += ManagedEventRegistrationImpl.s_eventRegistrations.Keys.Count;
118 if (NativeOrStaticEventRegistrationImpl.s_eventRegistrations != null)
120 lock (NativeOrStaticEventRegistrationImpl.s_eventRegistrations)
122 count += NativeOrStaticEventRegistrationImpl.s_eventRegistrations.Count;
130 // Optimized version of List of EventRegistrationToken
131 // It is made a struct to reduce overhead
133 internal struct EventRegistrationTokenList
135 private EventRegistrationToken firstToken; // Optimization for common case where there is only one token
136 private List<EventRegistrationToken> restTokens; // Rest of the tokens
138 internal EventRegistrationTokenList(EventRegistrationToken token)
144 internal EventRegistrationTokenList(EventRegistrationTokenList list)
146 firstToken = list.firstToken;
147 restTokens = list.restTokens;
150 // Push a new token into this list
151 // Returns true if you need to copy back this list into the dictionary (so that you
152 // don't lose change outside the dictionary). false otherwise.
153 public bool Push(EventRegistrationToken token)
155 bool needCopy = false;
157 if (restTokens == null)
159 restTokens = new List<EventRegistrationToken>();
163 restTokens.Add(token);
168 // Pops the last token
169 // Returns false if no more tokens left, true otherwise
170 public bool Pop(out EventRegistrationToken token)
172 // Only 1 token in this list and we just removed the last token
173 if (restTokens == null || restTokens.Count == 0)
179 int last = restTokens.Count - 1;
180 token = restTokens[last];
181 restTokens.RemoveAt(last);
186 public void CopyTo(List<EventRegistrationToken> tokens)
188 tokens.Add(firstToken);
189 if (restTokens != null)
190 tokens.AddRange(restTokens);
195 // Event registration support for managed objects events & static events
197 internal static class ManagedEventRegistrationImpl
199 // Mappings of delegates registered for events -> their registration tokens.
200 // These mappings are stored indexed by the remove method which can be used to undo the registrations.
202 // The full structure of this table is:
203 // object the event is being registered on ->
204 // Table [RemoveMethod] ->
205 // Table [Handler] -> Token
207 // Note: There are a couple of optimizations I didn't do here because they don't make sense for managed events:
208 // 1. Flatten the event cache (see EventCacheKey in native WinRT event implementation below)
210 // This is because managed events use ConditionalWeakTable to hold Objects->(Event->(Handler->Tokens)),
211 // and when object goes away everything else will be nicely cleaned up. If I flatten it like native WinRT events,
212 // I'll have to use Dictionary (as ConditionalWeakTable won't work - nobody will hold the new key alive anymore)
213 // instead, and that means I'll have to add more code from native WinRT events into managed WinRT event to support
214 // self-cleanup in the finalization, as well as reader/writer lock to protect against ----s in the finalization,
215 // which adds a lot more complexity and doesn't really worth it.
217 // 2. Use conditionalWeakTable to hold Handler->Tokens.
219 // The reason is very simple - managed object use dictionary (see EventRegistrationTokenTable) to hold delegates alive.
220 // If the delegates aren't alive, it means either they have been unsubscribed, or the object itself is gone,
221 // and in either case, they've been already taken care of.
223 internal volatile static
224 ConditionalWeakTable<object, Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>>> s_eventRegistrations =
225 new ConditionalWeakTable<object, Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>>>();
228 internal static void AddEventHandler<T>(Func<T, EventRegistrationToken> addMethod,
229 Action<EventRegistrationToken> removeMethod,
232 Contract.Requires(addMethod != null);
233 Contract.Requires(removeMethod != null);
235 // Add the method, and make a note of the token -> delegate mapping.
236 object instance = removeMethod.Target;
237 Dictionary<object, EventRegistrationTokenList> registrationTokens = GetEventRegistrationTokenTable(instance, removeMethod);
238 EventRegistrationToken token = addMethod(handler);
239 lock (registrationTokens)
241 EventRegistrationTokenList tokens;
242 if (!registrationTokens.TryGetValue(handler, out tokens))
244 tokens = new EventRegistrationTokenList(token);
245 registrationTokens[handler] = tokens;
249 bool needCopy = tokens.Push(token);
251 // You need to copy back this list into the dictionary (so that you don't lose change outside dictionary)
253 registrationTokens[handler] = tokens;
256 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event subscribed for managed instance = " + instance + ", handler = " + handler + "\n");
260 // Get the event registration token table for an event. These are indexed by the remove method of the event.
261 private static Dictionary<object, EventRegistrationTokenList> GetEventRegistrationTokenTable(object instance, Action<EventRegistrationToken> removeMethod)
263 Contract.Requires(instance != null);
264 Contract.Requires(removeMethod != null);
265 Contract.Requires(s_eventRegistrations != null);
267 lock (s_eventRegistrations)
269 Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>> instanceMap = null;
270 if (!s_eventRegistrations.TryGetValue(instance, out instanceMap))
272 instanceMap = new Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>>();
273 s_eventRegistrations.Add(instance, instanceMap);
276 Dictionary<object, EventRegistrationTokenList> tokens = null;
277 if (!instanceMap.TryGetValue(removeMethod.Method, out tokens))
279 tokens = new Dictionary<object, EventRegistrationTokenList>();
280 instanceMap.Add(removeMethod.Method, tokens);
288 internal static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler)
290 Contract.Requires(removeMethod != null);
292 object instance = removeMethod.Target;
293 Dictionary<object, EventRegistrationTokenList> registrationTokens = GetEventRegistrationTokenTable(instance, removeMethod);
294 EventRegistrationToken token;
296 lock (registrationTokens)
298 EventRegistrationTokenList tokens;
300 // Failure to find a registration for a token is not an error - it's simply a no-op.
301 if (!registrationTokens.TryGetValue(handler, out tokens))
303 BCLDebug.Log("INTEROP", "[WinRT_Eventing] no registrationTokens found for instance=" + instance + ", handler= " + handler + "\n");
308 // Select a registration token to unregister
309 // We don't care which one but I'm returning the last registered token to be consistent
310 // with native event registration implementation
311 bool moreItems = tokens.Pop(out token);
314 // Remove it from cache if this list become empty
315 // This must be done because EventRegistrationTokenList now becomes invalid
316 // (mostly because there is no safe default value for EventRegistrationToken to express 'no token')
317 // NOTE: We should try to remove registrationTokens itself from cache if it is empty, otherwise
318 // we could run into a race condition where one thread removes it from cache and another thread adds
319 // into the empty registrationToken table
320 registrationTokens.Remove(handler);
326 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event unsubscribed for managed instance = " + instance + ", handler = " + handler + ", token = " + token.m_value + "\n");
330 internal static void RemoveAllEventHandlers(Action<EventRegistrationToken> removeMethod)
332 Contract.Requires(removeMethod != null);
334 object instance = removeMethod.Target;
335 Dictionary<object, EventRegistrationTokenList> registrationTokens = GetEventRegistrationTokenTable(instance, removeMethod);
337 List<EventRegistrationToken> tokensToRemove = new List<EventRegistrationToken>();
339 lock (registrationTokens)
341 // Copy all tokens to tokensToRemove array which later we'll call removeMethod on
343 foreach (EventRegistrationTokenList tokens in registrationTokens.Values)
345 tokens.CopyTo(tokensToRemove);
348 // Clear the dictionary - at this point all event handlers are no longer in the cache
349 // but they are not removed yet
350 registrationTokens.Clear();
351 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Cache cleared for managed instance = " + instance + "\n");
355 // Remove all handlers outside the lock
357 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Start removing all events for instance = " + instance + "\n");
358 CallRemoveMethods(removeMethod, tokensToRemove);
359 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Finished removing all events for instance = " + instance + "\n");
364 // WinRT event registration implementation code
366 internal static class NativeOrStaticEventRegistrationImpl
369 // Key = (target object, event)
370 // We use a key of object+event to save an extra dictionary
372 internal struct EventCacheKey
374 internal object target;
375 internal MethodInfo method;
377 public override string ToString()
379 return "(" + target + ", " + method + ")";
383 internal class EventCacheKeyEqualityComparer : IEqualityComparer<EventCacheKey>
385 public bool Equals(EventCacheKey lhs, EventCacheKey rhs)
387 return (Object.Equals(lhs.target, rhs.target) && Object.Equals(lhs.method, rhs.method));
390 public int GetHashCode(EventCacheKey key)
392 return key.target.GetHashCode() ^ key.method.GetHashCode();
397 // EventRegistrationTokenListWithCount
399 // A list of EventRegistrationTokens that maintains a count
401 // The reason this needs to be a separate class is that we need a finalizer for this class
402 // If the delegate is collected, it will take this list away with it (due to dependent handles),
403 // and we need to remove the PerInstancEntry from cache
404 // See ~EventRegistrationTokenListWithCount for more details
406 internal class EventRegistrationTokenListWithCount
408 private TokenListCount _tokenListCount;
409 EventRegistrationTokenList _tokenList;
411 internal EventRegistrationTokenListWithCount(TokenListCount tokenListCount, EventRegistrationToken token)
413 _tokenListCount = tokenListCount;
414 _tokenListCount.Inc();
416 _tokenList = new EventRegistrationTokenList(token);
419 ~EventRegistrationTokenListWithCount()
421 // Decrement token list count
422 // This is need to correctly keep trace of number of tokens for EventCacheKey
423 // and remove it from cache when the token count drop to 0
424 // we don't need to take locks for decrement the count - we only need to take a global
425 // lock when we decide to destroy cache for the IUnknown */type instance
426 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Finalizing EventRegistrationTokenList for " + _tokenListCount.Key + "\n");
427 _tokenListCount.Dec();
430 public void Push(EventRegistrationToken token)
432 // Since EventRegistrationTokenListWithCount is a reference type, there is no need
433 // to copy back. Ignore the return value
434 _tokenList.Push(token);
437 public bool Pop(out EventRegistrationToken token)
439 return _tokenList.Pop(out token);
442 public void CopyTo(List<EventRegistrationToken> tokens)
444 _tokenList.CopyTo(tokens);
449 // Maintains the number of tokens for a particular EventCacheKey
450 // TokenListCount is a class for two reasons:
451 // 1. Efficient update in the Dictionary to avoid lookup twice to update the value
452 // 2. Update token count without taking a global lock. Only takes a global lock when drop to 0
454 internal class TokenListCount
457 private EventCacheKey _key;
459 internal TokenListCount(EventCacheKey key)
464 internal EventCacheKey Key
472 int newCount = Interlocked.Increment(ref _count);
473 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Incremented TokenListCount for " + _key + ", Value = " + newCount + "\n");
478 // Avoid racing with Add/Remove event entries into the cache
479 // You don't want this removing the key in the middle of a Add/Remove
480 s_eventCacheRWLock.AcquireWriterLock(Timeout.Infinite);
483 int newCount = Interlocked.Decrement(ref _count);
484 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Decremented TokenListCount for " + _key + ", Value = " + newCount + "\n");
490 s_eventCacheRWLock.ReleaseWriterLock();
494 private void CleanupCache()
496 // Time to destroy cache for this IUnknown */type instance
497 // because the total token list count has dropped to 0 and we don't have any events subscribed
498 Contract.Requires(s_eventRegistrations != null);
500 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Removing " + _key + " from cache" + "\n");
501 s_eventRegistrations.Remove(_key);
502 BCLDebug.Log("INTEROP", "[WinRT_Eventing] s_eventRegistrations size = " + s_eventRegistrations.Count + "\n");
506 internal struct EventCacheEntry
508 // [Handler] -> Token
509 internal ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTable;
511 // Maintains current total count for the EventRegistrationTokenListWithCount for this event cache key
512 internal TokenListCount tokenListCount;
515 // Mappings of delegates registered for events -> their registration tokens.
516 // These mappings are stored indexed by the remove method which can be used to undo the registrations.
518 // The full structure of this table is:
519 // EventCacheKey (instanceKey, eventMethod) -> EventCacheEntry (Handler->tokens)
521 // A InstanceKey is the IUnknown * or static type instance
523 // Couple of things to note:
524 // 1. We need to use IUnknown* because we want to be able to unscribe to the event for another RCW
525 // based on the same COM object. For example:
526 // m_canvas.GetAt(0).Event += Func;
527 // m_canvas.GetAt(0).Event -= Func; // GetAt(0) might create a new RCW
529 // 2. Handler->Token is a ConditionalWeakTable because we don't want to keep the delegate alive
530 // and we want EventRegistrationTokenListWithCount to be finalized after the delegate is no longer alive
531 // 3. It is possible another COM object is created at the same address
532 // before the entry in cache is destroyed. More specifically,
533 // a. The same delegate is being unsubscribed. In this case we'll give them a
534 // stale token - unlikely to be a problem
535 // b. The same delegate is subscribed then unsubscribed. We need to make sure give
536 // them the latest token in this case. This is guaranteed by always giving the last token and always use equality to
537 // add/remove event handlers
538 internal volatile static Dictionary<EventCacheKey, EventCacheEntry> s_eventRegistrations =
539 new Dictionary<EventCacheKey, EventCacheEntry>(new EventCacheKeyEqualityComparer());
541 // Prevent add/remove handler code to run at the same with with cache cleanup code
542 private volatile static MyReaderWriterLock s_eventCacheRWLock = new MyReaderWriterLock();
544 // Get InstanceKey to use in the cache
545 [SecuritySafeCritical]
546 private static object GetInstanceKey(Action<EventRegistrationToken> removeMethod)
548 object target = removeMethod.Target;
549 Contract.Assert(target == null || Marshal.IsComObject(target), "Must be null or a RCW");
551 return removeMethod.Method.DeclaringType;
553 // Need the "Raw" IUnknown pointer for the RCW that is not bound to the current context
554 return (object) Marshal.GetRawIUnknownForComObjectNoAddRef(target);
558 internal static void AddEventHandler<T>(Func<T, EventRegistrationToken> addMethod,
559 Action<EventRegistrationToken> removeMethod,
562 // The instanceKey will be IUnknown * of the target object
563 object instanceKey = GetInstanceKey(removeMethod);
565 // Call addMethod outside of RW lock
566 // At this point we don't need to worry about race conditions and we can avoid deadlocks
567 // if addMethod waits on finalizer thread
568 // If we later throw we need to remove the method
569 EventRegistrationToken token = addMethod(handler);
571 bool tokenAdded = false;
575 EventRegistrationTokenListWithCount tokens;
578 // The whole add/remove code has to be protected by a reader/writer lock
579 // Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
581 s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
584 // Add the method, and make a note of the delegate -> token mapping.
585 TokenListCount tokenListCount;
586 ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetOrCreateEventRegistrationTokenTable(instanceKey, removeMethod, out tokenListCount);
587 lock (registrationTokens)
590 // We need to find the key that equals to this handler
591 // Suppose we have 3 handlers A, B, C that are equal (refer to the same object and method),
592 // the first handler (let's say A) will be used as the key and holds all the tokens.
593 // We don't need to hold onto B and C, because the COM object itself will keep them alive,
594 // and they won't die anyway unless the COM object dies or they get unsubscribed.
595 // It may appear that it is fine to hold A, B, C, and add them and their corresponding tokens
596 // into registrationTokens table. However, this is very dangerous, because this COM object
597 // may die, but A, B, C might not get collected yet, and another COM object comes into life
598 // with the same IUnknown address, and we subscribe event B. In this case, the right token
599 // will be added into B's token list, but once we unsubscribe B, we might end up removing
600 // the last token in C, and that may lead to crash.
602 object key = registrationTokens.FindEquivalentKeyUnsafe(handler, out tokens);
605 tokens = new EventRegistrationTokenListWithCount(tokenListCount, token);
606 registrationTokens.Add(handler, tokens);
618 s_eventCacheRWLock.ReleaseReaderLock();
621 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event subscribed for instance = " + instanceKey + ", handler = " + handler + "\n");
625 // If we've already added the token and go there, we don't need to "UNDO" anything
628 // Otherwise, "Undo" addMethod if any exception occurs
629 // There is no need to cleanup our data structure as we haven't added the token yet
638 private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetEventRegistrationTokenTableNoCreate(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount)
640 Contract.Requires(instance != null);
641 Contract.Requires(removeMethod != null);
643 return GetEventRegistrationTokenTableInternal(instance, removeMethod, out tokenListCount, /* createIfNotFound = */ false);
646 private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetOrCreateEventRegistrationTokenTable(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount)
648 Contract.Requires(instance != null);
649 Contract.Requires(removeMethod != null);
651 return GetEventRegistrationTokenTableInternal(instance, removeMethod, out tokenListCount, /* createIfNotFound = */ true);
654 // Get the event registration token table for an event. These are indexed by the remove method of the event.
655 private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetEventRegistrationTokenTableInternal(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount, bool createIfNotFound)
657 Contract.Requires(instance != null);
658 Contract.Requires(removeMethod != null);
659 Contract.Requires(s_eventRegistrations != null);
661 EventCacheKey eventCacheKey;
662 eventCacheKey.target = instance;
663 eventCacheKey.method = removeMethod.Method;
665 lock (s_eventRegistrations)
667 EventCacheEntry eventCacheEntry;
668 if (!s_eventRegistrations.TryGetValue(eventCacheKey, out eventCacheEntry))
670 if (!createIfNotFound)
672 // No need to create an entry in this case
673 tokenListCount = null;
677 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Adding (" + instance + "," + removeMethod.Method + ") into cache" + "\n");
679 eventCacheEntry = new EventCacheEntry();
680 eventCacheEntry.registrationTable = new ConditionalWeakTable<object, EventRegistrationTokenListWithCount>();
681 eventCacheEntry.tokenListCount = new TokenListCount(eventCacheKey);
683 s_eventRegistrations.Add(eventCacheKey, eventCacheEntry);
686 tokenListCount = eventCacheEntry.tokenListCount;
688 return eventCacheEntry.registrationTable;
693 internal static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler)
695 object instanceKey = GetInstanceKey(removeMethod);
697 EventRegistrationToken token;
700 // The whole add/remove code has to be protected by a reader/writer lock
701 // Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
703 s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
706 TokenListCount tokenListCount;
707 ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetEventRegistrationTokenTableNoCreate(instanceKey, removeMethod, out tokenListCount);
708 if (registrationTokens == null)
710 // We have no information regarding this particular instance (IUnknown*/type) - just return
711 // This is necessary to avoid leaking empty dictionary/conditionalWeakTables for this instance
712 BCLDebug.Log("INTEROP", "[WinRT_Eventing] no registrationTokens found for instance=" + instanceKey + ", handler= " + handler + "\n");
716 lock (registrationTokens)
718 EventRegistrationTokenListWithCount tokens;
721 // When unsubscribing events, we allow subscribing the event using a different delegate
722 // (but with the same object/method), so we need to find the first delegate that matches
723 // and unsubscribe it
724 // It actually doesn't matter which delegate - as long as it matches
725 // Note that inside TryGetValueWithValueEquality we assumes that any delegate
726 // with the same value equality would have the same hash code
727 object key = registrationTokens.FindEquivalentKeyUnsafe(handler, out tokens);
728 Contract.Assert((key != null && tokens != null) || (key == null && tokens == null),
729 "key and tokens must be both null or non-null");
732 // Failure to find a registration for a token is not an error - it's simply a no-op.
733 BCLDebug.Log("INTEROP", "[WinRT_Eventing] no token list found for instance=" + instanceKey + ", handler= " + handler + "\n");
737 // Select a registration token to unregister
738 // Note that we need to always get the last token just in case another COM object
739 // is created at the same address before the entry for the old one goes away.
740 // See comments above s_eventRegistrations for more details
741 bool moreItems = tokens.Pop(out token);
743 // If the last token is removed from token list, we need to remove it from the cache
744 // otherwise FindEquivalentKeyUnsafe may found this empty token list even though there could be other
745 // equivalent keys in there with non-0 token list
748 // Remove it from (handler)->(tokens)
749 // NOTE: We should not check whether registrationTokens has 0 entries and remove it from the cache
750 // (just like managed event implementation), because this might ---- with the finalizer of
751 // EventRegistrationTokenList
752 registrationTokens.Remove(key);
755 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event unsubscribed for managed instance = " + instanceKey + ", handler = " + handler + ", token = " + token.m_value + "\n");
760 s_eventCacheRWLock.ReleaseReaderLock();
763 // Call removeMethod outside of RW lock
764 // At this point we don't need to worry about race conditions and we can avoid deadlocks
765 // if removeMethod waits on finalizer thread
770 internal static void RemoveAllEventHandlers(Action<EventRegistrationToken> removeMethod)
772 object instanceKey = GetInstanceKey(removeMethod);
774 List<EventRegistrationToken> tokensToRemove = new List<EventRegistrationToken>();
777 // The whole add/remove code has to be protected by a reader/writer lock
778 // Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
780 s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
783 TokenListCount tokenListCount;
784 ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetEventRegistrationTokenTableNoCreate(instanceKey, removeMethod, out tokenListCount);
785 if (registrationTokens == null)
787 // We have no information regarding this particular instance (IUnknown*/type) - just return
788 // This is necessary to avoid leaking empty dictionary/conditionalWeakTables for this instance
792 lock (registrationTokens)
794 // Copy all tokens to tokensToRemove array which later we'll call removeMethod on
796 foreach (EventRegistrationTokenListWithCount tokens in registrationTokens.Values)
798 tokens.CopyTo(tokensToRemove);
801 // Clear the table - at this point all event handlers are no longer in the cache
802 // but they are not removed yet
803 registrationTokens.Clear();
804 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Cache cleared for managed instance = " + instanceKey + "\n");
809 s_eventCacheRWLock.ReleaseReaderLock();
813 // Remove all handlers outside the lock
815 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Start removing all events for instance = " + instanceKey + "\n");
816 CallRemoveMethods(removeMethod, tokensToRemove);
817 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Finished removing all events for instance = " + instanceKey + "\n");
821 internal class ReaderWriterLockTimedOutException : ApplicationException
825 /// I borrowed Vance's reader writer lock implementation from his blog as ReaderWriterLockSlim is
826 /// available in System.Core.dll!
829 /// A reader-writer lock implementation that is intended to be simple, yet very
830 /// efficient. In particular only 1 interlocked operation is taken for any lock
831 /// operation (we use spin locks to achieve this). The spin lock is never held
832 /// for more than a few instructions (in particular, we never call event APIs
833 /// or in fact any non-trivial API while holding the spin lock).
835 /// Currently this ReaderWriterLock does not support recurision, however it is
838 internal class MyReaderWriterLock
840 // Lock specifiation for myLock: This lock protects exactly the local fields associted
841 // instance of MyReaderWriterLock. It does NOT protect the memory associted with the
842 // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
845 // Who owns the lock owners > 0 => readers
846 // owners = -1 means there is one writer. Owners must be >= -1.
849 // These variables allow use to avoid Setting events (which is expensive) if we don't have to.
850 uint numWriteWaiters; // maximum number of threads that can be doing a WaitOne on the writeEvent
851 uint numReadWaiters; // maximum number of threads that can be doing a WaitOne on the readEvent
853 // conditions we wait on.
854 EventWaitHandle writeEvent; // threads waiting to aquire a write lock go here.
855 EventWaitHandle readEvent; // threads waiting to aquire a read lock go here (will be released in bulk)
857 internal MyReaderWriterLock()
859 // All state can start out zeroed.
862 internal void AcquireReaderLock(int millisecondsTimeout)
867 // We can enter a read lock if there are only read-locks have been given out
868 // and a writer is not trying to get in.
869 if (owners >= 0 && numWriteWaiters == 0)
871 // Good case, there is no contention, we are basically done
872 owners++; // Indicate we have another reader
876 // Drat, we need to wait. Mark that we have waiters and wait.
877 if (readEvent == null) // Create the needed event
879 LazyCreateEvent(ref readEvent, false);
880 continue; // since we left the lock, start over.
883 WaitOnEvent(readEvent, ref numReadWaiters, millisecondsTimeout);
888 internal void AcquireWriterLock(int millisecondsTimeout)
895 // Good case, there is no contention, we are basically done
896 owners = -1; // indicate we have a writer.
900 // Drat, we need to wait. Mark that we have waiters and wait.
901 if (writeEvent == null) // create the needed event.
903 LazyCreateEvent(ref writeEvent, true);
904 continue; // since we left the lock, start over.
907 WaitOnEvent(writeEvent, ref numWriteWaiters, millisecondsTimeout);
912 internal void ReleaseReaderLock()
915 Contract.Assert(owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
917 ExitAndWakeUpAppropriateWaiters();
920 internal void ReleaseWriterLock()
923 Contract.Assert(owners == -1, "Calling ReleaseWriterLock when no write lock is held");
925 ExitAndWakeUpAppropriateWaiters();
929 /// A routine for lazily creating a event outside the lock (so if errors
930 /// happen they are outside the lock and that we don't do much work
931 /// while holding a spin lock). If all goes well, reenter the lock and
934 private void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent) {
935 Contract.Assert(myLock != 0, "Lock must be held");
936 Contract.Assert(waitEvent == null, "Wait event must be null");
939 EventWaitHandle newEvent;
940 if (makeAutoResetEvent)
941 newEvent = new AutoResetEvent(false);
943 newEvent = new ManualResetEvent(false);
945 if (waitEvent == null) // maybe someone snuck in.
946 waitEvent = newEvent;
950 /// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.
951 /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
953 private void WaitOnEvent(EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
955 Contract.Assert(myLock != 0, "Lock must be held");
960 bool waitSuccessful = false;
961 ExitMyLock(); // Do the wait outside of any lock
964 if (!waitEvent.WaitOne(millisecondsTimeout, false))
965 throw new ReaderWriterLockTimedOutException();
967 waitSuccessful = true;
973 if (!waitSuccessful) // We are going to throw for some reason. Exit myLock.
979 /// Determines the appropriate events to set, leaves the locks, and sets the events.
981 private void ExitAndWakeUpAppropriateWaiters()
983 Contract.Assert(myLock != 0, "Lock must be held");
985 if (owners == 0 && numWriteWaiters > 0)
987 ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock)
988 writeEvent.Set(); // release one writer.
990 else if (owners >= 0 && numReadWaiters != 0)
992 ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock)
993 readEvent.Set(); // release all readers.
999 private void EnterMyLock() {
1000 if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
1004 private void EnterMyLockSpin()
1006 for (int i = 0; ;i++)
1008 if (i < 3 && Environment.ProcessorCount > 1)
1009 Thread.SpinWait(20); // Wait a few dozen instructions to let another processor release lock.
1011 Thread.Sleep(0); // Give up my quantum.
1013 if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
1017 private void ExitMyLock()
1019 Contract.Assert(myLock != 0, "Exiting spin lock that is not held");
1026 // Call removeMethod on each token and aggregate all exceptions thrown from removeMethod into one in case of failure
1028 internal static void CallRemoveMethods(Action<EventRegistrationToken> removeMethod, List<EventRegistrationToken> tokensToRemove)
1031 List<Exception> exceptions = new List<Exception>();
1033 foreach (EventRegistrationToken token in tokensToRemove)
1037 removeMethod(token);
1044 BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event unsubscribed for token = " + token.m_value + "\n");
1047 if (exceptions.Count > 0)
1048 throw new AggregateException(exceptions.ToArray());
1052 internal static unsafe string HStringToString(IntPtr hstring)
1054 Contract.Requires(Environment.IsWinRTSupported);
1056 // There is no difference between a null and empty HSTRING
1057 if (hstring == IntPtr.Zero)
1059 return String.Empty;
1065 char* rawBuffer = UnsafeNativeMethods.WindowsGetStringRawBuffer(hstring, &length);
1066 return new String(rawBuffer, 0, checked((int)length));
1070 internal static Exception GetExceptionForHR(int hresult, Exception innerException, string messageResource)
1073 if (innerException != null)
1075 string message = innerException.Message;
1076 if (message == null && messageResource != null)
1078 message = Environment.GetResourceString(messageResource);
1080 e = new Exception(message, innerException);
1084 string message = (messageResource != null ? Environment.GetResourceString(messageResource) : null);
1085 e = new Exception(message);
1088 e.SetErrorCode(hresult);
1092 internal static Exception GetExceptionForHR(int hresult, Exception innerException)
1094 return GetExceptionForHR(hresult, innerException, null);
1097 private static bool s_haveBlueErrorApis = true;
1100 private static bool RoOriginateLanguageException(int error, string message, IntPtr languageException)
1102 if (s_haveBlueErrorApis)
1106 return UnsafeNativeMethods.RoOriginateLanguageException(error, message, languageException);
1108 catch (EntryPointNotFoundException)
1110 s_haveBlueErrorApis = false;
1118 private static void RoReportUnhandledError(IRestrictedErrorInfo error)
1120 if (s_haveBlueErrorApis)
1124 UnsafeNativeMethods.RoReportUnhandledError(error);
1126 catch (EntryPointNotFoundException)
1128 s_haveBlueErrorApis = false;
1133 private static Guid s_iidIErrorInfo = new Guid(0x1CF2B120, 0x547D, 0x101B, 0x8E, 0x65, 0x08, 0x00, 0x2B, 0x2B, 0xD1, 0x19);
1136 /// Report that an exception has occured which went user unhandled. This allows the global error handler
1137 /// for the application to be invoked to process the error.
1139 /// <returns>true if the error was reported, false if not (ie running on Win8)</returns>
1140 [FriendAccessAllowed]
1141 [SecuritySafeCritical]
1142 internal static bool ReportUnhandledError(Exception e)
1144 // Only report to the WinRT global exception handler in modern apps
1145 if (!AppDomain.IsAppXModel())
1150 // If we don't have the capability to report to the global error handler, early out
1151 if (!s_haveBlueErrorApis)
1158 IntPtr exceptionIUnknown = IntPtr.Zero;
1159 IntPtr exceptionIErrorInfo = IntPtr.Zero;
1162 // Get an IErrorInfo for the current exception and originate it as a langauge error in order to have
1163 // Windows generate an IRestrictedErrorInfo corresponding to the exception object. We can then
1164 // notify the global error handler that this IRestrictedErrorInfo instance represents an exception that
1165 // went unhandled in managed code.
1167 // Note that we need to get an IUnknown for the exception object and then QI for IErrorInfo since Exception
1168 // doesn't implement IErrorInfo in managed code - only its CCW does.
1169 exceptionIUnknown = Marshal.GetIUnknownForObject(e);
1170 if (exceptionIUnknown != IntPtr.Zero)
1172 Marshal.QueryInterface(exceptionIUnknown, ref s_iidIErrorInfo, out exceptionIErrorInfo);
1173 if (exceptionIErrorInfo != IntPtr.Zero)
1175 if (RoOriginateLanguageException(Marshal.GetHRForException_WinRT(e), e.Message, exceptionIErrorInfo))
1177 IRestrictedErrorInfo restrictedError = UnsafeNativeMethods.GetRestrictedErrorInfo();
1178 if (restrictedError != null)
1180 RoReportUnhandledError(restrictedError);
1189 if (exceptionIErrorInfo != IntPtr.Zero)
1191 Marshal.Release(exceptionIErrorInfo);
1194 if (exceptionIUnknown != IntPtr.Zero)
1196 Marshal.Release(exceptionIUnknown);
1201 // If we got here, then some step of the marshaling failed, which means the GEH was not invoked
1205 #if FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
1206 // Get an IActivationFactory * for a managed type
1208 internal static IntPtr GetActivationFactoryForType(Type type)
1210 ManagedActivationFactory activationFactory = GetManagedActivationFactory(type);
1211 return Marshal.GetComInterfaceForObject(activationFactory, typeof(IActivationFactory));
1215 internal static ManagedActivationFactory GetManagedActivationFactory(Type type)
1217 ManagedActivationFactory activationFactory = new ManagedActivationFactory(type);
1219 // If the type has any associated factory interfaces (i.e. supports non-default activation
1220 // or has statics), the CCW for this instance of ManagedActivationFactory must support them.
1221 Marshal.InitializeManagedWinRTFactoryObject(activationFactory, (RuntimeType)type);
1222 return activationFactory;
1225 #if FEATURE_COMINTEROP_WINRT_DESKTOP_HOST
1226 // Currently we use only a single class activator since we have a requirement that all class activations come from the same
1227 // app base and we haven't sorted through the various code sharing implications of spinning up multiple AppDomains. This
1228 // holds the IWinRTClassActivator* that is used for the process
1229 private static IntPtr s_pClassActivator = IntPtr.Zero;
1232 internal static IntPtr GetClassActivatorForApplication(string appBase)
1234 if (s_pClassActivator == IntPtr.Zero)
1236 AppDomainSetup hostDomainSetup = new AppDomainSetup()
1238 ApplicationBase = appBase,
1241 AppDomain hostDomain = AppDomain.CreateDomain(Environment.GetResourceString("WinRTHostDomainName", appBase), null, hostDomainSetup);
1242 WinRTClassActivator activator = (WinRTClassActivator)hostDomain.CreateInstanceAndUnwrap(typeof(WinRTClassActivator).Assembly.FullName, typeof(WinRTClassActivator).FullName);
1243 IntPtr pActivator = activator.GetIWinRTClassActivator();
1245 if (Interlocked.CompareExchange(ref s_pClassActivator, pActivator, IntPtr.Zero) != IntPtr.Zero)
1247 Marshal.Release(pActivator);
1252 AppDomain.Unload(hostDomain);
1254 catch (CannotUnloadAppDomainException) { }
1258 Marshal.AddRef(s_pClassActivator);
1259 return s_pClassActivator;
1261 #endif // FEATURE_COMINTEROP_WINRT_DESKTOP_HOST
1263 #endif // FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
1266 // Get activation factory object for a specified WinRT type
1267 // If the WinRT type is a native type, we'll always create a unique RCW for it,
1268 // This is necessary because WinRT factories are often implemented as a singleton,
1269 // and getting back a RCW for such WinRT factory would usually get back a RCW from
1270 // another apartment, even if the interface pointe returned from GetActivationFactory
1271 // is a raw pointer. As a result, user would randomly get back RCWs for activation
1272 // factories from other apartments and make transiton to those apartments and cause
1273 // deadlocks and create objects in incorrect apartments
1276 public static IActivationFactory GetActivationFactory(Type type)
1279 throw new ArgumentNullException("type");
1281 #if FEATURE_COMINTEROP || MONO_COM
1282 if (type.IsWindowsRuntimeObject && type.IsImport)
1284 return (IActivationFactory)Marshal.GetNativeActivationFactory(type);
1289 #if FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
1290 return GetManagedActivationFactory(type);
1292 // Managed factories are not supported so as to minimize public surface (and test effort)
1293 throw new NotSupportedException();
1298 // HSTRING marshaling methods:
1301 public static IntPtr StringToHString(String s)
1303 if (!Environment.IsWinRTSupported)
1304 throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_WinRT"));
1307 throw new ArgumentNullException("s");
1312 int hrCreate = UnsafeNativeMethods.WindowsCreateString(s, s.Length, &hstring);
1313 Marshal.ThrowExceptionForHR(hrCreate, new IntPtr(-1));
1319 public static String PtrToStringHString(IntPtr ptr)
1321 if (!Environment.IsWinRTSupported)
1323 throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_WinRT"));
1326 return HStringToString(ptr);
1330 public static void FreeHString(IntPtr ptr)
1332 if (!Environment.IsWinRTSupported)
1333 throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_WinRT"));
1335 if (ptr != IntPtr.Zero)
1337 UnsafeNativeMethods.WindowsDeleteString(ptr);