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.
5 using System.Runtime.InteropServices;
7 using System.Reflection;
18 [DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
19 private static extern int MarshalPointer_In(HandleRef pintValue, int stackGuard);
21 [DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
22 private static extern int MarshalPointer_InOut(HandleRef pintValue, int stackGuard);
24 [DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
25 private static extern int MarshalPointer_Out(HandleRef pintValue, int stackGuard);
27 [DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
28 private static extern int TestNoGC(HandleRef pintValue, Action gcCallback);
30 public unsafe static int Main(string[] args)
33 const int intManaged = 1000;
34 const int intNative = 2000;
35 const int intReturn = 3000;
36 const int stackGuard = 5000;
38 Console.WriteLine("MarshalPointer_In");
39 int int1 = intManaged;
41 HandleRef hr1 = new HandleRef(new Object(), (IntPtr)int1Ptr);
42 Assert.AreEqual(intReturn, MarshalPointer_In(hr1, stackGuard), "The return value is wrong");
43 Assert.AreEqual(intManaged, int1, "The parameter value is changed");
45 Console.WriteLine("MarshalPointer_InOut");
46 int int2 = intManaged;
48 HandleRef hr2 = new HandleRef(new Object(), (IntPtr)int2Ptr);
49 Assert.AreEqual(intReturn, MarshalPointer_InOut(hr2, stackGuard), "The return value is wrong");
50 Assert.AreEqual(intNative, int2, "The passed value is wrong");
52 Console.WriteLine("MarshalPointer_Out");
53 int int3 = intManaged;
55 HandleRef hr3 = new HandleRef(new Object(), (IntPtr)int3Ptr);
56 Assert.AreEqual(intReturn, MarshalPointer_Out(hr3, stackGuard), "The return value is wrong");
57 Assert.AreEqual(intNative, int3, "The passed value is wrong");
59 // Note that this scenario will always pass in a debug build because all values
60 // stay rooted until the end of the method.
61 Console.WriteLine("TestNoGC");
63 // Keep the int boxed and pinned to prevent it from getting collected.
64 // That way, we can safely reference it from finalizers that run on shutdown.
65 BoxedInt boxedInt = new BoxedInt();
66 GCHandle.Alloc(boxedInt, GCHandleType.Normal);
68 fixed (int* tempIntPtr = &boxedInt.MyInt)
70 // Smuggle the pointer out of the fixed scope
73 Console.WriteLine("2");
74 *int4Ptr = intManaged;
75 CollectableClass collectableClass = new CollectableClass(int4Ptr);
76 HandleRef hr4 = new HandleRef(collectableClass, (IntPtr)int4Ptr);
77 Action gcCallback = () => { Console.WriteLine("GC callback now"); GC.Collect(2, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); GC.Collect(2, GCCollectionMode.Forced); };
78 Assert.AreEqual(intReturn, TestNoGC(hr4, gcCallback), "The return value is wrong");
79 Console.WriteLine("Native code finished");
82 } catch (Exception e){
83 Console.WriteLine($"Test Failure: {e}");
89 /// Class that will change a pointer passed to native code when this class gets finalized.
90 /// Native code can check whether the pointer changed during a P/Invoke
92 unsafe class CollectableClass
95 public CollectableClass(int* ptrToChange)
97 PtrToChange = ptrToChange;
102 Console.WriteLine("CollectableClass collected");
103 *PtrToChange = Int32.MaxValue;