Fix using .NET COM server with `dynamic` keyword (#48037)
authorElinor Fung <elfung@microsoft.com>
Wed, 17 Feb 2021 00:32:42 +0000 (16:32 -0800)
committerGitHub <noreply@github.com>
Wed, 17 Feb 2021 00:32:42 +0000 (16:32 -0800)
src/coreclr/dlls/mscorrc/mscorrc.rc
src/coreclr/dlls/mscorrc/resource.h
src/coreclr/vm/olevariant.cpp
src/coreclr/vm/stdinterfaces.cpp
src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/ComInterop/ComRuntimeHelpers.cs
src/tests/Interop/COM/Dynamic/App.manifest
src/tests/Interop/COM/Dynamic/CoreShim.X.manifest [new file with mode: 0644]
src/tests/Interop/COM/Dynamic/Dynamic.csproj
src/tests/Interop/COM/Dynamic/NETServerTest.cs [new file with mode: 0644]
src/tests/Interop/COM/Dynamic/Program.cs

index e53950f..e510400 100644 (file)
@@ -524,6 +524,7 @@ BEGIN
     IDS_EE_INVALIDCOMDEFITF                 "Type '%1' has an invalid default COM interface: '%2'."
     IDS_EE_COMDEFITFNOTSUPPORTED            "Type '%1' does not support the specified default COM interface: '%2'"
 
+    IDS_EE_CLASS_TO_VARIANT_TLB_NOT_REG     "Type '%1' cannot be marshalled to a Variant. Type library is not registered."
     IDS_EE_CANNOT_MAP_TO_MANAGED_VC         "The specified record cannot be mapped to a managed value class."
 
     IDS_EE_SAFEHANDLECLOSED                 "Safe handle has been closed"
index 834a047..a44e365 100644 (file)
 #define IDS_EE_INVALIDCOMDEFITF                 0x1a32
 #define IDS_EE_COMDEFITFNOTSUPPORTED            0x1a33
 
+#define IDS_EE_CLASS_TO_VARIANT_TLB_NOT_REG     0x1a35
 #define IDS_EE_CANNOT_MAP_TO_MANAGED_VC         0x1a36
 
 #define IDS_EE_MARSHAL_UNMAPPABLE_CHAR          0x1a37
index 91d4105..cc747ba 100644 (file)
@@ -4710,7 +4710,21 @@ void OleVariant::ConvertValueClassToVariant(OBJECTREF *pBoxedValueClass, VARIANT
 
     // Retrieve the ITypeInfo for the value class.
     MethodTable *pValueClassMT = (*pBoxedValueClass)->GetMethodTable();
-    IfFailThrow(GetITypeInfoForEEClass(pValueClassMT, &pTypeInfo, true /* bClassInfo */));
+    hr = GetITypeInfoForEEClass(pValueClassMT, &pTypeInfo, true /* bClassInfo */);
+    if (FAILED(hr))
+    {
+        if (hr == TLBX_E_LIBNOTREGISTERED)
+        {
+            // Indicate that conversion of the class to variant without a registered type lib is not supported
+            StackSString className;
+            pValueClassMT->_GetFullyQualifiedNameForClass(className);
+            COMPlusThrow(kNotSupportedException, IDS_EE_CLASS_TO_VARIANT_TLB_NOT_REG, className.GetUnicode());
+        }
+        else
+        {
+            COMPlusThrowHR(hr);
+        }
+    }
 
     // Convert the ITypeInfo to an IRecordInfo.
     hr = GetRecordInfoFromTypeInfo(pTypeInfo, &V_RECORDINFO(pRecHolder));
index 6d0233c..094b5ab 100644 (file)
@@ -550,11 +550,6 @@ HRESULT GetITypeLibForAssembly(_In_ Assembly *pAssembly, _Outptr_ ITypeLib **ppT
     }
     CONTRACTL_END;
 
-    // If the module wasn't imported from COM, fail. In .NET Framework the runtime
-    // would generate a ITypeLib instance, but .NET Core doesn't support that.
-    if (!pAssembly->IsImportedFromTypeLib())
-        return COR_E_NOTSUPPORTED;
-
     HRESULT hr;
 
     // Check for cached copy.
index 475c2c5..77363c6 100644 (file)
@@ -103,25 +103,29 @@ namespace Microsoft.CSharp.RuntimeBinder.ComInterop
         }
 
         /// <summary>
-        /// Look for typeinfo using IDispatch.GetTypeInfo
+        /// Look for type info using IDispatch.GetTypeInfo
         /// </summary>
         /// <param name="dispatch">IDispatch object</param>
         /// <remarks>
-        /// Some COM objects just dont expose typeinfo. In these cases, this method will return null.
-        /// Some COM objects do intend to expose typeinfo, but may not be able to do so if the type-library is not properly
-        /// registered. This will be considered as acceptable or as an error condition depending on throwIfMissingExpectedTypeInfo
+        /// Some COM objects just don't expose type info. In these cases, this method will return null.
+        /// Some COM objects do intend to expose type info, but may not be able to do so if the type library
+        /// is not properly registered. This will be considered an error.
         /// </remarks>
         /// <returns>Type info</returns>
         internal static ComTypes.ITypeInfo GetITypeInfoFromIDispatch(IDispatch dispatch)
         {
             int hresult = dispatch.TryGetTypeInfoCount(out uint typeCount);
-            Marshal.ThrowExceptionForHR(hresult);
-            Debug.Assert(typeCount <= 1);
             if (typeCount == 0)
             {
+                // COM objects should return a type count of 0 to indicate that type info is not exposed.
+                // Some COM objects may return a non-success HRESULT when type info is not supported, so
+                // we only check the count and not the HRESULT in this case.
                 return null;
             }
 
+            Marshal.ThrowExceptionForHR(hresult);
+            Debug.Assert(typeCount == 1);
+
             IntPtr typeInfoPtr;
             hresult = dispatch.TryGetTypeInfo(0, 0, out typeInfoPtr);
             if (!ComHresults.IsSuccess(hresult))
index b975142..572d0b3 100644 (file)
@@ -1,13 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
   <assemblyIdentity
-    type="win32" 
+    type="win32"
     name="COMDynamicTest"
     version="1.0.0.0" />
 
   <dependency>
     <dependentAssembly>
-      <!-- RegFree COM -->
+      <!-- RegFree COM to activate managed server -->
+      <assemblyIdentity
+          type="win32"
+          name="CoreShim.X"
+          version="1.0.0.0"/>
+    </dependentAssembly>
+  </dependency>
+  <dependency>
+    <dependentAssembly>
+      <!-- RegFree COM to activate native server-->
       <assemblyIdentity
           type="win32"
           name="DynamicTestServer.X"
diff --git a/src/tests/Interop/COM/Dynamic/CoreShim.X.manifest b/src/tests/Interop/COM/Dynamic/CoreShim.X.manifest
new file mode 100644 (file)
index 0000000..abb39fb
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+
+<assemblyIdentity
+  type="win32"
+  name="CoreShim.X"
+  version="1.0.0.0" />
+
+<file name="CoreShim.dll">
+  <!-- ConsumeNETServerTesting -->
+  <comClass
+    clsid="{DE4ACF53-5957-4D31-8BE2-EA6C80683246}"
+    threadingModel="Both" />
+</file>
+
+</assembly>
index 504edaf..af0ab54 100644 (file)
@@ -3,6 +3,7 @@
     <OutputType>Exe</OutputType>
     <ApplicationManifest>App.manifest</ApplicationManifest>
     <IsManagedCOMClient>true</IsManagedCOMClient>
+    <UseManagedCOMServer>true</UseManagedCOMServer>
     <!-- This test is very slow under some GCStress variations, especially with COMPlus_HeapVerify=1, so disable it under GCStress to avoid test timeouts in the CI.
          Issue: https://github.com/dotnet/runtime/issues/39584
     -->
     <Compile Include="CollectionTest.cs" />
     <Compile Include="EventTest.cs" />
     <Compile Include="ParametersTest.cs" />
+    <Compile Include="NETServerTest.cs" />
     <Compile Include="Program.cs" />
     <Compile Include="ServerGuids.cs" />
+    <Compile Include="../ServerContracts/ServerGuids.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="CoreShim.X.manifest">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="Server/CMakeLists.txt" />
     <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+    <ProjectReference Include="../NETServer/NETServer.csproj">
+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+      <OutputItemType>Content</OutputItemType>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </ProjectReference>
   </ItemGroup>
 </Project>
diff --git a/src/tests/Interop/COM/Dynamic/NETServerTest.cs b/src/tests/Interop/COM/Dynamic/NETServerTest.cs
new file mode 100644 (file)
index 0000000..eafd092
--- /dev/null
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Dynamic
+{
+    using System;
+    using System.Runtime.InteropServices;
+    using TestLibrary;
+
+    internal class NETServerTest
+    {
+        public void Run()
+        {
+            Console.WriteLine($"Running {nameof(NETServerTest)}");
+
+            // Initialize CoreShim and hostpolicymock
+            HostPolicyMock.Initialize(Environment.CurrentDirectory, null);
+            Environment.SetEnvironmentVariable("CORESHIM_COMACT_ASSEMBLYNAME", "NETServer");
+            Environment.SetEnvironmentVariable("CORESHIM_COMACT_TYPENAME", "ConsumeNETServerTesting");
+
+            using (HostPolicyMock.Mock_corehost_resolve_component_dependencies(
+                    0,
+                    string.Empty,
+                    string.Empty,
+                    string.Empty))
+            {
+                Type t = Type.GetTypeFromCLSID(Guid.Parse(Server.Contract.Guids.ConsumeNETServerTesting));
+                dynamic obj = Activator.CreateInstance(t);
+
+                try
+                {
+                    Assert.IsTrue(obj.EqualByCCW(obj));
+                    Assert.IsTrue(obj.NotEqualByRCW(obj));
+                }
+                finally
+                {
+                    obj.ReleaseResources();
+                }
+            }
+        }
+    }
+}
index 7795c8f..8f22c9e 100644 (file)
@@ -22,6 +22,7 @@ namespace Dynamic
                 new CollectionTest().Run();
                 new EventTest().Run();
                 new ParametersTest().Run();
+                new NETServerTest().Run();
             }
             catch (Exception e)
             {