summaryrefslogtreecommitdiff
path: root/tests/src/Interop/PInvoke/Miscellaneous/HandleRef/HandleRefTest.cs
blob: 02dc8ff464f575749ab4cdce710a2dd7aab424a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Runtime.InteropServices;
using System;
using System.Reflection;
using System.Text;
using TestLibrary;

class HandleRefTest
{
    [DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
    private static extern int MarshalPointer_In(HandleRef pintValue, int stackGuard);

    [DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
    private static extern int MarshalPointer_InOut(HandleRef pintValue, int stackGuard);

    [DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
    private static extern int MarshalPointer_Out(HandleRef pintValue, int stackGuard);

    [DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
    private static extern int TestNoGC(HandleRef pintValue, Action gcCallback);

    [DllImport(@"HandleRefNative")]
    private static extern HandleRef InvalidMarshalPointer_Return();

    public unsafe static int Main(string[] args)
    {
        try{
            const int intManaged = 1000;
            const int intNative = 2000;
            const int intReturn = 3000;
            const int stackGuard = 5000;

            Console.WriteLine("MarshalPointer_In");
            int int1 = intManaged;
            int* int1Ptr = &int1;
            HandleRef hr1 = new HandleRef(new Object(), (IntPtr)int1Ptr);
            Assert.AreEqual(intReturn, MarshalPointer_In(hr1, stackGuard), "The return value is wrong");
            Assert.AreEqual(intManaged, int1, "The parameter value is changed");
            
            Console.WriteLine("MarshalPointer_InOut");
            int int2 = intManaged;
            int* int2Ptr = &int2;
            HandleRef hr2 = new HandleRef(new Object(), (IntPtr)int2Ptr);
            Assert.AreEqual(intReturn, MarshalPointer_InOut(hr2, stackGuard), "The return value is wrong");
            Assert.AreEqual(intNative, int2, "The passed value is wrong");
            
            Console.WriteLine("MarshalPointer_Out");
            int int3 = intManaged;
            int* int3Ptr = &int3;
            HandleRef hr3 = new HandleRef(new Object(), (IntPtr)int3Ptr);
            Assert.AreEqual(intReturn, MarshalPointer_Out(hr3, stackGuard), "The return value is wrong");
            Assert.AreEqual(intNative, int3, "The passed value is wrong");

            // Note that this scenario will always pass in a debug build because all values 
            // stay rooted until the end of the method. 
            Console.WriteLine("TestNoGC");

            int* int4Ptr = (int*)Marshal.AllocHGlobal(sizeof(int)); // We don't free this memory so we don't have to worry about a GC run between freeing and return (possible in a GCStress mode).
            Console.WriteLine("2");
            *int4Ptr = intManaged;
            CollectableClass collectableClass = new CollectableClass(int4Ptr);
            HandleRef hr4 = new HandleRef(collectableClass, (IntPtr)int4Ptr);
            Action gcCallback = () => { Console.WriteLine("GC callback now"); GC.Collect(2, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); GC.Collect(2, GCCollectionMode.Forced); };
            Assert.AreEqual(intReturn, TestNoGC(hr4, gcCallback), "The return value is wrong");
            Console.WriteLine("Native code finished");

            Console.WriteLine("InvalidMarshalPointer_Return");
            Assert.Throws<MarshalDirectiveException>(() => InvalidMarshalPointer_Return());

            return 100;
        } catch (Exception e){
            Console.WriteLine($"Test Failure: {e}"); 
            return 101; 
        }
    }

    /// <summary>
    /// Class that will change a pointer passed to native code when this class gets finalized.
    /// Native code can check whether the pointer changed during a P/Invoke
    /// </summary>
    unsafe class CollectableClass
    {
        int* PtrToChange;
        public CollectableClass(int* ptrToChange)
        {
            PtrToChange = ptrToChange;
        }

        ~CollectableClass()
        {
            Console.WriteLine("CollectableClass collected");
            *PtrToChange = Int32.MaxValue;
        }
    }
}