Implement support for copy constructors when marshalling in IJW (#22805)
authorJeremy Koritzinsky <jkoritzinsky@gmail.com>
Tue, 19 Mar 2019 23:22:47 +0000 (16:22 -0700)
committerGitHub <noreply@github.com>
Tue, 19 Mar 2019 23:22:47 +0000 (16:22 -0700)
Fixes #22219.

I decided to try simplifying the code when bringing this feature over from .NET Framework. The .NETFX x86 implementation of this marshaler adds a lot of extra code-paths, and intercepts the calling stub to enable it to exactly replicate the behavior of what would be the native code by copy-constructing an object in-place where it goes on the stack for the native call.

Instead of adding all of that extra goo, I decided to keep the implementation much more similar to the non-x86 implementation from .NETFX. Instead of intercepting the call and adding bookkeeping helper stubs, the marshaler just copies to a local in the IL stub, just like non-x86. When calling the native function, it just loads the local onto the IL stack and calls the native function as a normal function. There is a difference there, but I cannot think of a way that the difference is observable to the user.

The non-x86 implementation is identical to the .NETFX implementation.

12 files changed:
src/dlls/mscorrc/mscorrc.rc
src/dlls/mscorrc/resource.h
src/vm/dllimport.cpp
src/vm/ilmarshalers.cpp
src/vm/ilmarshalers.h
src/vm/mlinfo.cpp
src/vm/mtypes.h
tests/src/Interop/CMakeLists.txt
tests/src/Interop/IJW/CopyConstructorMarshaler/CMakeLists.txt [new file with mode: 0644]
tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.cs [new file with mode: 0644]
tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.csproj [new file with mode: 0644]
tests/src/Interop/IJW/CopyConstructorMarshaler/IjwCopyConstructorMarshaler.cpp [new file with mode: 0644]

index 517c548..8e272ad 100644 (file)
@@ -557,6 +557,7 @@ BEGIN
     IDS_EE_BADMARSHAL_DECIMALARRAY          "Invalid managed/unmanaged type combination (Decimal[] must be paired with an ArraySubType of Struct or Currency)."
     IDS_EE_BADMARSHAL_WINRT_MARSHAL_AS      "Invalid managed/unmanaged type combination (Windows Runtime parameters and fields must not have a MarshalAs attribute set)."
     IDS_EE_BADMARSHAL_WINRT_MISSING_GUID    "Invalid managed/unmanaged type combination (Windows Runtime interfaces, classes and delegates must have a Guid or a default interface)."
+    IDS_EE_BADMARSHAL_WINRT_COPYCTOR        "Windows Runtime marshaler does not support types with copy constructor."
     IDS_EE_BADMARSHAL_WINRT_DELEGATE        "Invalid managed/unmanaged type combination (Delegates must be Windows Runtime delegates)."
     IDS_EE_BADMARSHAL_DEFAULTIFACE_NOT_WINRT_IFACE "The default interface must refer to a Windows Runtime interface with a GUID."
     IDS_EE_BADMARSHAL_DEFAULTIFACE_NOT_SUBTYPE "The default interface must refer to an interface that is implemented by the type."
@@ -611,6 +612,7 @@ BEGIN
     IDS_EE_BADMARSHAL_ASANYRESTRICTION      "AsAny cannot be used on return types, ByRef parameters, ArrayWithOffset, or parameters passed from unmanaged to managed."
     IDS_EE_BADMARSHAL_VBBYVALSTRRESTRICTION "VBByRefStr can only be used in combination with in/out, ByRef managed-to-unmanaged strings."
     IDS_EE_BADMARSHAL_AWORESTRICTION        "ArrayWithOffsets can only be marshaled as inout, non-ByRef, managed-to-unmanaged parameters."
+    IDS_EE_BADMARSHAL_COPYCTORRESTRICTION   "Classes with copy-ctors can only be marshaled by value."
     IDS_EE_BADMARSHAL_ARGITERATORRESTRICTION "ArgIterators cannot be marshaled ByRef."
     IDS_EE_BADMARSHAL_HANDLEREFRESTRICTION  "HandleRefs cannot be marshaled ByRef or from unmanaged to managed."
     IDS_EE_BADMARSHAL_SAFEHANDLENATIVETOCOM "SafeHandles cannot be marshaled from unmanaged to managed."
index cec39bb..a7d9487 100644 (file)
 #define IDS_EE_NDIRECT_GETPROCADDR_WIN_DLL         0x2644
 #define IDS_EE_NDIRECT_GETPROCADDR_UNIX_SO         0x2645
 #define IDS_EE_BADMARSHAL_STRING_OUT               0x2646
+#define IDS_EE_BADMARSHAL_COPYCTORRESTRICTION      0x2647
+#define IDS_EE_BADMARSHAL_WINRT_COPYCTOR           0x2648
index 6fb16c5..f4c2838 100644 (file)
@@ -4139,6 +4139,7 @@ static void CreateNDirectStubWorker(StubState*         pss,
                     COMPlusThrow(kMarshalDirectiveException, IDS_EE_NDIRECT_BADNATL_THISCALL);
             }
 
+            fHasCopyCtorArgs = info.GetMarshalType() == MarshalInfo::MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR ? TRUE : FALSE;
 
             argidx++;
         }
index 2b7f7a6..7d8f046 100644 (file)
@@ -3292,6 +3292,92 @@ ILCriticalHandleMarshaler::ReturnOverride(
     return OVERRIDDEN;
 } // ILCriticalHandleMarshaler::ReturnOverride
 
+MarshalerOverrideStatus ILBlittableValueClassWithCopyCtorMarshaler::ArgumentOverride(NDirectStubLinker* psl,
+                                                BOOL               byref,
+                                                BOOL               fin,
+                                                BOOL               fout,
+                                                BOOL               fManagedToNative,
+                                                OverrideProcArgs*  pargs,
+                                                UINT*              pResID,
+                                                UINT               argidx,
+                                                UINT               nativeStackOffset)
+{
+    CONTRACTL
+    {
+        THROWS;
+        GC_TRIGGERS;
+        MODE_ANY;
+    }
+    CONTRACTL_END;
+    
+    ILCodeStream* pslIL         = psl->GetMarshalCodeStream();
+    ILCodeStream* pslILDispatch = psl->GetDispatchCodeStream();
+
+    if (byref)
+    {
+        *pResID = IDS_EE_BADMARSHAL_COPYCTORRESTRICTION;
+        return DISALLOWED;
+    }        
+
+    if (fManagedToNative)
+    {
+        // 1) create new native value type local
+        // 2) run new->CopyCtor(old)
+        // 3) run old->Dtor()
+
+        LocalDesc   locDesc(pargs->mm.m_pMT);
+
+        DWORD       dwNewValueTypeLocal;
+
+        // Step 1
+        dwNewValueTypeLocal = pslIL->NewLocal(locDesc);
+
+        // Step 2
+        if (pargs->mm.m_pCopyCtor)
+        {
+            // Managed copy constructor has signature of CopyCtor(T* new, T old);
+            pslIL->EmitLDLOCA(dwNewValueTypeLocal);
+            pslIL->EmitLDARG(argidx);
+            pslIL->EmitCALL(pslIL->GetToken(pargs->mm.m_pCopyCtor), 2, 0);
+        }
+        else
+        {
+            pslIL->EmitLDARG(argidx);
+            pslIL->EmitLDOBJ(pslIL->GetToken(pargs->mm.m_pMT));
+            pslIL->EmitSTLOC(dwNewValueTypeLocal);
+        }
+
+        // Step 3
+        if (pargs->mm.m_pDtor)
+        {
+            // Managed destructor has signature of Destructor(T old);
+            pslIL->EmitLDARG(argidx);
+            pslIL->EmitCALL(pslIL->GetToken(pargs->mm.m_pDtor), 1, 0);
+        }
+#ifdef _TARGET_X86_
+        pslIL->SetStubTargetArgType(&locDesc);              // native type is the value type
+        pslILDispatch->EmitLDLOC(dwNewValueTypeLocal);      // we load the local directly
+#else
+        pslIL->SetStubTargetArgType(ELEMENT_TYPE_I);        // native type is a pointer
+        pslILDispatch->EmitLDLOCA(dwNewValueTypeLocal);
+#endif
+
+        return OVERRIDDEN;
+    }
+    else
+    {
+        // nothing to do but pass the value along
+        // note that on x86 the argument comes by-value but is converted to pointer by the UM thunk
+        // so that we don't make copies that would not be accounted for by copy ctors
+        LocalDesc   locDesc(pargs->mm.m_pMT);
+        locDesc.MakeCopyConstructedPointer();
+
+        pslIL->SetStubTargetArgType(&locDesc);              // native type is a pointer
+        pslILDispatch->EmitLDARG(argidx);
+
+        return OVERRIDDEN;
+    }
+}
 
 LocalDesc ILArgIteratorMarshaler::GetNativeType()
 {
index 99a3d8f..c892ae6 100644 (file)
@@ -2725,8 +2725,40 @@ protected:
     virtual void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit);
 };
 
+class ILBlittableValueClassWithCopyCtorMarshaler : public ILMarshaler
+{
+public:
+    enum
+    {
+        c_fInOnly               = TRUE,
+        c_nativeSize            = VARIABLESIZE,
+        c_CLRSize               = sizeof(OBJECTREF),
+    };
 
+    LocalDesc GetManagedType()
+    {
+        LIMITED_METHOD_CONTRACT;
+        return LocalDesc();
+    }
 
+    LocalDesc GetNativeType()
+    {
+        LIMITED_METHOD_CONTRACT;
+        return LocalDesc();
+    }
+
+    static MarshalerOverrideStatus ArgumentOverride(NDirectStubLinker* psl,
+                                            BOOL               byref,
+                                            BOOL               fin,
+                                            BOOL               fout,
+                                            BOOL               fManagedToNative,
+                                            OverrideProcArgs*  pargs,
+                                            UINT*              pResID,
+                                            UINT               argidx,
+                                            UINT               nativeStackOffset);
+
+
+};
 
 class ILArgIteratorMarshaler : public ILMarshaler
 {
index e007891..edbf759 100644 (file)
@@ -1438,6 +1438,7 @@ MarshalInfo::MarshalInfo(Module* pModule,
 
     CorNativeType nativeType        = NATIVE_TYPE_DEFAULT;
     Assembly *pAssembly             = pModule->GetAssembly();
+    BOOL fNeedsCopyCtor             = FALSE;
     m_BestFit                       = BestFit;
     m_ThrowOnUnmappableChar         = ThrowOnUnmappableChar;
     m_ms                            = ms;
@@ -1548,6 +1549,21 @@ MarshalInfo::MarshalInfo(Module* pModule,
         IfFailGoto(sig.GetElemType(NULL), lFail);
         mtype = sig.PeekElemTypeNormalized(pModule, pTypeContext); 
 
+        // Check for Copy Constructor Modifier - peek closed elem type here to prevent ELEMENT_TYPE_VALUETYPE
+        // turning into a primitive.
+        if (sig.PeekElemTypeClosed(pModule, pTypeContext) == ELEMENT_TYPE_VALUETYPE) 
+        {
+            // Skip ET_BYREF
+            IfFailGoto(sigtmp.GetByte(NULL), lFail);
+            
+            if (sigtmp.HasCustomModifier(pModule, "Microsoft.VisualC.NeedsCopyConstructorModifier", ELEMENT_TYPE_CMOD_REQD) ||
+                sigtmp.HasCustomModifier(pModule, "System.Runtime.CompilerServices.IsCopyConstructed", ELEMENT_TYPE_CMOD_REQD) )
+            {
+                mtype = ELEMENT_TYPE_VALUETYPE;
+                fNeedsCopyCtor = TRUE;
+                m_byref = FALSE;
+            }
+        }
     }
     else
     {
@@ -1590,6 +1606,19 @@ MarshalInfo::MarshalInfo(Module* pModule,
                     IfFailGoto(E_FAIL, lFail);
                 }
 
+                // Check for Copy Constructor Modifier
+                if (sigtmp.HasCustomModifier(pModule, "Microsoft.VisualC.NeedsCopyConstructorModifier", ELEMENT_TYPE_CMOD_REQD) ||
+                    sigtmp.HasCustomModifier(pModule, "System.Runtime.CompilerServices.IsCopyConstructed", ELEMENT_TYPE_CMOD_REQD) )
+                {
+                    mtype = mtype2;
+
+                    // Keep the sig pointer in sync with mtype (skip ELEMENT_TYPE_PTR) because for the rest
+                    // of this method we are pretending that the parameter is a value type passed by-value.
+                    IfFailGoto(sig.GetElemType(NULL), lFail);
+
+                    fNeedsCopyCtor = TRUE;
+                    m_byref = FALSE;
+                }
             }
         }
         else
@@ -2684,6 +2713,29 @@ MarshalInfo::MarshalInfo(Module* pModule,
                     }
                     else
                     {
+                        if (fNeedsCopyCtor)
+                        {
+#ifdef FEATURE_COMINTEROP
+                            if (m_ms == MARSHAL_SCENARIO_WINRT)
+                            {
+                                // our WinRT-optimized GetCOMIPFromRCW helpers don't support copy
+                                // constructor stubs so make sure that this marshaler will not be used
+                                m_resID = IDS_EE_BADMARSHAL_WINRT_COPYCTOR;
+                                IfFailGoto(E_FAIL, lFail);
+                            }
+#endif
+
+                            MethodDesc *pCopyCtor;
+                            MethodDesc *pDtor;
+                            FindCopyCtor(pModule, m_pMT, &pCopyCtor);
+                            FindDtor(pModule, m_pMT, &pDtor);
+
+                            m_args.mm.m_pMT = m_pMT;
+                            m_args.mm.m_pCopyCtor = pCopyCtor;
+                            m_args.mm.m_pDtor = pDtor;
+                            m_type = MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR;
+                        }
+                        else
 #ifdef _TARGET_X86_
                         // JIT64 is not aware of normalized value types and this optimization
                         // (returning small value types by value in registers) is already done in JIT64.
@@ -3398,6 +3450,7 @@ UINT16 MarshalInfo::GetNativeSize(MarshalType mtype, MarshalScenario ms)
         {
             case MARSHAL_TYPE_BLITTABLEVALUECLASS:
             case MARSHAL_TYPE_VALUECLASS:
+            case MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR:
                 return (UINT16) m_pMT->GetNativeSize();
 
             default:
@@ -4049,6 +4102,7 @@ DispParamMarshaler *MarshalInfo::GenerateDispParamMarshaler()
         case MARSHAL_TYPE_BLITTABLEVALUECLASS:
         case MARSHAL_TYPE_BLITTABLEPTR:
         case MARSHAL_TYPE_LAYOUTCLASSPTR:
+        case MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR:
             pDispParamMarshaler = new DispParamRecordMarshaler(m_pMT);
             break;
 
@@ -4350,6 +4404,9 @@ VOID MarshalInfo::MarshalTypeToString(SString& strMarshalType, BOOL fSizeIsSpeci
             case MARSHAL_TYPE_ARGITERATOR:
                 strRetVal = W("ArgIterator");
                 break;
+            case MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR:
+                strRetVal = W("blittable value class with copy constructor");
+                break;
 #ifdef FEATURE_COMINTEROP
             case MARSHAL_TYPE_OBJECT:
                 strRetVal = W("VARIANT");
index af21d26..5f75eeb 100644 (file)
@@ -88,7 +88,7 @@ DEFINE_MARSHALER_TYPE(MARSHAL_TYPE_VALUECLASS,                      ValueClassMa
 
 DEFINE_MARSHALER_TYPE(MARSHAL_TYPE_REFERENCECUSTOMMARSHALER,        ReferenceCustomMarshaler,      false)
 DEFINE_MARSHALER_TYPE(MARSHAL_TYPE_ARGITERATOR,                     ArgIteratorMarshaler,          false)
-
+DEFINE_MARSHALER_TYPE(MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR, BlittableValueClassWithCopyCtorMarshaler, false)
 
 #ifdef FEATURE_COMINTEROP
 DEFINE_MARSHALER_TYPE(MARSHAL_TYPE_OBJECT,                          ObjectMarshaler,               false)
index b4dec57..32e4b77 100644 (file)
@@ -89,5 +89,6 @@ if(WIN32)
     if(NOT CLR_CMAKE_PLATFORM_ARCH_ARM64)
         add_subdirectory(IJW/ManagedCallingNative/IjwNativeDll)
         add_subdirectory(IJW/NativeCallingManaged/IjwNativeCallingManagedDll)
+        add_subdirectory(IJW/CopyConstructorMarshaler)
     endif()
 endif(WIN32)
diff --git a/tests/src/Interop/IJW/CopyConstructorMarshaler/CMakeLists.txt b/tests/src/Interop/IJW/CopyConstructorMarshaler/CMakeLists.txt
new file mode 100644 (file)
index 0000000..57726a4
--- /dev/null
@@ -0,0 +1,42 @@
+cmake_minimum_required (VERSION 2.6)
+project (CopyConstructorMarshaler)
+include_directories( ${INC_PLATFORM_DIR} )
+set(SOURCES IjwCopyConstructorMarshaler.cpp)
+
+if (WIN32)
+  # 4365 - signed/unsigned mismatch
+  add_compile_options(/wd4365)
+
+  # IJW
+  add_compile_options(/clr)
+  
+  # IJW requires the CRT as a dll, not linked in
+  add_compile_options(/MD$<$<OR:$<CONFIG:Debug>,$<CONFIG:Checked>>:d>)
+
+  # CMake enables /RTC1 and /EHsc by default, but they're not compatible with /clr, so remove them
+  if(CMAKE_CXX_FLAGS_DEBUG MATCHES "/RTC1")
+    string(REPLACE "/RTC1" " " CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
+  endif()
+  
+  if(CMAKE_CXX_FLAGS MATCHES "/EHsc")
+    string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+  endif()
+
+  # IJW isn't compatible with CFG
+   if(CMAKE_CXX_FLAGS MATCHES "/guard:cf")
+    string(REPLACE "/guard:cf" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+   endif()
+
+     # IJW isn't compatible with GR-
+   if(CMAKE_CXX_FLAGS MATCHES "/GR-")
+    string(REPLACE "/GR-" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+   endif()
+
+endif()
+
+# add the shared library
+add_library (IjwCopyConstructorMarshaler SHARED ${SOURCES})
+target_link_libraries(IjwCopyConstructorMarshaler ${LINK_LIBRARIES_ADDITIONAL})
+
+# add the install targets
+install (TARGETS IjwCopyConstructorMarshaler DESTINATION bin)
diff --git a/tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.cs b/tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.cs
new file mode 100644 (file)
index 0000000..d589cd4
--- /dev/null
@@ -0,0 +1,66 @@
+// 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.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using TestLibrary;
+
+namespace CopyConstructorMarshaler
+{
+    class CopyConstructorMarshaler
+    {
+        static int Main(string[] args)
+        {
+            if(Environment.OSVersion.Platform != PlatformID.Win32NT || TestLibrary.Utilities.IsWindows7)
+            {
+                return 100;
+            }
+
+            try
+            {
+                // Load a fake mscoree.dll to avoid starting desktop
+                LoadLibraryEx(Path.Combine(Environment.CurrentDirectory, "mscoree.dll"), IntPtr.Zero, 0);
+
+                Assembly ijwNativeDll = Assembly.Load("IjwCopyConstructorMarshaler");
+                Type testType = ijwNativeDll.GetType("TestClass");
+                object testInstance = Activator.CreateInstance(testType);
+                MethodInfo testMethod = testType.GetMethod("PInvokeNumCopies");
+
+                // PInvoke will copy twice. Once from argument to parameter, and once from the managed to native parameter.
+                Assert.AreEqual(2, (int)testMethod.Invoke(testInstance, null));
+
+                testMethod = testType.GetMethod("ReversePInvokeNumCopies");
+
+                // Reverse PInvoke will copy 3 times. Two are from the same paths as the PInvoke,
+                // and the third is from the reverse P/Invoke call.
+                Assert.AreEqual(3, (int)testMethod.Invoke(testInstance, null));
+                
+                testMethod = testType.GetMethod("PInvokeNumCopiesDerivedType");
+
+                // PInvoke will copy twice. Once from argument to parameter, and once from the managed to native parameter.
+                Assert.AreEqual(2, (int)testMethod.Invoke(testInstance, null));
+
+                testMethod = testType.GetMethod("ReversePInvokeNumCopiesDerivedType");
+
+                // Reverse PInvoke will copy 3 times. Two are from the same paths as the PInvoke,
+                // and the third is from the reverse P/Invoke call.
+                Assert.AreEqual(3, (int)testMethod.Invoke(testInstance, null));
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex);
+                return 101;
+            }
+            return 100;
+        }
+
+        [DllImport("kernel32.dll")]
+        static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, int dwFlags);
+
+        [DllImport("kernel32.dll")]
+        static extern IntPtr GetModuleHandle(string lpModuleName);
+    }
+}
diff --git a/tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.csproj b/tests/src/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.csproj
new file mode 100644 (file)
index 0000000..34aad53
--- /dev/null
@@ -0,0 +1,44 @@
+<?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" />
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Interop.settings.targets))\Interop.settings.targets" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>CopyConstructorMarshaler</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{49D1D482-E783-4CA9-B6BA-A9714BF81036}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    
+    <!-- IJW is Windows-only -->
+    <!-- Test unsupported outside of windows -->
+    <TestUnsupportedOutsideWindows>true</TestUnsupportedOutsideWindows>
+    <DisableProjectBuild Condition="'$(TargetsUnix)' == 'true'">true</DisableProjectBuild>
+
+    <!-- IJW is not supported on ARM64 -->
+    <DisableProjectBuild Condition="'$(Platform)' == 'arm64'">true</DisableProjectBuild>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <PropertyGroup>
+    <CopyDebugCRTDllsToOutputDirectory>true</CopyDebugCRTDllsToOutputDirectory>
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="CopyConstructorMarshaler.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="CMakeLists.txt" />
+    <ProjectReference Include="../FakeMscoree/CMakeLists.txt" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
diff --git a/tests/src/Interop/IJW/CopyConstructorMarshaler/IjwCopyConstructorMarshaler.cpp b/tests/src/Interop/IJW/CopyConstructorMarshaler/IjwCopyConstructorMarshaler.cpp
new file mode 100644 (file)
index 0000000..b82c33b
--- /dev/null
@@ -0,0 +1,106 @@
+// 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.
+
+#pragma managed
+class A
+{
+    int copyCount;
+
+public:
+    A()
+    :copyCount(0)
+    {};
+
+    A(A& other)
+    :copyCount(other.copyCount + 1)
+    {}
+
+    int GetCopyCount()
+    {
+        return copyCount;
+    }
+};
+
+class B : public A
+{
+    int bCopyCount;
+
+public:
+    B()
+    :A(),
+    bCopyCount(0)
+    {};
+
+    B(B& other)
+    :A(other),
+    bCopyCount(other.bCopyCount + 1)
+    {}
+
+    int GetBCopyCount()
+    {
+        return bCopyCount;
+    }
+};
+
+int Managed_GetCopyCount(A a)
+{
+    return a.GetCopyCount();
+}
+
+int Managed_GetCopyCount(B b)
+{
+    return b.GetBCopyCount();
+}
+
+#pragma unmanaged
+
+int GetCopyCount(A a)
+{
+    return a.GetCopyCount();
+}
+
+int GetCopyCount_ViaManaged(A a)
+{
+    return Managed_GetCopyCount(a);
+}
+
+int GetCopyCount(B b)
+{
+    return b.GetBCopyCount();
+}
+
+int GetCopyCount_ViaManaged(B b)
+{
+    return Managed_GetCopyCount(b);
+}
+
+#pragma managed
+
+public ref class TestClass
+{
+public:
+    int PInvokeNumCopies()
+    {
+        A a;
+        return GetCopyCount(a);
+    }
+
+    int PInvokeNumCopiesDerivedType()
+    {
+        B b;
+        return GetCopyCount(b);
+    }
+
+    int ReversePInvokeNumCopies()
+    {
+        A a;
+        return GetCopyCount_ViaManaged(a);
+    }
+
+    int ReversePInvokeNumCopiesDerivedType()
+    {
+        B b;
+        return GetCopyCount_ViaManaged(b);
+    }
+};