private static int ILStubCache_NoGCTransition_GCTransition(int expected) { // This test uses a callback marked UnmanagedCallersOnly as a way to verify that // SuppressGCTransition is taken into account when caching IL stubs. // It calls functions with the same signature, differing only in SuppressGCTransition. // When calling an UnmanagedCallersOnly method, the runtime validates that the GC is in // pre-emptive mode. If not, it throws a fatal error that cannot be caught and crashes. // If the stub for the p/invoke with the transition suppressed is incorrectly reused for // the p/invoke without the suppression, invoking the callback would produce a fatal error. Console.WriteLine($"{nameof(ILStubCache_NoGCTransition_GCTransition)} ({expected}) ..."); int n; // Call function that has SuppressGCTransition SuppressGCTransitionNative.InvokeCallbackFuncPtr_Inline_NoGCTransition(null, null); // Call function with same (blittable) signature, but without SuppressGCTransition. // IL stub should not be re-used, GC transition should occur, and callback should be invoked. SuppressGCTransitionNative.InvokeCallbackFuncPtr_Inline_GCTransition(&ReturnInt, &n); Assert.AreEqual(expected++, n); // Call function that has SuppressGCTransition SuppressGCTransitionNative.InvokeCallbackFuncPtr_NoInline_NoGCTransition(null, null); // Call function with same (non-blittable) signature, but without SuppressGCTransition // IL stub should not be re-used, GC transition should occur, and callback should be invoked. SuppressGCTransitionNative.InvokeCallbackFuncPtr_NoInline_GCTransition(&ReturnInt, &n); Assert.AreEqual(expected++, n); return(n + 1); }