1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 using System.Reflection;
11 using System.Runtime.CompilerServices;
12 using System.Runtime.InteropServices;
13 using System.Threading;
14 using System.Security;
16 namespace System.Runtime.InteropServices.WindowsRuntime
18 // Helper functions to manually marshal data between .NET and WinRT
19 public static class WindowsRuntimeMarshal
21 // Add an event handler to a Windows Runtime style event, such that it can be removed via a delegate
22 // lookup at a later time. This method adds the handler to the add method using the supplied
23 // delegate. It then stores the corresponding token in a dictionary for easy access by RemoveEventHandler
24 // later. Note that the dictionary is indexed by the remove method that will be used for RemoveEventHandler
25 // so the removeMethod given here must match the remove method supplied there exactly.
26 public static void AddEventHandler<T>(Func<T, EventRegistrationToken> addMethod,
27 Action<EventRegistrationToken> removeMethod,
30 if (addMethod == null)
31 throw new ArgumentNullException(nameof(addMethod));
32 if (removeMethod == null)
33 throw new ArgumentNullException(nameof(removeMethod));
35 // Managed code allows adding a null event handler, the effect is a no-op. To match this behavior
36 // for WinRT events, we simply ignore attempts to add null.
42 // Delegate to managed event registration implementation or native event registration implementation
43 // They have completely different implementation because native side has its own unique problem to solve -
44 // there could be more than one RCW for the same COM object
45 // it would be more confusing and less-performant if we were to merge them together
46 object target = removeMethod.Target;
47 if (target == null || Marshal.IsComObject(target))
48 NativeOrStaticEventRegistrationImpl.AddEventHandler<T>(addMethod, removeMethod, handler);
50 ManagedEventRegistrationImpl.AddEventHandler<T>(addMethod, removeMethod, handler);
53 // Remove the delegate handler from the Windows Runtime style event registration by looking for
54 // its token, previously stored via AddEventHandler<T>
55 public static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler)
57 if (removeMethod == null)
58 throw new ArgumentNullException(nameof(removeMethod));
60 // Managed code allows removing a null event handler, the effect is a no-op. To match this behavior
61 // for WinRT events, we simply ignore attempts to remove null.
67 // Delegate to managed event registration implementation or native event registration implementation
68 // They have completely different implementation because native side has its own unique problem to solve -
69 // there could be more than one RCW for the same COM object
70 // it would be more confusing and less-performant if we were to merge them together
71 object target = removeMethod.Target;
72 if (target == null || Marshal.IsComObject(target))
73 NativeOrStaticEventRegistrationImpl.RemoveEventHandler<T>(removeMethod, handler);
75 ManagedEventRegistrationImpl.RemoveEventHandler<T>(removeMethod, handler);
78 public static void RemoveAllEventHandlers(Action<EventRegistrationToken> removeMethod)
80 if (removeMethod == null)
81 throw new ArgumentNullException(nameof(removeMethod));
83 // Delegate to managed event registration implementation or native event registration implementation
84 // They have completely different implementation because native side has its own unique problem to solve -
85 // there could be more than one RCW for the same COM object
86 // it would be more confusing and less-performant if we were to merge them together
87 object target = removeMethod.Target;
88 if (target == null || Marshal.IsComObject(target))
89 NativeOrStaticEventRegistrationImpl.RemoveAllEventHandlers(removeMethod);
91 ManagedEventRegistrationImpl.RemoveAllEventHandlers(removeMethod);
94 // Returns the total cache size
95 // Used by test only to verify we don't leak event cache
96 internal static int GetRegistrationTokenCacheSize()
100 if (ManagedEventRegistrationImpl.s_eventRegistrations != null)
102 lock (ManagedEventRegistrationImpl.s_eventRegistrations)
104 foreach (var item in ManagedEventRegistrationImpl.s_eventRegistrations)
109 if (NativeOrStaticEventRegistrationImpl.s_eventRegistrations != null)
111 lock (NativeOrStaticEventRegistrationImpl.s_eventRegistrations)
113 count += NativeOrStaticEventRegistrationImpl.s_eventRegistrations.Count;
121 // Optimized version of List of EventRegistrationToken
122 // It is made a struct to reduce overhead
124 internal struct EventRegistrationTokenList
126 private EventRegistrationToken firstToken; // Optimization for common case where there is only one token
127 private List<EventRegistrationToken> restTokens; // Rest of the tokens
129 internal EventRegistrationTokenList(EventRegistrationToken token)
135 // Push a new token into this list
136 // Returns true if you need to copy back this list into the dictionary (so that you
137 // don't lose change outside the dictionary). false otherwise.
138 public bool Push(EventRegistrationToken token)
140 bool needCopy = false;
142 if (restTokens == null)
144 restTokens = new List<EventRegistrationToken>();
148 restTokens.Add(token);
153 // Pops the last token
154 // Returns false if no more tokens left, true otherwise
155 public bool Pop(out EventRegistrationToken token)
157 // Only 1 token in this list and we just removed the last token
158 if (restTokens == null || restTokens.Count == 0)
164 int last = restTokens.Count - 1;
165 token = restTokens[last];
166 restTokens.RemoveAt(last);
171 public void CopyTo(List<EventRegistrationToken> tokens)
173 tokens.Add(firstToken);
174 if (restTokens != null)
175 tokens.AddRange(restTokens);
180 // Event registration support for managed objects events & static events
182 internal static class ManagedEventRegistrationImpl
184 // Mappings of delegates registered for events -> their registration tokens.
185 // These mappings are stored indexed by the remove method which can be used to undo the registrations.
187 // The full structure of this table is:
188 // object the event is being registered on ->
189 // Table [RemoveMethod] ->
190 // Table [Handler] -> Token
192 // Note: There are a couple of optimizations I didn't do here because they don't make sense for managed events:
193 // 1. Flatten the event cache (see EventCacheKey in native WinRT event implementation below)
195 // This is because managed events use ConditionalWeakTable to hold Objects->(Event->(Handler->Tokens)),
196 // and when object goes away everything else will be nicely cleaned up. If I flatten it like native WinRT events,
197 // I'll have to use Dictionary (as ConditionalWeakTable won't work - nobody will hold the new key alive anymore)
198 // instead, and that means I'll have to add more code from native WinRT events into managed WinRT event to support
199 // self-cleanup in the finalization, as well as reader/writer lock to protect against race conditions in the finalization,
200 // which adds a lot more complexity and doesn't really worth it.
202 // 2. Use conditionalWeakTable to hold Handler->Tokens.
204 // The reason is very simple - managed object use dictionary (see EventRegistrationTokenTable) to hold delegates alive.
205 // If the delegates aren't alive, it means either they have been unsubscribed, or the object itself is gone,
206 // and in either case, they've been already taken care of.
208 internal volatile static
209 ConditionalWeakTable<object, Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>>> s_eventRegistrations =
210 new ConditionalWeakTable<object, Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>>>();
212 internal static void AddEventHandler<T>(Func<T, EventRegistrationToken> addMethod,
213 Action<EventRegistrationToken> removeMethod,
216 Debug.Assert(addMethod != null);
217 Debug.Assert(removeMethod != null);
219 // Add the method, and make a note of the token -> delegate mapping.
220 object instance = removeMethod.Target;
221 Dictionary<object, EventRegistrationTokenList> registrationTokens = GetEventRegistrationTokenTable(instance, removeMethod);
222 EventRegistrationToken token = addMethod(handler);
223 lock (registrationTokens)
225 EventRegistrationTokenList tokens;
226 if (!registrationTokens.TryGetValue(handler, out tokens))
228 tokens = new EventRegistrationTokenList(token);
229 registrationTokens[handler] = tokens;
233 bool needCopy = tokens.Push(token);
235 // You need to copy back this list into the dictionary (so that you don't lose change outside dictionary)
237 registrationTokens[handler] = tokens;
240 Log("[WinRT_Eventing] Event subscribed for managed instance = " + instance + ", handler = " + handler + "\n");
244 // Get the event registration token table for an event. These are indexed by the remove method of the event.
245 private static Dictionary<object, EventRegistrationTokenList> GetEventRegistrationTokenTable(object instance, Action<EventRegistrationToken> removeMethod)
247 Debug.Assert(instance != null);
248 Debug.Assert(removeMethod != null);
249 Debug.Assert(s_eventRegistrations != null);
251 lock (s_eventRegistrations)
253 Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>> instanceMap = null;
254 if (!s_eventRegistrations.TryGetValue(instance, out instanceMap))
256 instanceMap = new Dictionary<MethodInfo, Dictionary<object, EventRegistrationTokenList>>();
257 s_eventRegistrations.Add(instance, instanceMap);
260 Dictionary<object, EventRegistrationTokenList> tokens = null;
261 if (!instanceMap.TryGetValue(removeMethod.Method, out tokens))
263 tokens = new Dictionary<object, EventRegistrationTokenList>();
264 instanceMap.Add(removeMethod.Method, tokens);
271 internal static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler)
273 Debug.Assert(removeMethod != null);
275 object instance = removeMethod.Target;
276 Dictionary<object, EventRegistrationTokenList> registrationTokens = GetEventRegistrationTokenTable(instance, removeMethod);
277 EventRegistrationToken token;
279 lock (registrationTokens)
281 EventRegistrationTokenList tokens;
283 // Failure to find a registration for a token is not an error - it's simply a no-op.
284 if (!registrationTokens.TryGetValue(handler, out tokens))
286 Log("[WinRT_Eventing] no registrationTokens found for instance=" + instance + ", handler= " + handler + "\n");
291 // Select a registration token to unregister
292 // We don't care which one but I'm returning the last registered token to be consistent
293 // with native event registration implementation
294 bool moreItems = tokens.Pop(out token);
297 // Remove it from cache if this list become empty
298 // This must be done because EventRegistrationTokenList now becomes invalid
299 // (mostly because there is no safe default value for EventRegistrationToken to express 'no token')
300 // NOTE: We should try to remove registrationTokens itself from cache if it is empty, otherwise
301 // we could run into a race condition where one thread removes it from cache and another thread adds
302 // into the empty registrationToken table
303 registrationTokens.Remove(handler);
309 Log("[WinRT_Eventing] Event unsubscribed for managed instance = " + instance + ", handler = " + handler + ", token = " + token.Value + "\n");
312 internal static void RemoveAllEventHandlers(Action<EventRegistrationToken> removeMethod)
314 Debug.Assert(removeMethod != null);
316 object instance = removeMethod.Target;
317 Dictionary<object, EventRegistrationTokenList> registrationTokens = GetEventRegistrationTokenTable(instance, removeMethod);
319 List<EventRegistrationToken> tokensToRemove = new List<EventRegistrationToken>();
321 lock (registrationTokens)
323 // Copy all tokens to tokensToRemove array which later we'll call removeMethod on
325 foreach (EventRegistrationTokenList tokens in registrationTokens.Values)
327 tokens.CopyTo(tokensToRemove);
330 // Clear the dictionary - at this point all event handlers are no longer in the cache
331 // but they are not removed yet
332 registrationTokens.Clear();
333 Log("[WinRT_Eventing] Cache cleared for managed instance = " + instance + "\n");
337 // Remove all handlers outside the lock
339 Log("[WinRT_Eventing] Start removing all events for instance = " + instance + "\n");
340 CallRemoveMethods(removeMethod, tokensToRemove);
341 Log("[WinRT_Eventing] Finished removing all events for instance = " + instance + "\n");
346 // WinRT event registration implementation code
348 internal static class NativeOrStaticEventRegistrationImpl
351 // Key = (target object, event)
352 // We use a key of object+event to save an extra dictionary
354 internal struct EventCacheKey : IEquatable<EventCacheKey>
356 internal object target;
357 internal MethodInfo method;
359 public override string ToString()
361 return "(" + target + ", " + method + ")";
364 public bool Equals(EventCacheKey other)
366 return (object.Equals(target, other.target) && object.Equals(method, other.method));
369 public int GetHashCode(EventCacheKey key)
371 return key.target.GetHashCode() ^ key.method.GetHashCode();
376 // EventRegistrationTokenListWithCount
378 // A list of EventRegistrationTokens that maintains a count
380 // The reason this needs to be a separate class is that we need a finalizer for this class
381 // If the delegate is collected, it will take this list away with it (due to dependent handles),
382 // and we need to remove the PerInstancEntry from cache
383 // See ~EventRegistrationTokenListWithCount for more details
385 internal class EventRegistrationTokenListWithCount
387 private TokenListCount _tokenListCount;
388 private EventRegistrationTokenList _tokenList;
390 internal EventRegistrationTokenListWithCount(TokenListCount tokenListCount, EventRegistrationToken token)
392 _tokenListCount = tokenListCount;
393 _tokenListCount.Inc();
395 _tokenList = new EventRegistrationTokenList(token);
398 ~EventRegistrationTokenListWithCount()
400 // Decrement token list count
401 // This is need to correctly keep trace of number of tokens for EventCacheKey
402 // and remove it from cache when the token count drop to 0
403 // we don't need to take locks for decrement the count - we only need to take a global
404 // lock when we decide to destroy cache for the IUnknown */type instance
405 Log("[WinRT_Eventing] Finalizing EventRegistrationTokenList for " + _tokenListCount.Key + "\n");
406 _tokenListCount.Dec();
409 public void Push(EventRegistrationToken token)
411 // Since EventRegistrationTokenListWithCount is a reference type, there is no need
412 // to copy back. Ignore the return value
413 _tokenList.Push(token);
416 public bool Pop(out EventRegistrationToken token)
418 return _tokenList.Pop(out token);
421 public void CopyTo(List<EventRegistrationToken> tokens)
423 _tokenList.CopyTo(tokens);
428 // Maintains the number of tokens for a particular EventCacheKey
429 // TokenListCount is a class for two reasons:
430 // 1. Efficient update in the Dictionary to avoid lookup twice to update the value
431 // 2. Update token count without taking a global lock. Only takes a global lock when drop to 0
433 internal class TokenListCount
436 private EventCacheKey _key;
438 internal TokenListCount(EventCacheKey key)
443 internal EventCacheKey Key
450 int newCount = Interlocked.Increment(ref _count);
451 Log("[WinRT_Eventing] Incremented TokenListCount for " + _key + ", Value = " + newCount + "\n");
456 // Avoid racing with Add/Remove event entries into the cache
457 // You don't want this removing the key in the middle of a Add/Remove
458 s_eventCacheRWLock.AcquireWriterLock(Timeout.Infinite);
461 int newCount = Interlocked.Decrement(ref _count);
462 Log("[WinRT_Eventing] Decremented TokenListCount for " + _key + ", Value = " + newCount + "\n");
468 s_eventCacheRWLock.ReleaseWriterLock();
472 private void CleanupCache()
474 // Time to destroy cache for this IUnknown */type instance
475 // because the total token list count has dropped to 0 and we don't have any events subscribed
476 Debug.Assert(s_eventRegistrations != null);
478 Log("[WinRT_Eventing] Removing " + _key + " from cache" + "\n");
479 s_eventRegistrations.Remove(_key);
480 Log("[WinRT_Eventing] s_eventRegistrations size = " + s_eventRegistrations.Count + "\n");
484 internal struct EventCacheEntry
486 // [Handler] -> Token
487 internal ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTable;
489 // Maintains current total count for the EventRegistrationTokenListWithCount for this event cache key
490 internal TokenListCount tokenListCount;
493 // Mappings of delegates registered for events -> their registration tokens.
494 // These mappings are stored indexed by the remove method which can be used to undo the registrations.
496 // The full structure of this table is:
497 // EventCacheKey (instanceKey, eventMethod) -> EventCacheEntry (Handler->tokens)
499 // A InstanceKey is the IUnknown * or static type instance
501 // Couple of things to note:
502 // 1. We need to use IUnknown* because we want to be able to unscribe to the event for another RCW
503 // based on the same COM object. For example:
504 // m_canvas.GetAt(0).Event += Func;
505 // m_canvas.GetAt(0).Event -= Func; // GetAt(0) might create a new RCW
507 // 2. Handler->Token is a ConditionalWeakTable because we don't want to keep the delegate alive
508 // and we want EventRegistrationTokenListWithCount to be finalized after the delegate is no longer alive
509 // 3. It is possible another COM object is created at the same address
510 // before the entry in cache is destroyed. More specifically,
511 // a. The same delegate is being unsubscribed. In this case we'll give them a
512 // stale token - unlikely to be a problem
513 // b. The same delegate is subscribed then unsubscribed. We need to make sure give
514 // them the latest token in this case. This is guaranteed by always giving the last token and always use equality to
515 // add/remove event handlers
516 internal volatile static Dictionary<EventCacheKey, EventCacheEntry> s_eventRegistrations =
517 new Dictionary<EventCacheKey, EventCacheEntry>();
519 // Prevent add/remove handler code to run at the same with with cache cleanup code
520 private volatile static MyReaderWriterLock s_eventCacheRWLock = new MyReaderWriterLock();
522 // Get InstanceKey to use in the cache
523 private static object GetInstanceKey(Action<EventRegistrationToken> removeMethod)
525 object target = removeMethod.Target;
526 Debug.Assert(target == null || Marshal.IsComObject(target), "Must be null or a RCW");
528 return removeMethod.Method.DeclaringType;
530 // Need the "Raw" IUnknown pointer for the RCW that is not bound to the current context
531 return (object)Marshal.GetRawIUnknownForComObjectNoAddRef(target);
534 private static object FindEquivalentKeyUnsafe(ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTable, object handler, out EventRegistrationTokenListWithCount tokens)
536 foreach (KeyValuePair<object, EventRegistrationTokenListWithCount> item in registrationTable)
538 if (object.Equals(item.Key, handler))
548 internal static void AddEventHandler<T>(Func<T, EventRegistrationToken> addMethod,
549 Action<EventRegistrationToken> removeMethod,
552 // The instanceKey will be IUnknown * of the target object
553 object instanceKey = GetInstanceKey(removeMethod);
555 // Call addMethod outside of RW lock
556 // At this point we don't need to worry about race conditions and we can avoid deadlocks
557 // if addMethod waits on finalizer thread
558 // If we later throw we need to remove the method
559 EventRegistrationToken token = addMethod(handler);
561 bool tokenAdded = false;
565 EventRegistrationTokenListWithCount tokens;
568 // The whole add/remove code has to be protected by a reader/writer lock
569 // Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
571 s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
574 // Add the method, and make a note of the delegate -> token mapping.
575 TokenListCount tokenListCount;
576 ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetOrCreateEventRegistrationTokenTable(instanceKey, removeMethod, out tokenListCount);
577 lock (registrationTokens)
580 // We need to find the key that equals to this handler
581 // Suppose we have 3 handlers A, B, C that are equal (refer to the same object and method),
582 // the first handler (let's say A) will be used as the key and holds all the tokens.
583 // We don't need to hold onto B and C, because the COM object itself will keep them alive,
584 // and they won't die anyway unless the COM object dies or they get unsubscribed.
585 // It may appear that it is fine to hold A, B, C, and add them and their corresponding tokens
586 // into registrationTokens table. However, this is very dangerous, because this COM object
587 // may die, but A, B, C might not get collected yet, and another COM object comes into life
588 // with the same IUnknown address, and we subscribe event B. In this case, the right token
589 // will be added into B's token list, but once we unsubscribe B, we might end up removing
590 // the last token in C, and that may lead to crash.
592 object key = FindEquivalentKeyUnsafe(registrationTokens, handler, out tokens);
595 tokens = new EventRegistrationTokenListWithCount(tokenListCount, token);
596 registrationTokens.Add(handler, tokens);
608 s_eventCacheRWLock.ReleaseReaderLock();
611 Log("[WinRT_Eventing] Event subscribed for instance = " + instanceKey + ", handler = " + handler + "\n");
615 // If we've already added the token and go there, we don't need to "UNDO" anything
618 // Otherwise, "Undo" addMethod if any exception occurs
619 // There is no need to cleanup our data structure as we haven't added the token yet
628 private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetEventRegistrationTokenTableNoCreate(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount)
630 Debug.Assert(instance != null);
631 Debug.Assert(removeMethod != null);
633 return GetEventRegistrationTokenTableInternal(instance, removeMethod, out tokenListCount, /* createIfNotFound = */ false);
636 private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetOrCreateEventRegistrationTokenTable(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount)
638 Debug.Assert(instance != null);
639 Debug.Assert(removeMethod != null);
641 return GetEventRegistrationTokenTableInternal(instance, removeMethod, out tokenListCount, /* createIfNotFound = */ true);
644 // Get the event registration token table for an event. These are indexed by the remove method of the event.
645 private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetEventRegistrationTokenTableInternal(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount, bool createIfNotFound)
647 Debug.Assert(instance != null);
648 Debug.Assert(removeMethod != null);
649 Debug.Assert(s_eventRegistrations != null);
651 EventCacheKey eventCacheKey;
652 eventCacheKey.target = instance;
653 eventCacheKey.method = removeMethod.Method;
655 lock (s_eventRegistrations)
657 EventCacheEntry eventCacheEntry;
658 if (!s_eventRegistrations.TryGetValue(eventCacheKey, out eventCacheEntry))
660 if (!createIfNotFound)
662 // No need to create an entry in this case
663 tokenListCount = null;
667 Log("[WinRT_Eventing] Adding (" + instance + "," + removeMethod.Method + ") into cache" + "\n");
669 eventCacheEntry = new EventCacheEntry();
670 eventCacheEntry.registrationTable = new ConditionalWeakTable<object, EventRegistrationTokenListWithCount>();
671 eventCacheEntry.tokenListCount = new TokenListCount(eventCacheKey);
673 s_eventRegistrations.Add(eventCacheKey, eventCacheEntry);
676 tokenListCount = eventCacheEntry.tokenListCount;
678 return eventCacheEntry.registrationTable;
682 internal static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler)
684 object instanceKey = GetInstanceKey(removeMethod);
686 EventRegistrationToken token;
689 // The whole add/remove code has to be protected by a reader/writer lock
690 // Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
692 s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
695 TokenListCount tokenListCount;
696 ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetEventRegistrationTokenTableNoCreate(instanceKey, removeMethod, out tokenListCount);
697 if (registrationTokens == null)
699 // We have no information regarding this particular instance (IUnknown*/type) - just return
700 // This is necessary to avoid leaking empty dictionary/conditionalWeakTables for this instance
701 Log("[WinRT_Eventing] no registrationTokens found for instance=" + instanceKey + ", handler= " + handler + "\n");
705 lock (registrationTokens)
707 EventRegistrationTokenListWithCount tokens;
710 // When unsubscribing events, we allow subscribing the event using a different delegate
711 // (but with the same object/method), so we need to find the first delegate that matches
712 // and unsubscribe it
713 // It actually doesn't matter which delegate - as long as it matches
714 // Note that inside TryGetValueWithValueEquality we assumes that any delegate
715 // with the same value equality would have the same hash code
716 object key = FindEquivalentKeyUnsafe(registrationTokens, handler, out tokens);
717 Debug.Assert((key != null && tokens != null) || (key == null && tokens == null),
718 "key and tokens must be both null or non-null");
721 // Failure to find a registration for a token is not an error - it's simply a no-op.
722 Log("[WinRT_Eventing] no token list found for instance=" + instanceKey + ", handler= " + handler + "\n");
726 // Select a registration token to unregister
727 // Note that we need to always get the last token just in case another COM object
728 // is created at the same address before the entry for the old one goes away.
729 // See comments above s_eventRegistrations for more details
730 bool moreItems = tokens.Pop(out token);
732 // If the last token is removed from token list, we need to remove it from the cache
733 // otherwise FindEquivalentKeyUnsafe may found this empty token list even though there could be other
734 // equivalent keys in there with non-0 token list
737 // Remove it from (handler)->(tokens)
738 // NOTE: We should not check whether registrationTokens has 0 entries and remove it from the cache
739 // (just like managed event implementation), because this might have raced with the finalizer of
740 // EventRegistrationTokenList
741 registrationTokens.Remove(key);
744 Log("[WinRT_Eventing] Event unsubscribed for managed instance = " + instanceKey + ", handler = " + handler + ", token = " + token.Value + "\n");
749 s_eventCacheRWLock.ReleaseReaderLock();
752 // Call removeMethod outside of RW lock
753 // At this point we don't need to worry about race conditions and we can avoid deadlocks
754 // if removeMethod waits on finalizer thread
758 internal static void RemoveAllEventHandlers(Action<EventRegistrationToken> removeMethod)
760 object instanceKey = GetInstanceKey(removeMethod);
762 List<EventRegistrationToken> tokensToRemove = new List<EventRegistrationToken>();
765 // The whole add/remove code has to be protected by a reader/writer lock
766 // Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
768 s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
771 TokenListCount tokenListCount;
772 ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetEventRegistrationTokenTableNoCreate(instanceKey, removeMethod, out tokenListCount);
773 if (registrationTokens == null)
775 // We have no information regarding this particular instance (IUnknown*/type) - just return
776 // This is necessary to avoid leaking empty dictionary/conditionalWeakTables for this instance
780 lock (registrationTokens)
782 // Copy all tokens to tokensToRemove array which later we'll call removeMethod on
784 foreach (KeyValuePair<object, EventRegistrationTokenListWithCount> item in registrationTokens)
786 item.Value.CopyTo(tokensToRemove);
789 // Clear the table - at this point all event handlers are no longer in the cache
790 // but they are not removed yet
791 registrationTokens.Clear();
792 Log("[WinRT_Eventing] Cache cleared for managed instance = " + instanceKey + "\n");
797 s_eventCacheRWLock.ReleaseReaderLock();
801 // Remove all handlers outside the lock
803 Log("[WinRT_Eventing] Start removing all events for instance = " + instanceKey + "\n");
804 CallRemoveMethods(removeMethod, tokensToRemove);
805 Log("[WinRT_Eventing] Finished removing all events for instance = " + instanceKey + "\n");
809 internal class ReaderWriterLockTimedOutException : ApplicationException
813 /// Discussed @ https://blogs.msdn.microsoft.com/vancem/2006/03/29/analysis-of-reader-writer-lock/
816 /// A reader-writer lock implementation that is intended to be simple, yet very
817 /// efficient. In particular only 1 interlocked operation is taken for any lock
818 /// operation (we use spin locks to achieve this). The spin lock is never held
819 /// for more than a few instructions (in particular, we never call event APIs
820 /// or in fact any non-trivial API while holding the spin lock).
822 /// Currently this ReaderWriterLock does not support recursion, however it is
825 internal class MyReaderWriterLock
827 // Lock specifiation for myLock: This lock protects exactly the local fields associted
828 // instance of MyReaderWriterLock. It does NOT protect the memory associted with the
829 // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
832 // Who owns the lock owners > 0 => readers
833 // owners = -1 means there is one writer. Owners must be >= -1.
836 // These variables allow use to avoid Setting events (which is expensive) if we don't have to.
837 private uint numWriteWaiters; // maximum number of threads that can be doing a WaitOne on the writeEvent
838 private uint numReadWaiters; // maximum number of threads that can be doing a WaitOne on the readEvent
840 // conditions we wait on.
841 private EventWaitHandle writeEvent; // threads waiting to acquire a write lock go here.
842 private EventWaitHandle readEvent; // threads waiting to acquire a read lock go here (will be released in bulk)
844 internal MyReaderWriterLock()
846 // All state can start out zeroed.
849 internal void AcquireReaderLock(int millisecondsTimeout)
854 // We can enter a read lock if there are only read-locks have been given out
855 // and a writer is not trying to get in.
856 if (owners >= 0 && numWriteWaiters == 0)
858 // Good case, there is no contention, we are basically done
859 owners++; // Indicate we have another reader
863 // Drat, we need to wait. Mark that we have waiters and wait.
864 if (readEvent == null) // Create the needed event
866 LazyCreateEvent(ref readEvent, false);
867 continue; // since we left the lock, start over.
870 WaitOnEvent(readEvent, ref numReadWaiters, millisecondsTimeout);
875 internal void AcquireWriterLock(int millisecondsTimeout)
882 // Good case, there is no contention, we are basically done
883 owners = -1; // indicate we have a writer.
887 // Drat, we need to wait. Mark that we have waiters and wait.
888 if (writeEvent == null) // create the needed event.
890 LazyCreateEvent(ref writeEvent, true);
891 continue; // since we left the lock, start over.
894 WaitOnEvent(writeEvent, ref numWriteWaiters, millisecondsTimeout);
899 internal void ReleaseReaderLock()
902 Debug.Assert(owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
904 ExitAndWakeUpAppropriateWaiters();
907 internal void ReleaseWriterLock()
910 Debug.Assert(owners == -1, "Calling ReleaseWriterLock when no write lock is held");
912 ExitAndWakeUpAppropriateWaiters();
916 /// A routine for lazily creating a event outside the lock (so if errors
917 /// happen they are outside the lock and that we don't do much work
918 /// while holding a spin lock). If all goes well, reenter the lock and
921 private void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
923 Debug.Assert(myLock != 0, "Lock must be held");
924 Debug.Assert(waitEvent == null, "Wait event must be null");
927 EventWaitHandle newEvent;
928 if (makeAutoResetEvent)
929 newEvent = new AutoResetEvent(false);
931 newEvent = new ManualResetEvent(false);
933 if (waitEvent == null) // maybe someone snuck in.
934 waitEvent = newEvent;
938 /// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.
939 /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
941 private void WaitOnEvent(EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
943 Debug.Assert(myLock != 0, "Lock must be held");
948 bool waitSuccessful = false;
949 ExitMyLock(); // Do the wait outside of any lock
952 if (!waitEvent.WaitOne(millisecondsTimeout, false))
953 throw new ReaderWriterLockTimedOutException();
955 waitSuccessful = true;
961 if (!waitSuccessful) // We are going to throw for some reason. Exit myLock.
967 /// Determines the appropriate events to set, leaves the locks, and sets the events.
969 private void ExitAndWakeUpAppropriateWaiters()
971 Debug.Assert(myLock != 0, "Lock must be held");
973 if (owners == 0 && numWriteWaiters > 0)
975 ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock)
976 writeEvent.Set(); // release one writer.
978 else if (owners >= 0 && numReadWaiters != 0)
980 ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock)
981 readEvent.Set(); // release all readers.
987 private void EnterMyLock()
989 if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
993 private void EnterMyLockSpin()
995 for (int i = 0; ; i++)
997 if (i < 3 && Environment.ProcessorCount > 1)
998 Thread.SpinWait(20); // Wait a few dozen instructions to let another processor release lock.
1000 Thread.Sleep(0); // Give up my quantum.
1002 if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
1006 private void ExitMyLock()
1008 Debug.Assert(myLock != 0, "Exiting spin lock that is not held");
1015 // Call removeMethod on each token and aggregate all exceptions thrown from removeMethod into one in case of failure
1017 internal static void CallRemoveMethods(Action<EventRegistrationToken> removeMethod, List<EventRegistrationToken> tokensToRemove)
1019 List<Exception> exceptions = new List<Exception>();
1021 foreach (EventRegistrationToken token in tokensToRemove)
1025 removeMethod(token);
1027 catch (Exception ex)
1032 Log("[WinRT_Eventing] Event unsubscribed for token = " + token.Value + "\n");
1035 if (exceptions.Count > 0)
1036 throw new AggregateException(exceptions.ToArray());
1039 internal static unsafe string HStringToString(IntPtr hstring)
1041 Debug.Assert(Environment.IsWinRTSupported);
1043 // There is no difference between a null and empty HSTRING
1044 if (hstring == IntPtr.Zero)
1046 return string.Empty;
1052 char* rawBuffer = UnsafeNativeMethods.WindowsGetStringRawBuffer(hstring, &length);
1053 return new string(rawBuffer, 0, checked((int)length));
1057 internal static Exception GetExceptionForHR(int hresult, Exception innerException, string messageResource)
1060 if (innerException != null)
1062 string message = innerException.Message;
1063 if (message == null && messageResource != null)
1065 message = SR.GetResourceString(messageResource);
1067 e = new Exception(message, innerException);
1071 string message = (messageResource != null ? SR.GetResourceString(messageResource): null);
1072 e = new Exception(message);
1075 e.HResult = hresult;
1079 internal static Exception GetExceptionForHR(int hresult, Exception innerException)
1081 return GetExceptionForHR(hresult, innerException, null);
1084 private static bool s_haveBlueErrorApis = true;
1086 private static bool RoOriginateLanguageException(int error, string message, IntPtr languageException)
1088 if (s_haveBlueErrorApis)
1092 return UnsafeNativeMethods.RoOriginateLanguageException(error, message, languageException);
1094 catch (EntryPointNotFoundException)
1096 s_haveBlueErrorApis = false;
1103 private static void RoReportUnhandledError(IRestrictedErrorInfo error)
1105 if (s_haveBlueErrorApis)
1109 UnsafeNativeMethods.RoReportUnhandledError(error);
1111 catch (EntryPointNotFoundException)
1113 s_haveBlueErrorApis = false;
1118 private static Guid s_iidIErrorInfo = new Guid(0x1CF2B120, 0x547D, 0x101B, 0x8E, 0x65, 0x08, 0x00, 0x2B, 0x2B, 0xD1, 0x19);
1121 /// Report that an exception has occurred which went user unhandled. This allows the global error handler
1122 /// for the application to be invoked to process the error.
1124 /// <returns>true if the error was reported, false if not (ie running on Win8)</returns>
1125 internal static bool ReportUnhandledError(Exception e)
1127 // Only report to the WinRT global exception handler in modern apps
1128 if (!ApplicationModel.IsUap)
1133 // If we don't have the capability to report to the global error handler, early out
1134 if (!s_haveBlueErrorApis)
1141 IntPtr exceptionIUnknown = IntPtr.Zero;
1142 IntPtr exceptionIErrorInfo = IntPtr.Zero;
1145 // Get an IErrorInfo for the current exception and originate it as a langauge error in order to have
1146 // Windows generate an IRestrictedErrorInfo corresponding to the exception object. We can then
1147 // notify the global error handler that this IRestrictedErrorInfo instance represents an exception that
1148 // went unhandled in managed code.
1150 // Note that we need to get an IUnknown for the exception object and then QI for IErrorInfo since Exception
1151 // doesn't implement IErrorInfo in managed code - only its CCW does.
1152 exceptionIUnknown = Marshal.GetIUnknownForObject(e);
1153 if (exceptionIUnknown != IntPtr.Zero)
1155 Marshal.QueryInterface(exceptionIUnknown, ref s_iidIErrorInfo, out exceptionIErrorInfo);
1156 if (exceptionIErrorInfo != IntPtr.Zero)
1158 if (RoOriginateLanguageException(Marshal.GetHRForException(e), e.Message, exceptionIErrorInfo))
1160 IRestrictedErrorInfo restrictedError = UnsafeNativeMethods.GetRestrictedErrorInfo();
1161 if (restrictedError != null)
1163 RoReportUnhandledError(restrictedError);
1172 if (exceptionIErrorInfo != IntPtr.Zero)
1174 Marshal.Release(exceptionIErrorInfo);
1177 if (exceptionIUnknown != IntPtr.Zero)
1179 Marshal.Release(exceptionIUnknown);
1184 // If we got here, then some step of the marshaling failed, which means the GEH was not invoked
1188 #if FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
1189 // Get an IActivationFactory * for a managed type
1190 internal static IntPtr GetActivationFactoryForType(Type type)
1192 ManagedActivationFactory activationFactory = GetManagedActivationFactory(type);
1193 return Marshal.GetComInterfaceForObject(activationFactory, typeof(IActivationFactory));
1196 internal static ManagedActivationFactory GetManagedActivationFactory(Type type)
1198 ManagedActivationFactory activationFactory = new ManagedActivationFactory(type);
1200 // If the type has any associated factory interfaces (i.e. supports non-default activation
1201 // or has statics), the CCW for this instance of ManagedActivationFactory must support them.
1202 InitializeManagedWinRTFactoryObject(activationFactory, (RuntimeType)type);
1203 return activationFactory;
1207 #endif // FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
1210 // Get activation factory object for a specified WinRT type
1211 // If the WinRT type is a native type, we'll always create a unique RCW for it,
1212 // This is necessary because WinRT factories are often implemented as a singleton,
1213 // and getting back a RCW for such WinRT factory would usually get back a RCW from
1214 // another apartment, even if the interface pointe returned from GetActivationFactory
1215 // is a raw pointer. As a result, user would randomly get back RCWs for activation
1216 // factories from other apartments and make transiton to those apartments and cause
1217 // deadlocks and create objects in incorrect apartments
1219 public static IActivationFactory GetActivationFactory(Type type)
1222 throw new ArgumentNullException(nameof(type));
1224 if (type.IsWindowsRuntimeObject && type.IsImport)
1226 return (IActivationFactory)GetNativeActivationFactory(type);
1230 #if FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
1231 return GetManagedActivationFactory(type);
1233 // Managed factories are not supported so as to minimize public surface (and test effort)
1234 throw new NotSupportedException();
1239 // HSTRING marshaling methods:
1241 public static IntPtr StringToHString(string s)
1243 if (!Environment.IsWinRTSupported)
1244 throw new PlatformNotSupportedException(SR.PlatformNotSupported_WinRT);
1247 throw new ArgumentNullException(nameof(s));
1252 int hrCreate = UnsafeNativeMethods.WindowsCreateString(s, s.Length, &hstring);
1253 Marshal.ThrowExceptionForHR(hrCreate, new IntPtr(-1));
1258 public static string PtrToStringHString(IntPtr ptr)
1260 if (!Environment.IsWinRTSupported)
1262 throw new PlatformNotSupportedException(SR.PlatformNotSupported_WinRT);
1265 return HStringToString(ptr);
1268 public static void FreeHString(IntPtr ptr)
1270 if (!Environment.IsWinRTSupported)
1271 throw new PlatformNotSupportedException(SR.PlatformNotSupported_WinRT);
1273 if (ptr != IntPtr.Zero)
1275 UnsafeNativeMethods.WindowsDeleteString(ptr);
1279 [MethodImpl(MethodImplOptions.InternalCall)]
1280 public static extern object GetUniqueObjectForIUnknownWithoutUnboxing(IntPtr unknown);
1282 [MethodImpl(MethodImplOptions.InternalCall)]
1283 internal static extern void InitializeWrapper(object o, ref IntPtr pUnk);
1286 /// Converts the CLR exception to an HRESULT. This function also sets
1287 /// up an IErrorInfo for the exception.
1288 /// This function is only used in WinRT and converts ObjectDisposedException
1291 [MethodImpl(MethodImplOptions.InternalCall)]
1292 internal static extern int GetHRForException(Exception e);
1295 #if FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
1296 [MethodImpl(MethodImplOptions.InternalCall)]
1297 internal static extern void InitializeManagedWinRTFactoryObject(object o, RuntimeType runtimeClassType);
1301 /// Create activation factory and wraps it with a unique RCW.
1303 [MethodImpl(MethodImplOptions.InternalCall)]
1304 internal static extern object GetNativeActivationFactory(Type type);
1306 [Conditional("_LOGGING")]
1307 private static void Log(string s)
1309 // Internal.Console.WriteLine(s);