summaryrefslogtreecommitdiff
path: root/tests/src/Interop
diff options
context:
space:
mode:
authorHugh Bellamy <hughbellars@gmail.com>2018-11-18 18:31:28 +0000
committerAaron Robinson <arobins@microsoft.com>2018-11-18 10:31:28 -0800
commit52b9353a05890be9302111a8a0eec1cddc719945 (patch)
tree8666bc5364b260cb021b1502a959c2a481d14a20 /tests/src/Interop
parent8418985c3a36fe7b96a2878ef3d4e70a277a24bd (diff)
downloadcoreclr-52b9353a05890be9302111a8a0eec1cddc719945.tar.gz
coreclr-52b9353a05890be9302111a8a0eec1cddc719945.tar.bz2
coreclr-52b9353a05890be9302111a8a0eec1cddc719945.zip
Add ICustomMarshaler tests (#19195)
* Add ICustomMarshaler tests
Diffstat (limited to 'tests/src/Interop')
-rw-r--r--tests/src/Interop/ICustomMarshaler/ICustomMarshaler.cs597
-rw-r--r--tests/src/Interop/ICustomMarshaler/ICustomMarshaler.csproj34
-rw-r--r--tests/src/Interop/common/XunitBase.cs77
3 files changed, 708 insertions, 0 deletions
diff --git a/tests/src/Interop/ICustomMarshaler/ICustomMarshaler.cs b/tests/src/Interop/ICustomMarshaler/ICustomMarshaler.cs
new file mode 100644
index 0000000000..b07b8109cc
--- /dev/null
+++ b/tests/src/Interop/ICustomMarshaler/ICustomMarshaler.cs
@@ -0,0 +1,597 @@
+// 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.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Runtime.InteropServices.Tests
+{
+ public class ICustomMarshalerTests : XunitBase
+ {
+ // To avoid having to create a native test library to reference in tests that
+ // interact with native libraries, we can use a simple method from the C standard
+ // library. Unfortunately, the C standard library has different names on Windows
+ // vs Unix.
+#if Windows
+ public const string LibcLibrary = "msvcrt.dll";
+#else
+ public const string LibcLibrary = "libc";
+#endif
+
+ [Fact]
+ public void CustomMarshaler_StringType_Success()
+ {
+ int val = 64001;
+ Assert.Equal(val, MarshalerOnStringTypeMethod(val.ToString()));
+ }
+
+ public class StringForwardingCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi((string)ManagedObj);
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(string cookie) => new StringForwardingCustomMarshaler();
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi")]
+ public static extern int MarshalerOnStringTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StringForwardingCustomMarshaler))] string str);
+
+ [Fact]
+ public void CustomMarshaler_ArrayType_Success()
+ {
+ int val = 64001;
+ Assert.Equal(val, MarshalerOnArrayTypeMethod(new string[] { val.ToString() }));
+ }
+
+ public class ArrayForwardingCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi(((string[])ManagedObj)[0]);
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(string cookie) => new ArrayForwardingCustomMarshaler();
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi")]
+ public static extern int MarshalerOnArrayTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalType = "System.Runtime.InteropServices.Tests.ICustomMarshalerTests+ArrayForwardingCustomMarshaler")] string[] str);
+
+ [Fact]
+ public void CustomMarshaler_BoxedValueType_Success()
+ {
+ int val = 64001;
+ Assert.Equal(val * 2, MarshalerOnBoxedValueTypeMethod(val));
+ }
+
+ public class BoxedValueTypeCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj)
+ {
+ int unboxedValueType = (int)ManagedObj * 2;
+ return Marshal.StringToCoTaskMemAnsi(unboxedValueType.ToString());
+ }
+
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(string cookie) => new BoxedValueTypeCustomMarshaler();
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi")]
+ public static extern int MarshalerOnBoxedValueTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(BoxedValueTypeCustomMarshaler))] object i);
+
+ [Fact]
+ public void Parameter_CustomMarshalerProvidedOnClassType_ForwardsCorrectly()
+ {
+ int val = 64001;
+ Assert.Equal((val * 2).ToString(), MarshalerOnClassTypeMethod(new StringContainer { Value = val.ToString() }).Value);
+ }
+
+ public class StringContainer
+ {
+ public string Value { get; set; }
+ }
+
+ public class ClassForwardingCustomMarshaler : ICustomMarshaler
+ {
+ private bool CleanedString { get; set; }
+
+ public void CleanUpManagedData(object ManagedObj) {}
+
+ public void CleanUpNativeData(IntPtr pNativeData)
+ {
+ if (CleanedString)
+ {
+ return;
+ }
+
+ Marshal.ZeroFreeCoTaskMemAnsi(pNativeData);
+ CleanedString = true;
+ }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj)
+ {
+ return Marshal.StringToCoTaskMemAnsi(((StringContainer)ManagedObj).Value);
+ }
+
+ public object MarshalNativeToManaged(IntPtr pNativeData)
+ {
+ int doubleValue = pNativeData.ToInt32() * 2;
+ return new StringContainer { Value = doubleValue.ToString() };
+ }
+
+ public static ICustomMarshaler GetInstance(string cookie) => new ClassForwardingCustomMarshaler();
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ClassForwardingCustomMarshaler))]
+ public static extern StringContainer MarshalerOnClassTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ClassForwardingCustomMarshaler))] StringContainer str);
+
+ [Fact]
+ public void Parameter_CustomMarshalerProvided_CallsMethodsInCorrectOrdering()
+ {
+ Assert.Empty(OrderTrackingCustomMarshaler.Events);
+
+ string val1 = "64001";
+ Assert.Equal(val1, OrderTrackingMethod(val1));
+
+ string[] expectedOrderingFirstCall = new string[]
+ {
+ "Called GetInstance",
+ "Called MarshalManagedToNative",
+ "Called MarshalNativeToManaged",
+ "Called CleanUpNativeData"
+ };
+ Assert.Equal(expectedOrderingFirstCall, OrderTrackingCustomMarshaler.Events);
+
+ // GetInstance is only called once.
+ string val2 = "234";
+ Assert.Equal(val2, OrderTrackingMethod(val2));
+ IEnumerable<string> expectedOrderingSecondCall = expectedOrderingFirstCall.Concat(new string[]
+ {
+ "Called MarshalManagedToNative",
+ "Called MarshalNativeToManaged",
+ "Called CleanUpNativeData"
+ });
+ Assert.Equal(expectedOrderingSecondCall, OrderTrackingCustomMarshaler.Events);
+ }
+
+ // This should only be used *once*, as it uses static state.
+ public class OrderTrackingCustomMarshaler : ICustomMarshaler
+ {
+ public static List<string> Events { get; } = new List<string>();
+ public static IntPtr MarshaledNativeData { get; set; }
+
+ public void CleanUpManagedData(object ManagedObj)
+ {
+ Events.Add("Called CleanUpManagedData");
+ }
+
+ public void CleanUpNativeData(IntPtr pNativeData)
+ {
+ Assert.Equal(MarshaledNativeData, pNativeData);
+ Marshal.ZeroFreeCoTaskMemAnsi(pNativeData);
+
+ Events.Add("Called CleanUpNativeData");
+ }
+
+ public int GetNativeDataSize()
+ {
+ Events.Add("Called GetNativeDataSize");
+ return 0;
+ }
+
+ public IntPtr MarshalManagedToNative(object ManagedObj)
+ {
+ Events.Add("Called MarshalManagedToNative");
+ MarshaledNativeData = Marshal.StringToCoTaskMemAnsi((string)ManagedObj);
+ return MarshaledNativeData;
+ }
+
+ public object MarshalNativeToManaged(IntPtr pNativeData)
+ {
+ Events.Add("Called MarshalNativeToManaged");
+ return pNativeData.ToInt32().ToString();
+ }
+
+ public static ICustomMarshaler GetInstance(string cookie)
+ {
+ Assert.Empty(cookie);
+ Events.Add("Called GetInstance");
+ return new OrderTrackingCustomMarshaler();
+ }
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(OrderTrackingCustomMarshaler))]
+ public static extern string OrderTrackingMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(OrderTrackingCustomMarshaler))] string str);
+
+ [Fact]
+ public void CustomMarshaler_BothMarshalTypeRefAndMarshalTypeProvided_PicksMarshalType()
+ {
+ Assert.Equal(2, BothTypeRefAndTypeMethod("64001"));
+ }
+
+ public class OverridingCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi("2");
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(string cookie) => new OverridingCustomMarshaler();
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int BothTypeRefAndTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalType = "System.Runtime.InteropServices.Tests.ICustomMarshalerTests+OverridingCustomMarshaler", MarshalTypeRef = typeof(StringForwardingCustomMarshaler))] string str);
+
+ [Fact]
+ public void Parameter_CookieProvided_PassesCookieToGetInstance()
+ {
+ int val = 64001;
+ Assert.Equal(val, CustomCookieMethod(val.ToString()));
+ Assert.Equal("Cookie", CookieTrackingCustomMarshaler.Cookie);
+ }
+
+ public class CookieTrackingCustomMarshaler : ICustomMarshaler
+ {
+ public static string Cookie { get; set; }
+
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi((string)ManagedObj);
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(string cookie)
+ {
+ Cookie = cookie;
+ return new CookieTrackingCustomMarshaler();
+ }
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int CustomCookieMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(CookieTrackingCustomMarshaler), MarshalCookie = "Cookie")] string str);
+
+ [Fact]
+ public void Parameter_NotCustomMarshalerType_UsesSpecifiedMarshaler()
+ {
+ int val = 64001;
+ Assert.Equal(val, NonCustomMarshalerTypeMethod(val.ToString()));
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int NonCustomMarshalerTypeMethod([MarshalAs(UnmanagedType.LPStr, MarshalTypeRef = typeof(OverridingCustomMarshaler))] string str);
+
+ [Fact]
+ public void CustomMarshaler_Generic_Success()
+ {
+ Assert.Equal(234, GenericGetInstanceCustomMarshalerMethod("64001"));
+ }
+
+ public class GenericCustomMarshaler<T> : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi("234");
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(string cookie)
+ {
+ return new GenericCustomMarshaler<int>();
+ }
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int GenericGetInstanceCustomMarshalerMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(GenericCustomMarshaler<int>))] string str);
+
+ [Fact]
+ public void CustomMarshaler_ValueTypeWithStringType_Success()
+ {
+ Assert.Equal(234, ValueTypeMarshalerOnStringTypeMethod("64001"));
+ }
+
+ public struct CustomMarshalerValueType : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { Marshal.ZeroFreeCoTaskMemAnsi(pNativeData); }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi("234");
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(string cookie)
+ {
+ return new CustomMarshalerValueType();
+ }
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int ValueTypeMarshalerOnStringTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(CustomMarshalerValueType))] string str);
+
+ [Fact]
+ public void Parameter_MarshalerOnValueType_ThrowsMarshalDirectiveException()
+ {
+ Assert.Throws<MarshalDirectiveException>(() => MarshalerOnValueTypeMethod(0));
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int MarshalerOnValueTypeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StringForwardingCustomMarshaler))] int str);
+
+ [Fact]
+ public unsafe void Parameter_MarshalerOnPointer_ThrowsMarshalDirectiveException()
+ {
+ Assert.Throws<MarshalDirectiveException>(() => MarshalerOnPointerMethod(null));
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static unsafe extern int MarshalerOnPointerMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StringForwardingCustomMarshaler))] int* str);
+
+ [Fact]
+ public void Parameter_NullICustomMarshaler_ThrowsTypeLoadException()
+ {
+ Assert.Throws<TypeLoadException>(() => NullCustomMarshalerMethod(""));
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int NullCustomMarshalerMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = null)] string str);
+
+ [Fact]
+ public void Parameter_NotICustomMarshaler_ThrowsApplicationException()
+ {
+ Assert.Throws<ApplicationException>(() => NonICustomMarshalerMethod(""));
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int NonICustomMarshalerMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(string))] string str);
+
+ [Fact]
+ public void Parameter_OpenGenericICustomMarshaler_ThrowsTypeLoadException()
+ {
+ Assert.Throws<TypeLoadException>(() => OpenGenericICustomMarshalerMethod(""));
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int OpenGenericICustomMarshalerMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(GenericCustomMarshaler<>))] string str);
+
+ [Fact]
+ public void Parameter_GetInstanceMethodDoesntExist_ThrowsApplicationException()
+ {
+ Assert.Throws<ApplicationException>(() => NoGetInstanceMethod(""));
+ }
+
+ public class NoGetInstanceCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int NoGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(NoGetInstanceCustomMarshaler))] string str);
+
+ [Fact]
+ public void Parameter_GetInstanceMethodInstanceMethod_ThrowsApplicationException()
+ {
+ Assert.Throws<ApplicationException>(() => InstanceGetInstanceMethod(""));
+ }
+
+ public class InstanceGetInstanceCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+ public ICustomMarshaler GetInstance(string cookie) => new InstanceGetInstanceCustomMarshaler();
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int InstanceGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(InstanceGetInstanceCustomMarshaler))] string str);
+
+ [Fact]
+ public void Parameter_GetInstanceMethodNoParameters_ThrowsApplicationException()
+ {
+ Assert.Throws<ApplicationException>(() => NoParametersGetInstanceMethod(""));
+ }
+
+ public class NoParameterGetInstanceCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance() => new NoParameterGetInstanceCustomMarshaler();
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int NoParametersGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(NoParameterGetInstanceCustomMarshaler))] string str);
+
+ [Fact]
+ public void Parameter_GetInstanceMethodNonStringParameter_ThrowsApplicationException()
+ {
+ Assert.Throws<ApplicationException>(() => NonStringGetInstanceMethod(""));
+ }
+
+ public class NonStringGetInstanceCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(int x) => new NonStringGetInstanceCustomMarshaler();
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int NonStringGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(NonStringGetInstanceCustomMarshaler))] string str);
+
+ [Fact]
+ public void Parameter_GetInstanceMethodReturnsVoid_ThrowsApplicationException()
+ {
+ Assert.Throws<ApplicationException>(() => VoidGetInstanceMethod(""));
+ }
+
+ public class VoidGetInstanceCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static void GetInstance(string cookie) { }
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int VoidGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(VoidGetInstanceCustomMarshaler))] string str);
+
+ [Fact]
+ public void Parameter_GetInstanceMethodReturnsNull_ThrowsApplicationException()
+ {
+ Assert.Throws<ApplicationException>(() => NullGetInstanceMethod(""));
+ }
+
+ public class NullGetInstanceCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(string cookie) => null;
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int NullGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(NullGetInstanceCustomMarshaler))] string str);
+
+ [Fact]
+ public void Parameter_GetInstanceMethodThrows_ThrowsActualException()
+ {
+ Assert.Throws<NotImplementedException>(() => ThrowingGetInstanceMethod(""));
+ }
+
+ public class ThrowingGetInstanceCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => IntPtr.Zero;
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(string cookie) => throw new NotImplementedException();
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int ThrowingGetInstanceMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ThrowingGetInstanceCustomMarshaler))] string str);
+
+ [Fact]
+ public void Parameter_MarshalManagedToNativeThrows_ThrowsActualException()
+ {
+ Assert.Throws<NotImplementedException>(() => ThrowingMarshalManagedToNativeMethod(""));
+ }
+
+ public class ThrowingMarshalManagedToNativeCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) { }
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => throw new NotImplementedException();
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(string cookie) => new ThrowingMarshalManagedToNativeCustomMarshaler();
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int ThrowingMarshalManagedToNativeMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ThrowingMarshalManagedToNativeCustomMarshaler))] string str);
+
+ [Fact]
+ public void Parameter_CleanUpNativeDataMethodThrows_ThrowsActualException()
+ {
+ Assert.Throws<NotImplementedException>(() => ThrowingCleanUpNativeDataMethod(""));
+ }
+
+ public class ThrowingCleanUpNativeDataCustomMarshaler : ICustomMarshaler
+ {
+ public void CleanUpManagedData(object ManagedObj) { }
+ public void CleanUpNativeData(IntPtr pNativeData) => throw new NotImplementedException();
+
+ public int GetNativeDataSize() => IntPtr.Size;
+
+ public IntPtr MarshalManagedToNative(object ManagedObj) => Marshal.StringToCoTaskMemAnsi((string)ManagedObj);
+ public object MarshalNativeToManaged(IntPtr pNativeData) => null;
+
+ public static ICustomMarshaler GetInstance(string cookie) => new ThrowingMarshalManagedToNativeCustomMarshaler();
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int ThrowingCleanUpNativeDataMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ThrowingCleanUpNativeDataCustomMarshaler))] string str);
+
+ [Fact]
+ public static void Field_ParentIsStruct_ThrowsTypeLoadException()
+ {
+ Assert.Throws<TypeLoadException>(() => StructWithCustomMarshalerFieldMethod(new StructWithCustomMarshalerField()));
+ }
+
+ public struct StructWithCustomMarshalerField
+ {
+ [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StringForwardingCustomMarshaler))]
+ public string Field;
+ }
+
+ [DllImport(LibcLibrary, EntryPoint = "atoi", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int StructWithCustomMarshalerFieldMethod(StructWithCustomMarshalerField c);
+
+ public static int Main(String[] args)
+ {
+ return new ICustomMarshalerTests().RunTests();
+ }
+ }
+}
diff --git a/tests/src/Interop/ICustomMarshaler/ICustomMarshaler.csproj b/tests/src/Interop/ICustomMarshaler/ICustomMarshaler.csproj
new file mode 100644
index 0000000000..ae3ef0e4cf
--- /dev/null
+++ b/tests/src/Interop/ICustomMarshaler/ICustomMarshaler.csproj
@@ -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>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <DefineConstants Condition="'$(TargetsWindows)' == 'true'">$(DefineConstants);Windows</DefineConstants>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " />
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ICustomMarshaler.cs" />
+ <Compile Include="..\common\XunitBase.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>
diff --git a/tests/src/Interop/common/XunitBase.cs b/tests/src/Interop/common/XunitBase.cs
new file mode 100644
index 0000000000..06341e739a
--- /dev/null
+++ b/tests/src/Interop/common/XunitBase.cs
@@ -0,0 +1,77 @@
+// 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 System.Threading;
+using Xunit.Runners;
+
+namespace Xunit
+{
+ public abstract class XunitBase
+ {
+ private static object consoleLock = new object();
+
+ private static ManualResetEvent finished = new ManualResetEvent(false);
+
+ private static int result = 100;
+
+ public int RunTests()
+ {
+ var runner = AssemblyRunner.WithoutAppDomain(GetType().Assembly.Location);
+ runner.OnDiscoveryComplete = OnDiscoveryComplete;
+ runner.OnExecutionComplete = OnExecutionComplete;
+ runner.OnTestFailed = OnTestFailed;
+ runner.OnTestSkipped = OnTestSkipped;
+
+ Console.WriteLine("Discovering...");
+
+ runner.Start();
+
+ finished.WaitOne();
+ finished.Dispose();
+
+ return result;
+ }
+
+ private static void OnDiscoveryComplete(DiscoveryCompleteInfo info)
+ {
+ lock (consoleLock)
+ Console.WriteLine($"Running {info.TestCasesToRun} of {info.TestCasesDiscovered} tests...");
+ }
+
+ private static void OnExecutionComplete(ExecutionCompleteInfo info)
+ {
+ lock (consoleLock)
+ Console.WriteLine($"Finished: {info.TotalTests} tests in {Math.Round(info.ExecutionTime, 3)}s ({info.TestsFailed} failed, {info.TestsSkipped} skipped)");
+
+ finished.Set();
+ }
+
+ private static void OnTestFailed(TestFailedInfo info)
+ {
+ lock (consoleLock)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+
+ Console.WriteLine("[FAIL] {0}: {1}", info.TestDisplayName, info.ExceptionMessage);
+ if (info.ExceptionStackTrace != null)
+ Console.WriteLine(info.ExceptionStackTrace);
+
+ Console.ResetColor();
+ }
+
+ result = 101;
+ }
+
+ private static void OnTestSkipped(TestSkippedInfo info)
+ {
+ lock (consoleLock)
+ {
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.WriteLine("[SKIP] {0}: {1}", info.TestDisplayName, info.SkipReason);
+ Console.ResetColor();
+ }
+ }
+ }
+}