Add PInvoke/CriticalHandles tests (#19297)
authorZeng Jiang <v-jiazen@microsoft.com>
Fri, 16 Nov 2018 07:46:51 +0000 (15:46 +0800)
committerJeremy Koritzinsky <jkoritzinsky@gmail.com>
Fri, 16 Nov 2018 07:46:51 +0000 (23:46 -0800)
* Add PInvoke/CreticalHandles tests

* Bring up to current infrastructure.

* Fix ReverseTest.

tests/src/Interop/CMakeLists.txt
tests/src/Interop/PInvoke/CriticalHandles/ArrayTest/ArrayTest.cs [new file with mode: 0644]
tests/src/Interop/PInvoke/CriticalHandles/ArrayTest/ArrayTest.csproj [new file with mode: 0644]
tests/src/Interop/PInvoke/CriticalHandles/CMakeLists.txt [new file with mode: 0644]
tests/src/Interop/PInvoke/CriticalHandles/CriticalHandlesNative.cpp [new file with mode: 0644]
tests/src/Interop/PInvoke/CriticalHandles/ReverseTest/ReverseTest.cs [new file with mode: 0644]
tests/src/Interop/PInvoke/CriticalHandles/ReverseTest/ReverseTest.csproj [new file with mode: 0644]
tests/src/Interop/PInvoke/CriticalHandles/StructTest/StructTest.cs [new file with mode: 0644]
tests/src/Interop/PInvoke/CriticalHandles/StructTest/StructTest.csproj [new file with mode: 0644]
tests/src/Interop/PInvoke/CriticalHandles/Test/Test.cs [new file with mode: 0644]
tests/src/Interop/PInvoke/CriticalHandles/Test/Test.csproj [new file with mode: 0644]

index 1ecb281..d49da33 100644 (file)
@@ -27,6 +27,7 @@ add_subdirectory(PInvoke/Array/MarshalArrayAsParam/LPArrayNative)
 add_subdirectory(PInvoke/Miscellaneous/HandleRef)
 add_subdirectory(PInvoke/Miscellaneous/MultipleAssembliesWithSamePInvoke)
 add_subdirectory(PInvoke/ExactSpelling)
+add_subdirectory(PInvoke/CriticalHandles)
 add_subdirectory(PInvoke/AsAny)
 add_subdirectory(NativeCallable)
 add_subdirectory(PrimitiveMarshalling/Bool)
diff --git a/tests/src/Interop/PInvoke/CriticalHandles/ArrayTest/ArrayTest.cs b/tests/src/Interop/PInvoke/CriticalHandles/ArrayTest/ArrayTest.cs
new file mode 100644 (file)
index 0000000..fb3918c
--- /dev/null
@@ -0,0 +1,157 @@
+// 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;
+using TestLibrary;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+internal class MyCriticalHandle : CriticalHandle
+{
+    static int s_uniqueHandleValue;
+    static HashSet<int> s_closedHandles = new HashSet<int>();
+
+    public MyCriticalHandle() : base(new IntPtr(-1))
+    {
+
+    }
+
+    public override bool IsInvalid
+    {
+        get { return false; }
+    }
+
+    protected override bool ReleaseHandle()
+    {
+        if (!s_closedHandles.Contains(handle.ToInt32()))
+        {
+            s_closedHandles.Add(handle.ToInt32());
+            return true;
+        }
+
+        return false;
+    }
+
+    internal IntPtr Handle
+    {
+        get
+        {
+            return handle;
+        }
+        set 
+        {
+            handle = value;
+        }
+    }
+    
+    internal static IntPtr GetUniqueHandle()
+    {
+        return new IntPtr(s_uniqueHandleValue++);
+    }
+    
+    internal static bool IsHandleClosed(IntPtr handle)
+    {
+        return s_closedHandles.Contains(handle.ToInt32());
+    }
+}
+
+internal class Native
+{
+    [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
+    [return: MarshalAs(UnmanagedType.Bool)]internal delegate bool IsHandleClosed(IntPtr handle);
+
+    [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+    internal static extern IntPtr In([MarshalAs(UnmanagedType.LPArray)]MyCriticalHandle[] handle);
+
+    [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+    internal static extern void Out(IntPtr handleValue, [MarshalAs(UnmanagedType.LPArray)]out MyCriticalHandle[] handle);
+
+    [DllImport("CriticalHandlesNative", EntryPoint = "Ref", CallingConvention = CallingConvention.StdCall)]
+    internal static extern IntPtr InRef([In, MarshalAs(UnmanagedType.LPArray)]ref MyCriticalHandle[] handle);
+
+    [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+    internal static extern IntPtr Ref([MarshalAs(UnmanagedType.LPArray)]ref MyCriticalHandle[] handle);
+
+    [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+    internal static extern IntPtr RefModify(IntPtr handleValue, [MarshalAs(UnmanagedType.LPArray)]ref MyCriticalHandle[] handle);
+    
+    [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+    internal static extern MyCriticalHandle[] Ret(IntPtr handleValue);
+
+    [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+    internal static extern IntPtr SetIsHandleClosedCallback([MarshalAs(UnmanagedType.FunctionPtr)]IsHandleClosed isHandleClosed);
+}
+
+public class CriticalHandleArrayTest
+{
+    private static Native.IsHandleClosed s_isHandleClose = (handleValue) => 
+    {
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        return MyCriticalHandle.IsHandleClosed(handleValue);
+    };
+
+    public static void In()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        MyCriticalHandle[] myCriticalHandleArray = new MyCriticalHandle[] { new MyCriticalHandle() { Handle = handleValue } };
+        Assert.Throws<MarshalDirectiveException>(() => Native.In(myCriticalHandleArray));
+    }
+
+    public static void Ret()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        Assert.Throws<MarshalDirectiveException>(() => Native.Ret(handleValue));
+    }
+
+    public static void Out()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        MyCriticalHandle[] myCriticalHandleArray;
+        Assert.Throws<MarshalDirectiveException>(() => Native.Out(handleValue, out myCriticalHandleArray));
+    }
+
+    public static void InRef()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        MyCriticalHandle[] myCriticalHandleArray = new MyCriticalHandle[] { new MyCriticalHandle() { Handle = handleValue } };
+        Assert.Throws<MarshalDirectiveException>(() => Native.InRef(ref myCriticalHandleArray));
+    }
+
+    public static void Ref()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        MyCriticalHandle[] myCriticalHandleArray = new MyCriticalHandle[] { new MyCriticalHandle() { Handle = handleValue } };
+        Assert.Throws<MarshalDirectiveException>(() => Native.Ref(ref myCriticalHandleArray));
+    }
+
+    public static void RefModify()
+    {
+        IntPtr handleValue1 = MyCriticalHandle.GetUniqueHandle();
+        IntPtr handleValue2 = MyCriticalHandle.GetUniqueHandle();
+        MyCriticalHandle[] myCriticalHandleArray = new MyCriticalHandle[] { new MyCriticalHandle() { Handle = handleValue1 } };
+        Assert.Throws<MarshalDirectiveException>(() => Native.RefModify(handleValue2, ref myCriticalHandleArray));
+    }
+
+    public static int Main(string[] args)
+    {
+        try
+        {
+            In();
+            Ret();
+            Out();
+            InRef();
+            Ref();
+            RefModify();
+
+            return 100;
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine($"Test Failure: {e}");
+            return 101;
+        }
+    }
+}
diff --git a/tests/src/Interop/PInvoke/CriticalHandles/ArrayTest/ArrayTest.csproj b/tests/src/Interop/PInvoke/CriticalHandles/ArrayTest/ArrayTest.csproj
new file mode 100644 (file)
index 0000000..47bbbc2
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>ArrayTest</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{F1E66554-8C8E-4141-85CF-D0CD6A0CD0B0}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\..\</SolutionDir>
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"></PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"></PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ArrayTest.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\CMakeLists.txt" />
+  </ItemGroup>
+  <Import Project="../../../Interop.settings.targets" />
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
diff --git a/tests/src/Interop/PInvoke/CriticalHandles/CMakeLists.txt b/tests/src/Interop/PInvoke/CriticalHandles/CMakeLists.txt
new file mode 100644 (file)
index 0000000..2dfa7e4
--- /dev/null
@@ -0,0 +1,13 @@
+#VCXPROJ 
+cmake_minimum_required (VERSION 2.6) 
+project (CriticalHandlesNative) 
+include ("${CLR_INTEROP_TEST_ROOT}/Interop.cmake") 
+include_directories(${INC_PLATFORM_DIR}) 
+set(SOURCES 
+    CriticalHandlesNative.cpp 
+) 
+# add the executable 
+add_library (CriticalHandlesNative SHARED ${SOURCES}) 
+target_link_libraries(CriticalHandlesNative ${LINK_LIBRARIES_ADDITIONAL}) 
+# add the install targets 
+install (TARGETS CriticalHandlesNative DESTINATION bin) 
diff --git a/tests/src/Interop/PInvoke/CriticalHandles/CriticalHandlesNative.cpp b/tests/src/Interop/PInvoke/CriticalHandles/CriticalHandlesNative.cpp
new file mode 100644 (file)
index 0000000..ec6ecb3
--- /dev/null
@@ -0,0 +1,78 @@
+// 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.
+
+#include <stdio.h>
+#include <xplatform.h>
+
+typedef BOOL(__stdcall *HandleCallback)(void*);
+
+extern "C" DLL_EXPORT size_t __stdcall In(void* handle, HandleCallback handleCallback)
+{
+    if (handleCallback != nullptr && !handleCallback(handle))
+    {
+        return (size_t)(-1);
+    }
+
+    return reinterpret_cast<size_t>(handle);
+}
+
+extern "C" DLL_EXPORT void* __stdcall Ret(void* handleValue)
+{
+    return handleValue;
+}
+
+extern "C" DLL_EXPORT void __stdcall Out(void* handleValue, void** pHandle)
+{
+    if (pHandle == nullptr)
+    {
+        return;
+    }
+
+    *pHandle = handleValue;
+}
+
+extern "C" DLL_EXPORT size_t __stdcall Ref(void** pHandle, HandleCallback handleCallback)
+{
+    if (handleCallback != nullptr && !handleCallback(*pHandle))
+    {
+        return (size_t)(-1);
+    }
+
+    return reinterpret_cast<size_t>(*pHandle);
+}
+
+extern "C" DLL_EXPORT size_t __stdcall RefModify(void* handleValue, void** pHandle, HandleCallback handleCallback)
+{
+    if (handleCallback != nullptr && !handleCallback(*pHandle))
+    {
+        return (size_t)(-1);
+    }
+
+    void* originalHandle = *pHandle;
+
+    *pHandle = handleValue;
+
+    return reinterpret_cast<size_t>(originalHandle);
+}
+
+typedef void(__stdcall *InCallback)(void*);
+
+extern "C" DLL_EXPORT void __stdcall InvokeInCallback(InCallback callback, void* handle)
+{
+    callback(handle);
+}
+
+typedef void(__stdcall *RefCallback)(void**);
+
+extern "C" DLL_EXPORT void __stdcall InvokeRefCallback(RefCallback callback, void** pHandle)
+{
+    callback(pHandle);
+}
+
+typedef void*(__stdcall *RetCallback)();
+
+extern "C" DLL_EXPORT void* __stdcall InvokeRetCallback(RetCallback callback)
+{
+    return callback();
+}
diff --git a/tests/src/Interop/PInvoke/CriticalHandles/ReverseTest/ReverseTest.cs b/tests/src/Interop/PInvoke/CriticalHandles/ReverseTest/ReverseTest.cs
new file mode 100644 (file)
index 0000000..8fefa0f
--- /dev/null
@@ -0,0 +1,153 @@
+// 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;
+using TestLibrary;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+internal class MyCriticalHandle : CriticalHandle
+{
+    static int s_uniqueHandleValue;
+    static HashSet<int> s_closedHandles = new HashSet<int>();
+
+    public MyCriticalHandle() : base(new IntPtr(-1))
+    {
+
+    }
+
+    public override bool IsInvalid
+    {
+        get { return false; }
+    }
+
+    protected override bool ReleaseHandle()
+    {
+        if (!s_closedHandles.Contains(handle.ToInt32()))
+        {
+            s_closedHandles.Add(handle.ToInt32());
+            return true;
+        }
+
+        return false;
+    }
+
+    internal IntPtr Handle
+    {
+        get
+        {
+            return handle;
+        }
+        set 
+        {
+            handle = value;
+        }
+    }
+    
+    internal static IntPtr GetUniqueHandle()
+    {
+        return new IntPtr(s_uniqueHandleValue++);
+    }
+    
+    internal static bool IsHandleClosed(IntPtr handle)
+    {
+        return s_closedHandles.Contains(handle.ToInt32());
+    }
+}
+
+public class Reverse
+{
+    public static void In()
+    {
+        IntPtr handleValue = new IntPtr(1);
+        Native.InCallback callback = (handle) => { };
+        Assert.Throws<MarshalDirectiveException>(() => Native.InvokeInCallback(callback, handleValue), "Calling P/Invoke that invokes a delegate that has an CriticalHandle parameter");
+        GC.KeepAlive(callback);
+    }
+
+    public static void Ret()
+    {
+        IntPtr handleValue = new IntPtr(2);
+        Native.RetCallback callback = () => new MyCriticalHandle();
+        Assert.Throws<MarshalDirectiveException>(() => Native.InvokeRetCallback(callback), "Calling P/Invoke that invokes a delegate that returns a CriticalHandle parameter");
+        GC.KeepAlive(callback);
+    }
+
+    public static void Out()
+    {
+        IntPtr handleValue = new IntPtr(3);
+        Native.OutCallback callback = (out MyCriticalHandle handle) => handle = null;
+        Assert.Throws<MarshalDirectiveException>(() => Native.InvokeOutCallback(callback, ref handleValue), "Calling P/Invoke that invokes a delegate that has an out CriticalHandle parameter");
+        GC.KeepAlive(callback);
+    }
+
+    public static void InRef()
+    {
+        IntPtr handleValue = new IntPtr(4);
+        Native.InRefCallback callback = (ref MyCriticalHandle handle) => { };
+        Assert.Throws<MarshalDirectiveException>(() => Native.InvokeInRefCallback(callback, ref handleValue), "Calling P/Invoke that invokes a delegate that has an [In] ref CriticalHandle parameter");
+        GC.KeepAlive(callback);
+    }
+
+    public static void Ref()
+    {
+        IntPtr handleValue = new IntPtr(5);
+        Native.RefCallback callback = (ref MyCriticalHandle handle) => { };
+        Assert.Throws<MarshalDirectiveException>(() => Native.InvokeRefCallback(callback, ref handleValue), "Calling P/Invoke that invokes a delegate that has an ref CriticalHandle parameter");
+        GC.KeepAlive(callback);
+    }
+
+    internal class Native
+    {
+        [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
+        internal delegate void InCallback(MyCriticalHandle handle);
+
+        [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
+        internal delegate void OutCallback(out MyCriticalHandle handle);
+
+        [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
+        internal delegate void InRefCallback([In]ref MyCriticalHandle handle);
+
+        [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
+        internal delegate void RefCallback(ref MyCriticalHandle handle);
+
+        [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
+        internal delegate MyCriticalHandle RetCallback();
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern void InvokeInCallback(InCallback callback, IntPtr handle);
+
+        [DllImport("CriticalHandlesNative", EntryPoint = "InvokeRefCallback", CallingConvention = CallingConvention.StdCall)]
+        internal static extern void InvokeOutCallback(OutCallback callback, ref IntPtr handle);
+
+        [DllImport("CriticalHandlesNative", EntryPoint = "InvokeRefCallback", CallingConvention = CallingConvention.StdCall)]
+        internal static extern void InvokeInRefCallback(InRefCallback callback, ref IntPtr handle);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern void InvokeRefCallback(RefCallback callback, ref IntPtr handle);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr InvokeRetCallback(RetCallback callback);
+    }
+
+    public static int Main(string[] args)
+    {
+        try
+        {
+            In();
+            Ret();
+            Out();
+            InRef();
+            Ref();
+
+            return 100;
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine($"Test Failure: {e}");
+            return 101;
+        }
+    }
+}
diff --git a/tests/src/Interop/PInvoke/CriticalHandles/ReverseTest/ReverseTest.csproj b/tests/src/Interop/PInvoke/CriticalHandles/ReverseTest/ReverseTest.csproj
new file mode 100644 (file)
index 0000000..0c25efe
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>ReverseTest</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{F1E66554-8C8E-4141-85CF-D0CD6A0CD0B0}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\..\</SolutionDir>
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"></PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"></PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ReverseTest.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\CMakeLists.txt" />
+  </ItemGroup>
+  <Import Project="../../../Interop.settings.targets" />
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
diff --git a/tests/src/Interop/PInvoke/CriticalHandles/StructTest/StructTest.cs b/tests/src/Interop/PInvoke/CriticalHandles/StructTest/StructTest.cs
new file mode 100644 (file)
index 0000000..abd2c3f
--- /dev/null
@@ -0,0 +1,201 @@
+// 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;
+using TestLibrary;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+internal class MyCriticalHandle : CriticalHandle
+{
+    static int s_uniqueHandleValue;
+    static HashSet<int> s_closedHandles = new HashSet<int>();
+
+    public MyCriticalHandle() : base(new IntPtr(-1))
+    {
+
+    }
+
+    public override bool IsInvalid
+    {
+        get { return false; }
+    }
+
+    protected override bool ReleaseHandle()
+    {
+        if (!s_closedHandles.Contains(handle.ToInt32()))
+        {
+            s_closedHandles.Add(handle.ToInt32());
+            return true;
+        }
+
+        return false;
+    }
+
+    internal IntPtr Handle
+    {
+        get
+        {
+            return handle;
+        }
+        set 
+        {
+            handle = value;
+        }
+    }
+    
+    internal static IntPtr GetUniqueHandle()
+    {
+        return new IntPtr(s_uniqueHandleValue++);
+    }
+    
+    internal static bool IsHandleClosed(IntPtr handle)
+    {
+        return s_closedHandles.Contains(handle.ToInt32());
+    }
+}
+
+public class CriticalHandleStructTest
+{
+    private static Native.HandleCallback s_handleCallback = (handleValue) => 
+    {
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        return !MyCriticalHandle.IsHandleClosed(handleValue);
+    };
+
+    public static void In()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        InWorker(handleValue);
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        Assert.IsTrue(MyCriticalHandle.IsHandleClosed(handleValue), "Handle was not closed");
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void InWorker(IntPtr handleValue)
+    {
+        Native.MyCriticalHandleStruct handleStruct = new Native.MyCriticalHandleStruct() { Handle = new MyCriticalHandle() { Handle = handleValue } };
+        IntPtr value;
+        value = Native.In(handleStruct, s_handleCallback);
+        Assert.AreEqual(handleValue.ToInt32(), value.ToInt32(), "Handle value");
+    }
+
+    public static void Ret()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        Assert.Throws<MarshalDirectiveException>(() => Native.Ret(handleValue));
+    }
+
+    public static void Out()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        Native.MyCriticalHandleStruct handleStruct;
+        Assert.Throws<NotSupportedException>(() => Native.Out(handleValue, out handleStruct));
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void OutWorker(IntPtr handleValue)
+    {
+        Native.MyCriticalHandleStruct handleStruct;
+        Native.Out(handleValue, out handleStruct);
+        Assert.AreEqual(handleValue.ToInt32(), handleStruct.Handle.Handle.ToInt32(), "Handle value");
+    }
+
+    public static void InRef()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        InRefWorker(handleValue);
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        Assert.IsTrue(MyCriticalHandle.IsHandleClosed(handleValue), "Handle was not closed");
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void InRefWorker(IntPtr handleValue)
+    {
+        Native.MyCriticalHandleStruct handleStruct = new Native.MyCriticalHandleStruct() { Handle = new MyCriticalHandle() { Handle = handleValue } };
+        Native.InRef(ref handleStruct, s_handleCallback);
+        Assert.AreEqual(handleValue.ToInt32(), handleStruct.Handle.Handle.ToInt32(), "Handle value");
+    }
+
+    public static void Ref()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        RefWorker(handleValue);
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        Assert.IsTrue(MyCriticalHandle.IsHandleClosed(handleValue), "Handle was not closed");
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void RefWorker(IntPtr handleValue)
+    {
+        Native.MyCriticalHandleStruct handleStruct = new Native.MyCriticalHandleStruct() { Handle = new MyCriticalHandle() { Handle = handleValue } };
+        Native.Ref(ref handleStruct, s_handleCallback);
+        Assert.AreEqual(handleValue.ToInt32(), handleStruct.Handle.Handle.ToInt32(), "Handle value");
+    }
+
+    public static void RefModify()
+    {
+        IntPtr handleValue1 = MyCriticalHandle.GetUniqueHandle();
+        IntPtr handleValue2 = MyCriticalHandle.GetUniqueHandle();
+        Native.MyCriticalHandleStruct handleStruct = new Native.MyCriticalHandleStruct() { Handle = new MyCriticalHandle() { Handle = handleValue1 } };
+
+        Assert.Throws<NotSupportedException>(() => Native.RefModify(handleValue2, ref handleStruct, null));
+    }
+
+    internal class Native
+    {
+        [StructLayout(LayoutKind.Sequential)]
+        internal struct MyCriticalHandleStruct
+        {
+            internal MyCriticalHandle Handle;
+        }
+
+        [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        internal delegate bool HandleCallback(IntPtr handle);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr In(MyCriticalHandleStruct handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern void Out(IntPtr handleValue, out MyCriticalHandleStruct handle);
+
+        [DllImport("CriticalHandlesNative", EntryPoint = "Ref", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr InRef([In]ref MyCriticalHandleStruct handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr Ref(ref MyCriticalHandleStruct handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr RefModify(IntPtr handleValue, ref MyCriticalHandleStruct handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern MyCriticalHandleStruct Ret(IntPtr handleValue);
+    }
+
+    public static int Main(string[] args)
+    {
+        try
+        {
+            In();
+            Ret();
+            Out();
+            InRef();
+            Ref();
+            RefModify();
+
+            return 100;
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine($"Test Failure: {e}");
+            return 101;
+        }
+    }
+}
diff --git a/tests/src/Interop/PInvoke/CriticalHandles/StructTest/StructTest.csproj b/tests/src/Interop/PInvoke/CriticalHandles/StructTest/StructTest.csproj
new file mode 100644 (file)
index 0000000..0be3beb
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>StructTest</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{F1E66554-8C8E-4141-85CF-D0CD6A0CD0B0}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\..\</SolutionDir>
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"></PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"></PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="StructTest.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\CMakeLists.txt" />
+  </ItemGroup>
+  <Import Project="../../../Interop.settings.targets" />
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
diff --git a/tests/src/Interop/PInvoke/CriticalHandles/Test/Test.cs b/tests/src/Interop/PInvoke/CriticalHandles/Test/Test.cs
new file mode 100644 (file)
index 0000000..f510c64
--- /dev/null
@@ -0,0 +1,393 @@
+// 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;
+using TestLibrary;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+internal class MyCriticalHandle : CriticalHandle
+{
+    static int s_uniqueHandleValue;
+    static HashSet<int> s_closedHandles = new HashSet<int>();
+
+    public MyCriticalHandle() : base(new IntPtr(-1))
+    {
+
+    }
+
+    public override bool IsInvalid
+    {
+        get { return false; }
+    }
+
+    protected override bool ReleaseHandle()
+    {
+        if (!s_closedHandles.Contains(handle.ToInt32()))
+        {
+            s_closedHandles.Add(handle.ToInt32());
+            return true;
+        }
+
+        return false;
+    }
+
+    internal IntPtr Handle
+    {
+        get
+        {
+            return handle;
+        }
+        set 
+        {
+            handle = value;
+        }
+    }
+    
+    internal static IntPtr GetUniqueHandle()
+    {
+        return new IntPtr(s_uniqueHandleValue++);
+    }
+    
+    internal static bool IsHandleClosed(IntPtr handle)
+    {
+        return s_closedHandles.Contains(handle.ToInt32());
+    }
+}
+
+public abstract class AbstractCriticalHandle : CriticalHandle
+{
+    public AbstractCriticalHandle() : base(new IntPtr(-1))
+    {
+
+    }
+
+    internal IntPtr Handle
+    {
+        get
+        {
+            return handle;
+        }
+    }
+}
+
+public class CriticalHandleWithNoDefaultCtor : AbstractCriticalHandle
+{
+    public CriticalHandleWithNoDefaultCtor(IntPtr handle)
+    {
+        this.handle = handle;
+    }
+
+    public override bool IsInvalid
+    {
+        get { return false; }
+    }
+
+    protected override bool ReleaseHandle()
+    {
+        return true;
+    }
+}
+
+public class CriticalHandleTest
+{
+    private static Native.HandleCallback s_handleCallback = (handleValue) => 
+    {
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        return !MyCriticalHandle.IsHandleClosed(handleValue);
+    };
+
+    public static void In()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        InWorker(handleValue);
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        Assert.IsTrue(MyCriticalHandle.IsHandleClosed(handleValue), "Handle was not closed");
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void InWorker(IntPtr handleValue)
+    {
+        MyCriticalHandle hande = new MyCriticalHandle() { Handle = handleValue };
+        IntPtr value;
+        value = Native.In(hande, s_handleCallback);
+        Assert.AreEqual(handleValue.ToInt32(), value.ToInt32(), "Handle value");
+    }
+
+    public static void Ret()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        RetWorker(handleValue);
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        Assert.IsTrue(MyCriticalHandle.IsHandleClosed(handleValue), "Handle was not closed");
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void RetWorker(IntPtr handleValue)
+    {
+        MyCriticalHandle hande = Native.Ret(handleValue);
+        Assert.AreEqual(handleValue.ToInt32(), hande.Handle.ToInt32(), "Handle value");
+    }
+
+    public static void Out()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        OutWorker(handleValue);
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        Assert.IsTrue(MyCriticalHandle.IsHandleClosed(handleValue), "Handle was not closed");
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void OutWorker(IntPtr handleValue)
+    {
+        MyCriticalHandle hande;
+        Native.Out(handleValue, out hande);
+        Assert.AreEqual(handleValue.ToInt32(), hande.Handle.ToInt32(), "Handle value");
+    }
+
+    public static void InRef()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        InRefWorker(handleValue);
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        Assert.IsTrue(MyCriticalHandle.IsHandleClosed(handleValue), "Handle was not closed");
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void InRefWorker(IntPtr handleValue)
+    {
+        MyCriticalHandle hande = new MyCriticalHandle() { Handle = handleValue };
+        Native.InRef(ref hande, s_handleCallback);
+        Assert.AreEqual(handleValue.ToInt32(), hande.Handle.ToInt32(), "Handle value");
+    }
+
+    public static void Ref()
+    {
+        IntPtr handleValue = MyCriticalHandle.GetUniqueHandle();
+        RefWorker(handleValue);
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        Assert.IsTrue(MyCriticalHandle.IsHandleClosed(handleValue), "Handle was not closed");
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void RefWorker(IntPtr handleValue)
+    {
+        MyCriticalHandle hande = new MyCriticalHandle() { Handle = handleValue };
+        Native.Ref(ref hande, s_handleCallback);
+        Assert.AreEqual(handleValue.ToInt32(), hande.Handle.ToInt32(), "Handle value");
+    }
+
+    public static void RefModify()
+    {
+        IntPtr handleValue1 = MyCriticalHandle.GetUniqueHandle();
+        IntPtr handleValue2 = MyCriticalHandle.GetUniqueHandle();
+        RefModifyWorker(handleValue1, handleValue2);
+        GC.Collect();
+        GC.WaitForPendingFinalizers();
+        Assert.IsTrue(MyCriticalHandle.IsHandleClosed(handleValue1), "Handle 1 was not closed");
+        Assert.IsTrue(MyCriticalHandle.IsHandleClosed(handleValue2), "Handle 2 was not closed");
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void RefModifyWorker(IntPtr handleValue1, IntPtr handleValue2)
+    {
+        MyCriticalHandle hande = new MyCriticalHandle() { Handle = handleValue1 };
+        Native.RefModify(handleValue2, ref hande, s_handleCallback);
+        Assert.AreEqual(handleValue2.ToInt32(), hande.Handle.ToInt32(), "Handle value");
+    }
+
+    internal class Native
+    {
+        [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        internal delegate bool HandleCallback(IntPtr handle);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr In(MyCriticalHandle handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern void Out(IntPtr handleValue, out MyCriticalHandle handle);
+
+        [DllImport("CriticalHandlesNative", EntryPoint = "Ref", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr InRef([In]ref MyCriticalHandle handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr Ref(ref MyCriticalHandle handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr RefModify(IntPtr handleValue, ref MyCriticalHandle handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern MyCriticalHandle Ret(IntPtr handleValue);
+    }
+}
+
+public class AbstractCriticalHandleTest
+{
+    public static void In()
+    {
+        IntPtr handleValue = new IntPtr(1);
+        AbstractCriticalHandle handle = new CriticalHandleWithNoDefaultCtor(handleValue);
+        IntPtr value;
+        value = Native.In(handle, null);
+        Assert.AreEqual(handleValue.ToInt32(), value.ToInt32(), "Handle value");
+    }
+
+    public static void Ret()
+    {
+        IntPtr handleValue = new IntPtr(2);
+        Assert.Throws<MarshalDirectiveException>(() => Native.Ret(handleValue), "Calling P/Invoke that returns an abstract critical handle");
+    }
+
+    public static void Out()
+    {
+        IntPtr handleValue = new IntPtr(3);
+        AbstractCriticalHandle handle;
+        Assert.Throws<MarshalDirectiveException>(() => Native.Out(handleValue, out handle), "Calling P/Invoke that has an out abstract critical handle parameter");
+    }
+
+    public static void InRef()
+    {
+        IntPtr handleValue = new IntPtr(4);
+        AbstractCriticalHandle handle = new CriticalHandleWithNoDefaultCtor(handleValue);
+        Native.InRef(ref handle, null);
+        Assert.AreEqual(handleValue.ToInt32(), handle.Handle.ToInt32(), "Handle value");
+    }
+
+    public static void Ref()
+    {
+        IntPtr handleValue = new IntPtr(5);
+        AbstractCriticalHandle handle = new CriticalHandleWithNoDefaultCtor(handleValue);
+        Assert.Throws<MarshalDirectiveException>(() => Native.Ref(ref handle, null), "Calling P/Invoke that has a ref abstract critical handle parameter");
+    }
+
+    internal class Native
+    {
+        [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        internal delegate bool HandleCallback(IntPtr handle);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr In(AbstractCriticalHandle handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern void Out(IntPtr handleValue, out AbstractCriticalHandle handle);
+
+        [DllImport("CriticalHandlesNative", EntryPoint = "Ref", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr InRef([In]ref AbstractCriticalHandle handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr Ref(ref AbstractCriticalHandle handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern AbstractCriticalHandle Ret(IntPtr handleValue);
+    }
+}
+
+public class NoDefaultCtorCriticalHandleTest
+{
+    public static void In()
+    {
+        IntPtr handleValue = new IntPtr(1);
+        CriticalHandleWithNoDefaultCtor handle = new CriticalHandleWithNoDefaultCtor(handleValue);
+        IntPtr value;
+        value = Native.In(handle, null);
+        Assert.AreEqual(handleValue.ToInt32(), value.ToInt32(), "Handle value");
+    }
+
+    public static void Ret()
+    {
+        IntPtr handleValue = new IntPtr(2);
+        //TODO: Expected MissingMemberException but throws MissingMethodException
+        Assert.Throws<MissingMethodException>(() => Native.Ret(handleValue), "Calling P/Invoke that returns an no default ctor critical handle");
+    }
+
+    public static void Out()
+    {
+        IntPtr handleValue = new IntPtr(3);
+        CriticalHandleWithNoDefaultCtor handle;
+        //TODO: Expected MissingMemberException but throws MissingMethodException
+        Assert.Throws<MissingMethodException>(() => Native.Out(handleValue, out handle), "Calling P/Invoke that has an out no default ctor critical handle parameter");
+    }
+
+    public static void InRef()
+    {
+        IntPtr handleValue = new IntPtr(4);
+        CriticalHandleWithNoDefaultCtor handle = new CriticalHandleWithNoDefaultCtor(handleValue);
+        //TODO: Expected MissingMemberException but throws MissingMethodException
+        Assert.Throws<MissingMethodException>(() => Native.InRef(ref handle, null), "Calling P/Invoke that has a [In] ref no default ctor critical handle parameter");
+    }
+
+    public static void Ref()
+    {
+        IntPtr handleValue = new IntPtr(5);
+        CriticalHandleWithNoDefaultCtor handle = new CriticalHandleWithNoDefaultCtor(handleValue);
+        //TODO: Expected MissingMemberException but throws MissingMethodException
+        Assert.Throws<MissingMethodException>(() => Native.Ref(ref handle, null), "Calling P/Invoke that has a ref no default ctor critical handle parameter");
+    }
+
+    internal class Native
+    {
+        [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.StdCall)]
+        [return: MarshalAs(UnmanagedType.Bool)]
+        internal delegate bool HandleCallback(IntPtr handle);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr In(CriticalHandleWithNoDefaultCtor handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern void Out(IntPtr handleValue, out CriticalHandleWithNoDefaultCtor handle);
+
+        [DllImport("CriticalHandlesNative", EntryPoint = "Ref", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr InRef([In]ref CriticalHandleWithNoDefaultCtor handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern IntPtr Ref(ref CriticalHandleWithNoDefaultCtor handle, HandleCallback handleCallback);
+
+        [DllImport("CriticalHandlesNative", CallingConvention = CallingConvention.StdCall)]
+        internal static extern CriticalHandleWithNoDefaultCtor Ret(IntPtr handleValue);
+    }
+}
+
+public class Test
+{
+    public static int Main(string[] args)
+    {
+        try
+        {
+            CriticalHandleTest.In();
+            CriticalHandleTest.Ret();
+            CriticalHandleTest.Out();
+            CriticalHandleTest.InRef();
+            CriticalHandleTest.Ref();
+            CriticalHandleTest.RefModify();
+
+            AbstractCriticalHandleTest.In();
+            AbstractCriticalHandleTest.Ret();
+            AbstractCriticalHandleTest.Out();
+            AbstractCriticalHandleTest.InRef();
+            AbstractCriticalHandleTest.Ref();
+
+            NoDefaultCtorCriticalHandleTest.In();
+            NoDefaultCtorCriticalHandleTest.Ret();
+            NoDefaultCtorCriticalHandleTest.Out();
+            NoDefaultCtorCriticalHandleTest.InRef();
+            NoDefaultCtorCriticalHandleTest.Ref();
+
+            return 100;
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine($"Test Failure: {e}");
+            return 101;
+        }
+    }
+}
diff --git a/tests/src/Interop/PInvoke/CriticalHandles/Test/Test.csproj b/tests/src/Interop/PInvoke/CriticalHandles/Test/Test.csproj
new file mode 100644 (file)
index 0000000..1a442e3
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>Test</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{F1E66554-8C8E-4141-85CF-D0CD6A0CD0B0}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\..\</SolutionDir>
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"></PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"></PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Test.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\CMakeLists.txt" />
+  </ItemGroup>
+  <Import Project="../../../Interop.settings.targets" />
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>