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.m_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
356 internal object target;
357 internal MethodInfo method;
359 public override string ToString()
361 return "(" + target + ", " + method + ")";
365 internal class EventCacheKeyEqualityComparer : IEqualityComparer<EventCacheKey>
367 public bool Equals(EventCacheKey lhs, EventCacheKey rhs)
369 return (Object.Equals(lhs.target, rhs.target) && Object.Equals(lhs.method, rhs.method));
372 public int GetHashCode(EventCacheKey key)
374 return key.target.GetHashCode() ^ key.method.GetHashCode();
379 // EventRegistrationTokenListWithCount
381 // A list of EventRegistrationTokens that maintains a count
383 // The reason this needs to be a separate class is that we need a finalizer for this class
384 // If the delegate is collected, it will take this list away with it (due to dependent handles),
385 // and we need to remove the PerInstancEntry from cache
386 // See ~EventRegistrationTokenListWithCount for more details
388 internal class EventRegistrationTokenListWithCount
390 private TokenListCount _tokenListCount;
391 private EventRegistrationTokenList _tokenList;
393 internal EventRegistrationTokenListWithCount(TokenListCount tokenListCount, EventRegistrationToken token)
395 _tokenListCount = tokenListCount;
396 _tokenListCount.Inc();
398 _tokenList = new EventRegistrationTokenList(token);
401 ~EventRegistrationTokenListWithCount()
403 // Decrement token list count
404 // This is need to correctly keep trace of number of tokens for EventCacheKey
405 // and remove it from cache when the token count drop to 0
406 // we don't need to take locks for decrement the count - we only need to take a global
407 // lock when we decide to destroy cache for the IUnknown */type instance
408 Log("[WinRT_Eventing] Finalizing EventRegistrationTokenList for " + _tokenListCount.Key + "\n");
409 _tokenListCount.Dec();
412 public void Push(EventRegistrationToken token)
414 // Since EventRegistrationTokenListWithCount is a reference type, there is no need
415 // to copy back. Ignore the return value
416 _tokenList.Push(token);
419 public bool Pop(out EventRegistrationToken token)
421 return _tokenList.Pop(out token);
424 public void CopyTo(List<EventRegistrationToken> tokens)
426 _tokenList.CopyTo(tokens);
431 // Maintains the number of tokens for a particular EventCacheKey
432 // TokenListCount is a class for two reasons:
433 // 1. Efficient update in the Dictionary to avoid lookup twice to update the value
434 // 2. Update token count without taking a global lock. Only takes a global lock when drop to 0
436 internal class TokenListCount
439 private EventCacheKey _key;
441 internal TokenListCount(EventCacheKey key)
446 internal EventCacheKey Key
453 int newCount = Interlocked.Increment(ref _count);
454 Log("[WinRT_Eventing] Incremented TokenListCount for " + _key + ", Value = " + newCount + "\n");
459 // Avoid racing with Add/Remove event entries into the cache
460 // You don't want this removing the key in the middle of a Add/Remove
461 s_eventCacheRWLock.AcquireWriterLock(Timeout.Infinite);
464 int newCount = Interlocked.Decrement(ref _count);
465 Log("[WinRT_Eventing] Decremented TokenListCount for " + _key + ", Value = " + newCount + "\n");
471 s_eventCacheRWLock.ReleaseWriterLock();
475 private void CleanupCache()
477 // Time to destroy cache for this IUnknown */type instance
478 // because the total token list count has dropped to 0 and we don't have any events subscribed
479 Debug.Assert(s_eventRegistrations != null);
481 Log("[WinRT_Eventing] Removing " + _key + " from cache" + "\n");
482 s_eventRegistrations.Remove(_key);
483 Log("[WinRT_Eventing] s_eventRegistrations size = " + s_eventRegistrations.Count + "\n");
487 internal struct EventCacheEntry
489 // [Handler] -> Token
490 internal ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTable;
492 // Maintains current total count for the EventRegistrationTokenListWithCount for this event cache key
493 internal TokenListCount tokenListCount;
496 // Mappings of delegates registered for events -> their registration tokens.
497 // These mappings are stored indexed by the remove method which can be used to undo the registrations.
499 // The full structure of this table is:
500 // EventCacheKey (instanceKey, eventMethod) -> EventCacheEntry (Handler->tokens)
502 // A InstanceKey is the IUnknown * or static type instance
504 // Couple of things to note:
505 // 1. We need to use IUnknown* because we want to be able to unscribe to the event for another RCW
506 // based on the same COM object. For example:
507 // m_canvas.GetAt(0).Event += Func;
508 // m_canvas.GetAt(0).Event -= Func; // GetAt(0) might create a new RCW
510 // 2. Handler->Token is a ConditionalWeakTable because we don't want to keep the delegate alive
511 // and we want EventRegistrationTokenListWithCount to be finalized after the delegate is no longer alive
512 // 3. It is possible another COM object is created at the same address
513 // before the entry in cache is destroyed. More specifically,
514 // a. The same delegate is being unsubscribed. In this case we'll give them a
515 // stale token - unlikely to be a problem
516 // b. The same delegate is subscribed then unsubscribed. We need to make sure give
517 // them the latest token in this case. This is guaranteed by always giving the last token and always use equality to
518 // add/remove event handlers
519 internal volatile static Dictionary<EventCacheKey, EventCacheEntry> s_eventRegistrations =
520 new Dictionary<EventCacheKey, EventCacheEntry>(new EventCacheKeyEqualityComparer());
522 // Prevent add/remove handler code to run at the same with with cache cleanup code
523 private volatile static MyReaderWriterLock s_eventCacheRWLock = new MyReaderWriterLock();
525 // Get InstanceKey to use in the cache
526 private static object GetInstanceKey(Action<EventRegistrationToken> removeMethod)
528 object target = removeMethod.Target;
529 Debug.Assert(target == null || Marshal.IsComObject(target), "Must be null or a RCW");
531 return removeMethod.Method.DeclaringType;
533 // Need the "Raw" IUnknown pointer for the RCW that is not bound to the current context
534 return (object)Marshal.GetRawIUnknownForComObjectNoAddRef(target);
537 private static object FindEquivalentKeyUnsafe(ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTable, object handler, out EventRegistrationTokenListWithCount tokens)
539 foreach (KeyValuePair<object, EventRegistrationTokenListWithCount> item in registrationTable)
541 if (Object.Equals(item.Key, handler))
551 internal static void AddEventHandler<T>(Func<T, EventRegistrationToken> addMethod,
552 Action<EventRegistrationToken> removeMethod,
555 // The instanceKey will be IUnknown * of the target object
556 object instanceKey = GetInstanceKey(removeMethod);
558 // Call addMethod outside of RW lock
559 // At this point we don't need to worry about race conditions and we can avoid deadlocks
560 // if addMethod waits on finalizer thread
561 // If we later throw we need to remove the method
562 EventRegistrationToken token = addMethod(handler);
564 bool tokenAdded = false;
568 EventRegistrationTokenListWithCount tokens;
571 // The whole add/remove code has to be protected by a reader/writer lock
572 // Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
574 s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
577 // Add the method, and make a note of the delegate -> token mapping.
578 TokenListCount tokenListCount;
579 ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetOrCreateEventRegistrationTokenTable(instanceKey, removeMethod, out tokenListCount);
580 lock (registrationTokens)
583 // We need to find the key that equals to this handler
584 // Suppose we have 3 handlers A, B, C that are equal (refer to the same object and method),
585 // the first handler (let's say A) will be used as the key and holds all the tokens.
586 // We don't need to hold onto B and C, because the COM object itself will keep them alive,
587 // and they won't die anyway unless the COM object dies or they get unsubscribed.
588 // It may appear that it is fine to hold A, B, C, and add them and their corresponding tokens
589 // into registrationTokens table. However, this is very dangerous, because this COM object
590 // may die, but A, B, C might not get collected yet, and another COM object comes into life
591 // with the same IUnknown address, and we subscribe event B. In this case, the right token
592 // will be added into B's token list, but once we unsubscribe B, we might end up removing
593 // the last token in C, and that may lead to crash.
595 object key = FindEquivalentKeyUnsafe(registrationTokens, handler, out tokens);
598 tokens = new EventRegistrationTokenListWithCount(tokenListCount, token);
599 registrationTokens.Add(handler, tokens);
611 s_eventCacheRWLock.ReleaseReaderLock();
614 Log("[WinRT_Eventing] Event subscribed for instance = " + instanceKey + ", handler = " + handler + "\n");
618 // If we've already added the token and go there, we don't need to "UNDO" anything
621 // Otherwise, "Undo" addMethod if any exception occurs
622 // There is no need to cleanup our data structure as we haven't added the token yet
631 private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetEventRegistrationTokenTableNoCreate(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount)
633 Debug.Assert(instance != null);
634 Debug.Assert(removeMethod != null);
636 return GetEventRegistrationTokenTableInternal(instance, removeMethod, out tokenListCount, /* createIfNotFound = */ false);
639 private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetOrCreateEventRegistrationTokenTable(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount)
641 Debug.Assert(instance != null);
642 Debug.Assert(removeMethod != null);
644 return GetEventRegistrationTokenTableInternal(instance, removeMethod, out tokenListCount, /* createIfNotFound = */ true);
647 // Get the event registration token table for an event. These are indexed by the remove method of the event.
648 private static ConditionalWeakTable<object, EventRegistrationTokenListWithCount> GetEventRegistrationTokenTableInternal(object instance, Action<EventRegistrationToken> removeMethod, out TokenListCount tokenListCount, bool createIfNotFound)
650 Debug.Assert(instance != null);
651 Debug.Assert(removeMethod != null);
652 Debug.Assert(s_eventRegistrations != null);
654 EventCacheKey eventCacheKey;
655 eventCacheKey.target = instance;
656 eventCacheKey.method = removeMethod.Method;
658 lock (s_eventRegistrations)
660 EventCacheEntry eventCacheEntry;
661 if (!s_eventRegistrations.TryGetValue(eventCacheKey, out eventCacheEntry))
663 if (!createIfNotFound)
665 // No need to create an entry in this case
666 tokenListCount = null;
670 Log("[WinRT_Eventing] Adding (" + instance + "," + removeMethod.Method + ") into cache" + "\n");
672 eventCacheEntry = new EventCacheEntry();
673 eventCacheEntry.registrationTable = new ConditionalWeakTable<object, EventRegistrationTokenListWithCount>();
674 eventCacheEntry.tokenListCount = new TokenListCount(eventCacheKey);
676 s_eventRegistrations.Add(eventCacheKey, eventCacheEntry);
679 tokenListCount = eventCacheEntry.tokenListCount;
681 return eventCacheEntry.registrationTable;
685 internal static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler)
687 object instanceKey = GetInstanceKey(removeMethod);
689 EventRegistrationToken token;
692 // The whole add/remove code has to be protected by a reader/writer lock
693 // Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
695 s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
698 TokenListCount tokenListCount;
699 ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetEventRegistrationTokenTableNoCreate(instanceKey, removeMethod, out tokenListCount);
700 if (registrationTokens == null)
702 // We have no information regarding this particular instance (IUnknown*/type) - just return
703 // This is necessary to avoid leaking empty dictionary/conditionalWeakTables for this instance
704 Log("[WinRT_Eventing] no registrationTokens found for instance=" + instanceKey + ", handler= " + handler + "\n");
708 lock (registrationTokens)
710 EventRegistrationTokenListWithCount tokens;
713 // When unsubscribing events, we allow subscribing the event using a different delegate
714 // (but with the same object/method), so we need to find the first delegate that matches
715 // and unsubscribe it
716 // It actually doesn't matter which delegate - as long as it matches
717 // Note that inside TryGetValueWithValueEquality we assumes that any delegate
718 // with the same value equality would have the same hash code
719 object key = FindEquivalentKeyUnsafe(registrationTokens, handler, out tokens);
720 Debug.Assert((key != null && tokens != null) || (key == null && tokens == null),
721 "key and tokens must be both null or non-null");
724 // Failure to find a registration for a token is not an error - it's simply a no-op.
725 Log("[WinRT_Eventing] no token list found for instance=" + instanceKey + ", handler= " + handler + "\n");
729 // Select a registration token to unregister
730 // Note that we need to always get the last token just in case another COM object
731 // is created at the same address before the entry for the old one goes away.
732 // See comments above s_eventRegistrations for more details
733 bool moreItems = tokens.Pop(out token);
735 // If the last token is removed from token list, we need to remove it from the cache
736 // otherwise FindEquivalentKeyUnsafe may found this empty token list even though there could be other
737 // equivalent keys in there with non-0 token list
740 // Remove it from (handler)->(tokens)
741 // NOTE: We should not check whether registrationTokens has 0 entries and remove it from the cache
742 // (just like managed event implementation), because this might have raced with the finalizer of
743 // EventRegistrationTokenList
744 registrationTokens.Remove(key);
747 Log("[WinRT_Eventing] Event unsubscribed for managed instance = " + instanceKey + ", handler = " + handler + ", token = " + token.m_value + "\n");
752 s_eventCacheRWLock.ReleaseReaderLock();
755 // Call removeMethod outside of RW lock
756 // At this point we don't need to worry about race conditions and we can avoid deadlocks
757 // if removeMethod waits on finalizer thread
761 internal static void RemoveAllEventHandlers(Action<EventRegistrationToken> removeMethod)
763 object instanceKey = GetInstanceKey(removeMethod);
765 List<EventRegistrationToken> tokensToRemove = new List<EventRegistrationToken>();
768 // The whole add/remove code has to be protected by a reader/writer lock
769 // Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time
771 s_eventCacheRWLock.AcquireReaderLock(Timeout.Infinite);
774 TokenListCount tokenListCount;
775 ConditionalWeakTable<object, EventRegistrationTokenListWithCount> registrationTokens = GetEventRegistrationTokenTableNoCreate(instanceKey, removeMethod, out tokenListCount);
776 if (registrationTokens == null)
778 // We have no information regarding this particular instance (IUnknown*/type) - just return
779 // This is necessary to avoid leaking empty dictionary/conditionalWeakTables for this instance
783 lock (registrationTokens)
785 // Copy all tokens to tokensToRemove array which later we'll call removeMethod on
787 foreach (KeyValuePair<object, EventRegistrationTokenListWithCount> item in registrationTokens)
789 item.Value.CopyTo(tokensToRemove);
792 // Clear the table - at this point all event handlers are no longer in the cache
793 // but they are not removed yet
794 registrationTokens.Clear();
795 Log("[WinRT_Eventing] Cache cleared for managed instance = " + instanceKey + "\n");
800 s_eventCacheRWLock.ReleaseReaderLock();
804 // Remove all handlers outside the lock
806 Log("[WinRT_Eventing] Start removing all events for instance = " + instanceKey + "\n");
807 CallRemoveMethods(removeMethod, tokensToRemove);
808 Log("[WinRT_Eventing] Finished removing all events for instance = " + instanceKey + "\n");
812 internal class ReaderWriterLockTimedOutException : ApplicationException
816 /// Discussed @ https://blogs.msdn.microsoft.com/vancem/2006/03/29/analysis-of-reader-writer-lock/
819 /// A reader-writer lock implementation that is intended to be simple, yet very
820 /// efficient. In particular only 1 interlocked operation is taken for any lock
821 /// operation (we use spin locks to achieve this). The spin lock is never held
822 /// for more than a few instructions (in particular, we never call event APIs
823 /// or in fact any non-trivial API while holding the spin lock).
825 /// Currently this ReaderWriterLock does not support recursion, however it is
828 internal class MyReaderWriterLock
830 // Lock specifiation for myLock: This lock protects exactly the local fields associted
831 // instance of MyReaderWriterLock. It does NOT protect the memory associted with the
832 // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
835 // Who owns the lock owners > 0 => readers
836 // owners = -1 means there is one writer. Owners must be >= -1.
839 // These variables allow use to avoid Setting events (which is expensive) if we don't have to.
840 private uint numWriteWaiters; // maximum number of threads that can be doing a WaitOne on the writeEvent
841 private uint numReadWaiters; // maximum number of threads that can be doing a WaitOne on the readEvent
843 // conditions we wait on.
844 private EventWaitHandle writeEvent; // threads waiting to aquire a write lock go here.
845 private EventWaitHandle readEvent; // threads waiting to aquire a read lock go here (will be released in bulk)
847 internal MyReaderWriterLock()
849 // All state can start out zeroed.
852 internal void AcquireReaderLock(int millisecondsTimeout)
857 // We can enter a read lock if there are only read-locks have been given out
858 // and a writer is not trying to get in.
859 if (owners >= 0 && numWriteWaiters == 0)
861 // Good case, there is no contention, we are basically done
862 owners++; // Indicate we have another reader
866 // Drat, we need to wait. Mark that we have waiters and wait.
867 if (readEvent == null) // Create the needed event
869 LazyCreateEvent(ref readEvent, false);
870 continue; // since we left the lock, start over.
873 WaitOnEvent(readEvent, ref numReadWaiters, millisecondsTimeout);
878 internal void AcquireWriterLock(int millisecondsTimeout)
885 // Good case, there is no contention, we are basically done
886 owners = -1; // indicate we have a writer.
890 // Drat, we need to wait. Mark that we have waiters and wait.
891 if (writeEvent == null) // create the needed event.
893 LazyCreateEvent(ref writeEvent, true);
894 continue; // since we left the lock, start over.
897 WaitOnEvent(writeEvent, ref numWriteWaiters, millisecondsTimeout);
902 internal void ReleaseReaderLock()
905 Debug.Assert(owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
907 ExitAndWakeUpAppropriateWaiters();
910 internal void ReleaseWriterLock()
913 Debug.Assert(owners == -1, "Calling ReleaseWriterLock when no write lock is held");
915 ExitAndWakeUpAppropriateWaiters();
919 /// A routine for lazily creating a event outside the lock (so if errors
920 /// happen they are outside the lock and that we don't do much work
921 /// while holding a spin lock). If all goes well, reenter the lock and
924 private void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
926 Debug.Assert(myLock != 0, "Lock must be held");
927 Debug.Assert(waitEvent == null, "Wait event must be null");
930 EventWaitHandle newEvent;
931 if (makeAutoResetEvent)
932 newEvent = new AutoResetEvent(false);
934 newEvent = new ManualResetEvent(false);
936 if (waitEvent == null) // maybe someone snuck in.
937 waitEvent = newEvent;
941 /// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.
942 /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
944 private void WaitOnEvent(EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
946 Debug.Assert(myLock != 0, "Lock must be held");
951 bool waitSuccessful = false;
952 ExitMyLock(); // Do the wait outside of any lock
955 if (!waitEvent.WaitOne(millisecondsTimeout, false))
956 throw new ReaderWriterLockTimedOutException();
958 waitSuccessful = true;
964 if (!waitSuccessful) // We are going to throw for some reason. Exit myLock.
970 /// Determines the appropriate events to set, leaves the locks, and sets the events.
972 private void ExitAndWakeUpAppropriateWaiters()
974 Debug.Assert(myLock != 0, "Lock must be held");
976 if (owners == 0 && numWriteWaiters > 0)
978 ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock)
979 writeEvent.Set(); // release one writer.
981 else if (owners >= 0 && numReadWaiters != 0)
983 ExitMyLock(); // Exit before signaling to improve efficiency (wakee will need the lock)
984 readEvent.Set(); // release all readers.
990 private void EnterMyLock()
992 if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
996 private void EnterMyLockSpin()
998 for (int i = 0; ; i++)
1000 if (i < 3 && Environment.ProcessorCount > 1)
1001 Thread.SpinWait(20); // Wait a few dozen instructions to let another processor release lock.
1003 Thread.Sleep(0); // Give up my quantum.
1005 if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
1009 private void ExitMyLock()
1011 Debug.Assert(myLock != 0, "Exiting spin lock that is not held");
1018 // Call removeMethod on each token and aggregate all exceptions thrown from removeMethod into one in case of failure
1020 internal static void CallRemoveMethods(Action<EventRegistrationToken> removeMethod, List<EventRegistrationToken> tokensToRemove)
1022 List<Exception> exceptions = new List<Exception>();
1024 foreach (EventRegistrationToken token in tokensToRemove)
1028 removeMethod(token);
1030 catch (Exception ex)
1035 Log("[WinRT_Eventing] Event unsubscribed for token = " + token.m_value + "\n");
1038 if (exceptions.Count > 0)
1039 throw new AggregateException(exceptions.ToArray());
1042 internal static unsafe string HStringToString(IntPtr hstring)
1044 Debug.Assert(Environment.IsWinRTSupported);
1046 // There is no difference between a null and empty HSTRING
1047 if (hstring == IntPtr.Zero)
1049 return String.Empty;
1055 char* rawBuffer = UnsafeNativeMethods.WindowsGetStringRawBuffer(hstring, &length);
1056 return new String(rawBuffer, 0, checked((int)length));
1060 internal static Exception GetExceptionForHR(int hresult, Exception innerException, string messageResource)
1063 if (innerException != null)
1065 string message = innerException.Message;
1066 if (message == null && messageResource != null)
1068 message = SR.GetResourceString(messageResource);
1070 e = new Exception(message, innerException);
1074 string message = (messageResource != null ? SR.GetResourceString(messageResource): null);
1075 e = new Exception(message);
1078 e.SetErrorCode(hresult);
1082 internal static Exception GetExceptionForHR(int hresult, Exception innerException)
1084 return GetExceptionForHR(hresult, innerException, null);
1087 private static bool s_haveBlueErrorApis = true;
1089 private static bool RoOriginateLanguageException(int error, string message, IntPtr languageException)
1091 if (s_haveBlueErrorApis)
1095 return UnsafeNativeMethods.RoOriginateLanguageException(error, message, languageException);
1097 catch (EntryPointNotFoundException)
1099 s_haveBlueErrorApis = false;
1106 private static void RoReportUnhandledError(IRestrictedErrorInfo error)
1108 if (s_haveBlueErrorApis)
1112 UnsafeNativeMethods.RoReportUnhandledError(error);
1114 catch (EntryPointNotFoundException)
1116 s_haveBlueErrorApis = false;
1121 private static Guid s_iidIErrorInfo = new Guid(0x1CF2B120, 0x547D, 0x101B, 0x8E, 0x65, 0x08, 0x00, 0x2B, 0x2B, 0xD1, 0x19);
1124 /// Report that an exception has occurred which went user unhandled. This allows the global error handler
1125 /// for the application to be invoked to process the error.
1127 /// <returns>true if the error was reported, false if not (ie running on Win8)</returns>
1128 // [FriendAccessAllowed]
1129 internal static bool ReportUnhandledError(Exception e)
1131 // Only report to the WinRT global exception handler in modern apps
1132 if (!AppDomain.IsAppXModel())
1137 // If we don't have the capability to report to the global error handler, early out
1138 if (!s_haveBlueErrorApis)
1145 IntPtr exceptionIUnknown = IntPtr.Zero;
1146 IntPtr exceptionIErrorInfo = IntPtr.Zero;
1149 // Get an IErrorInfo for the current exception and originate it as a langauge error in order to have
1150 // Windows generate an IRestrictedErrorInfo corresponding to the exception object. We can then
1151 // notify the global error handler that this IRestrictedErrorInfo instance represents an exception that
1152 // went unhandled in managed code.
1154 // Note that we need to get an IUnknown for the exception object and then QI for IErrorInfo since Exception
1155 // doesn't implement IErrorInfo in managed code - only its CCW does.
1156 exceptionIUnknown = Marshal.GetIUnknownForObject(e);
1157 if (exceptionIUnknown != IntPtr.Zero)
1159 Marshal.QueryInterface(exceptionIUnknown, ref s_iidIErrorInfo, out exceptionIErrorInfo);
1160 if (exceptionIErrorInfo != IntPtr.Zero)
1162 if (RoOriginateLanguageException(Marshal.GetHRForException_WinRT(e), e.Message, exceptionIErrorInfo))
1164 IRestrictedErrorInfo restrictedError = UnsafeNativeMethods.GetRestrictedErrorInfo();
1165 if (restrictedError != null)
1167 RoReportUnhandledError(restrictedError);
1176 if (exceptionIErrorInfo != IntPtr.Zero)
1178 Marshal.Release(exceptionIErrorInfo);
1181 if (exceptionIUnknown != IntPtr.Zero)
1183 Marshal.Release(exceptionIUnknown);
1188 // If we got here, then some step of the marshaling failed, which means the GEH was not invoked
1192 #if FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
1193 // Get an IActivationFactory * for a managed type
1194 internal static IntPtr GetActivationFactoryForType(Type type)
1196 ManagedActivationFactory activationFactory = GetManagedActivationFactory(type);
1197 return Marshal.GetComInterfaceForObject(activationFactory, typeof(IActivationFactory));
1200 internal static ManagedActivationFactory GetManagedActivationFactory(Type type)
1202 ManagedActivationFactory activationFactory = new ManagedActivationFactory(type);
1204 // If the type has any associated factory interfaces (i.e. supports non-default activation
1205 // or has statics), the CCW for this instance of ManagedActivationFactory must support them.
1206 Marshal.InitializeManagedWinRTFactoryObject(activationFactory, (RuntimeType)type);
1207 return activationFactory;
1211 #endif // FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
1214 // Get activation factory object for a specified WinRT type
1215 // If the WinRT type is a native type, we'll always create a unique RCW for it,
1216 // This is necessary because WinRT factories are often implemented as a singleton,
1217 // and getting back a RCW for such WinRT factory would usually get back a RCW from
1218 // another apartment, even if the interface pointe returned from GetActivationFactory
1219 // is a raw pointer. As a result, user would randomly get back RCWs for activation
1220 // factories from other apartments and make transiton to those apartments and cause
1221 // deadlocks and create objects in incorrect apartments
1223 public static IActivationFactory GetActivationFactory(Type type)
1226 throw new ArgumentNullException(nameof(type));
1228 if (type.IsWindowsRuntimeObject && type.IsImport)
1230 return (IActivationFactory)Marshal.GetNativeActivationFactory(type);
1234 #if FEATURE_COMINTEROP_WINRT_MANAGED_ACTIVATION
1235 return GetManagedActivationFactory(type);
1237 // Managed factories are not supported so as to minimize public surface (and test effort)
1238 throw new NotSupportedException();
1243 // HSTRING marshaling methods:
1245 public static IntPtr StringToHString(String s)
1247 if (!Environment.IsWinRTSupported)
1248 throw new PlatformNotSupportedException(SR.PlatformNotSupported_WinRT);
1251 throw new ArgumentNullException(nameof(s));
1256 int hrCreate = UnsafeNativeMethods.WindowsCreateString(s, s.Length, &hstring);
1257 Marshal.ThrowExceptionForHR(hrCreate, new IntPtr(-1));
1262 public static String PtrToStringHString(IntPtr ptr)
1264 if (!Environment.IsWinRTSupported)
1266 throw new PlatformNotSupportedException(SR.PlatformNotSupported_WinRT);
1269 return HStringToString(ptr);
1272 public static void FreeHString(IntPtr ptr)
1274 if (!Environment.IsWinRTSupported)
1275 throw new PlatformNotSupportedException(SR.PlatformNotSupported_WinRT);
1277 if (ptr != IntPtr.Zero)
1279 UnsafeNativeMethods.WindowsDeleteString(ptr);
1283 [Conditional("_LOGGING")]
1284 private static void Log(string s)
1286 // Internal.Console.WriteLine(s);