Skip to content

Commit 1abc433

Browse files
authored
Enable loading COM component in default ALC via runtime config setting (#79026)
Allow COM components to opt-in to being loaded in the default ALC: - `comhost` checks for the `System.Runtime.InteropServices.COM.LoadComponentInDefaultContext` property and uses new functions on `ComActivator`, loading into the default context if the property is set to true - Default behaviour remains loading into an isolated context - Fall back to using `ComActivator` functions that always load into an isolated context if new functions aren't found and loading in an isolated context
1 parent 62d6100 commit 1abc433

File tree

11 files changed

+369
-88
lines changed

11 files changed

+369
-88
lines changed

src/coreclr/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Windows.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@
1919
</type>
2020
</assembly>
2121

22+
<assembly fullname="System.Private.CoreLib" feature="System.Runtime.InteropServices.BuiltInComInterop.IsSupported" featurevalue="true">
23+
<!-- Enables the .NET COM host (.NET 8.0+) to load a COM component. -->
24+
<type fullname="Internal.Runtime.InteropServices.ComActivator" >
25+
<method name="GetClassFactoryForTypeInContext" />
26+
<method name="RegisterClassForTypeInContext" />
27+
<method name="UnregisterClassForTypeInContext" />
28+
</type>
29+
</assembly>
30+
2231
<assembly fullname="System.Private.CoreLib" feature="System.Runtime.InteropServices.EnableCppCLIHostActivation" featurevalue="true">
2332
<!-- Enables the .NET IJW host (.NET 7.0+) to load an in-memory module as a .NET assembly. -->
2433
<type fullname="Internal.Runtime.InteropServices.InMemoryAssemblyLoader">

src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComActivationContextInternal.cs

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,4 @@ internal unsafe struct ComActivationContextInternal
1717
public char* TypeNameBuffer;
1818
public IntPtr ClassFactoryDest;
1919
}
20-
21-
//
22-
// Types below are 'public' only to aid in testing of functionality.
23-
// They should not be considered publicly consumable.
24-
//
25-
26-
[StructLayout(LayoutKind.Sequential)]
27-
internal partial struct ComActivationContext
28-
{
29-
public Guid ClassId;
30-
public Guid InterfaceId;
31-
public string AssemblyPath;
32-
public string AssemblyName;
33-
public string TypeName;
34-
}
35-
36-
[ComImport]
37-
[ComVisible(false)]
38-
[Guid("00000001-0000-0000-C000-000000000046")]
39-
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
40-
internal interface IClassFactory
41-
{
42-
[RequiresUnreferencedCode("Built-in COM support is not trim compatible", Url = "https://aka.ms/dotnet-illink/com")]
43-
void CreateInstance(
44-
[MarshalAs(UnmanagedType.Interface)] object? pUnkOuter,
45-
ref Guid riid,
46-
out IntPtr ppvObject);
47-
48-
void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock);
49-
}
5020
}

src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComActivator.cs

Lines changed: 146 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ internal struct LICINFO
2929
public bool fLicVerified;
3030
}
3131

32+
[ComImport]
33+
[ComVisible(false)]
34+
[Guid("00000001-0000-0000-C000-000000000046")]
35+
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
36+
internal interface IClassFactory
37+
{
38+
[RequiresUnreferencedCode("Built-in COM support is not trim compatible", Url = "https://aka.ms/dotnet-illink/com")]
39+
void CreateInstance(
40+
[MarshalAs(UnmanagedType.Interface)] object? pUnkOuter,
41+
ref Guid riid,
42+
out IntPtr ppvObject);
43+
44+
void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock);
45+
}
46+
3247
[ComImport]
3348
[ComVisible(false)]
3449
[Guid("B196B28F-BAB4-101A-B69C-00AA00341D07")]
@@ -57,9 +72,17 @@ void CreateInstanceLic(
5772
out IntPtr ppvObject);
5873
}
5974

60-
internal partial struct ComActivationContext
75+
[StructLayout(LayoutKind.Sequential)]
76+
internal struct ComActivationContext
6177
{
62-
public static unsafe ComActivationContext Create(ref ComActivationContextInternal cxtInt)
78+
public Guid ClassId;
79+
public Guid InterfaceId;
80+
public string AssemblyPath;
81+
public string AssemblyName;
82+
public string TypeName;
83+
public bool IsolatedContext;
84+
85+
public static unsafe ComActivationContext Create(ref ComActivationContextInternal cxtInt, bool isolatedContext)
6386
{
6487
if (!Marshal.IsBuiltInComSupported)
6588
{
@@ -72,7 +95,8 @@ public static unsafe ComActivationContext Create(ref ComActivationContextInterna
7295
InterfaceId = cxtInt.InterfaceId,
7396
AssemblyPath = Marshal.PtrToStringUni(new IntPtr(cxtInt.AssemblyPathBuffer))!,
7497
AssemblyName = Marshal.PtrToStringUni(new IntPtr(cxtInt.AssemblyNameBuffer))!,
75-
TypeName = Marshal.PtrToStringUni(new IntPtr(cxtInt.TypeNameBuffer))!
98+
TypeName = Marshal.PtrToStringUni(new IntPtr(cxtInt.TypeNameBuffer))!,
99+
IsolatedContext = isolatedContext
76100
};
77101
}
78102
}
@@ -84,6 +108,9 @@ internal static class ComActivator
84108
// unloadable COM server ALCs, this will need to be changed.
85109
private static readonly Dictionary<string, AssemblyLoadContext> s_assemblyLoadContexts = new Dictionary<string, AssemblyLoadContext>(StringComparer.InvariantCultureIgnoreCase);
86110

111+
// COM component assembly paths loaded in the default ALC
112+
private static readonly HashSet<string> s_loadedInDefaultContext = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
113+
87114
/// <summary>
88115
/// Entry point for unmanaged COM activation API from managed code
89116
/// </summary>
@@ -107,7 +134,7 @@ private static object GetClassFactoryForType(ComActivationContext cxt)
107134
throw new ArgumentException(null, nameof(cxt));
108135
}
109136

110-
Type classType = FindClassType(cxt.ClassId, cxt.AssemblyPath, cxt.AssemblyName, cxt.TypeName);
137+
Type classType = FindClassType(cxt);
111138

112139
if (LicenseInteropProxy.HasLicense(classType))
113140
{
@@ -145,7 +172,7 @@ private static void ClassRegistrationScenarioForType(ComActivationContext cxt, b
145172
throw new ArgumentException(null, nameof(cxt));
146173
}
147174

148-
Type classType = FindClassType(cxt.ClassId, cxt.AssemblyPath, cxt.AssemblyName, cxt.TypeName);
175+
Type classType = FindClassType(cxt);
149176

150177
Type? currentType = classType;
151178
bool calledFunction = false;
@@ -213,17 +240,45 @@ private static void ClassRegistrationScenarioForType(ComActivationContext cxt, b
213240
}
214241

215242
/// <summary>
216-
/// Internal entry point for unmanaged COM activation API from native code
243+
/// Gets a class factory for COM activation in an isolated load context
217244
/// </summary>
218245
/// <param name="pCxtInt">Pointer to a <see cref="ComActivationContextInternal"/> instance</param>
219246
[UnmanagedCallersOnly]
220247
private static unsafe int GetClassFactoryForTypeInternal(ComActivationContextInternal* pCxtInt)
221248
{
222249
if (!Marshal.IsBuiltInComSupported)
223-
{
224250
throw new NotSupportedException(SR.NotSupported_COM);
225-
}
226251

252+
#pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml
253+
return GetClassFactoryForTypeImpl(pCxtInt, isolatedContext: true);
254+
#pragma warning restore IL2026
255+
}
256+
257+
/// <summary>
258+
/// Gets a class factory for COM activation in the specified load context
259+
/// </summary>
260+
/// <param name="pCxtInt">Pointer to a <see cref="ComActivationContextInternal"/> instance</param>
261+
/// <param name="loadContext">Load context - currently must be IntPtr.Zero (default context) or -1 (isolated context)</param>
262+
[UnmanagedCallersOnly]
263+
private static unsafe int GetClassFactoryForTypeInContext(ComActivationContextInternal* pCxtInt, IntPtr loadContext)
264+
{
265+
if (!Marshal.IsBuiltInComSupported)
266+
throw new NotSupportedException(SR.NotSupported_COM);
267+
268+
if (loadContext != IntPtr.Zero && loadContext != (IntPtr)(-1))
269+
throw new ArgumentOutOfRangeException(nameof(loadContext));
270+
271+
return GetClassFactoryForTypeLocal(pCxtInt, isolatedContext: loadContext != IntPtr.Zero);
272+
273+
// Use a local function for a targeted suppression of the requires unreferenced code warning
274+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
275+
Justification = "The same feature switch applies to GetClassFactoryForTypeInternal and this function. We rely on the warning from GetClassFactoryForTypeInternal.")]
276+
static int GetClassFactoryForTypeLocal(ComActivationContextInternal* pCxtInt, bool isolatedContext) => GetClassFactoryForTypeImpl(pCxtInt, isolatedContext);
277+
}
278+
279+
[RequiresUnreferencedCode("Built-in COM support is not trim compatible", Url = "https://aka.ms/dotnet-illink/com")]
280+
private static unsafe int GetClassFactoryForTypeImpl(ComActivationContextInternal* pCxtInt, bool isolatedContext)
281+
{
227282
ref ComActivationContextInternal cxtInt = ref *pCxtInt;
228283

229284
if (IsLoggingEnabled())
@@ -240,10 +295,8 @@ private static unsafe int GetClassFactoryForTypeInternal(ComActivationContextInt
240295

241296
try
242297
{
243-
var cxt = ComActivationContext.Create(ref cxtInt);
244-
#pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml
298+
var cxt = ComActivationContext.Create(ref cxtInt, isolatedContext);
245299
object cf = GetClassFactoryForType(cxt);
246-
#pragma warning restore IL2026
247300
IntPtr nativeIUnknown = Marshal.GetIUnknownForObject(cf);
248301
Marshal.WriteIntPtr(cxtInt.ClassFactoryDest, nativeIUnknown);
249302
}
@@ -256,17 +309,37 @@ private static unsafe int GetClassFactoryForTypeInternal(ComActivationContextInt
256309
}
257310

258311
/// <summary>
259-
/// Internal entry point for registering a managed COM server API from native code
312+
/// Registers a managed COM server in an isolated load context
260313
/// </summary>
261314
/// <param name="pCxtInt">Pointer to a <see cref="ComActivationContextInternal"/> instance</param>
262315
[UnmanagedCallersOnly]
263316
private static unsafe int RegisterClassForTypeInternal(ComActivationContextInternal* pCxtInt)
264317
{
265318
if (!Marshal.IsBuiltInComSupported)
266-
{
267319
throw new NotSupportedException(SR.NotSupported_COM);
268-
}
269320

321+
return RegisterClassForTypeImpl(pCxtInt, isolatedContext: true);
322+
}
323+
324+
/// <summary>
325+
/// Registers a managed COM server in the specified load context
326+
/// </summary>
327+
/// <param name="pCxtInt">Pointer to a <see cref="ComActivationContextInternal"/> instance</param>
328+
/// <param name="loadContext">Load context - currently must be IntPtr.Zero (default context) or -1 (isolated context)</param>
329+
[UnmanagedCallersOnly]
330+
private static unsafe int RegisterClassForTypeInContext(ComActivationContextInternal* pCxtInt, IntPtr loadContext)
331+
{
332+
if (!Marshal.IsBuiltInComSupported)
333+
throw new NotSupportedException(SR.NotSupported_COM);
334+
335+
if (loadContext != IntPtr.Zero && loadContext != (IntPtr)(-1))
336+
throw new ArgumentOutOfRangeException(nameof(loadContext));
337+
338+
return RegisterClassForTypeImpl(pCxtInt, isolatedContext: loadContext != IntPtr.Zero);
339+
}
340+
341+
private static unsafe int RegisterClassForTypeImpl(ComActivationContextInternal* pCxtInt, bool isolatedContext)
342+
{
270343
ref ComActivationContextInternal cxtInt = ref *pCxtInt;
271344

272345
if (IsLoggingEnabled())
@@ -289,7 +362,7 @@ private static unsafe int RegisterClassForTypeInternal(ComActivationContextInter
289362

290363
try
291364
{
292-
var cxt = ComActivationContext.Create(ref cxtInt);
365+
var cxt = ComActivationContext.Create(ref cxtInt, isolatedContext);
293366
ClassRegistrationScenarioForTypeLocal(cxt, register: true);
294367
}
295368
catch (Exception e)
@@ -306,16 +379,36 @@ private static unsafe int RegisterClassForTypeInternal(ComActivationContextInter
306379
}
307380

308381
/// <summary>
309-
/// Internal entry point for unregistering a managed COM server API from native code
382+
/// Unregisters a managed COM server in an isolated load context
310383
/// </summary>
311384
[UnmanagedCallersOnly]
312385
private static unsafe int UnregisterClassForTypeInternal(ComActivationContextInternal* pCxtInt)
313386
{
314387
if (!Marshal.IsBuiltInComSupported)
315-
{
316388
throw new NotSupportedException(SR.NotSupported_COM);
317-
}
318389

390+
return UnregisterClassForTypeImpl(pCxtInt, isolatedContext: true);
391+
}
392+
393+
/// <summary>
394+
/// Unregisters a managed COM server in the specified load context
395+
/// </summary>
396+
/// <param name="pCxtInt">Pointer to a <see cref="ComActivationContextInternal"/> instance</param>
397+
/// <param name="loadContext">Load context - currently must be IntPtr.Zero (default context) or -1 (isolated context)</param>
398+
[UnmanagedCallersOnly]
399+
private static unsafe int UnregisterClassForTypeInContext(ComActivationContextInternal* pCxtInt, IntPtr loadContext)
400+
{
401+
if (!Marshal.IsBuiltInComSupported)
402+
throw new NotSupportedException(SR.NotSupported_COM);
403+
404+
if (loadContext != IntPtr.Zero && loadContext != (IntPtr)(-1))
405+
throw new ArgumentOutOfRangeException(nameof(loadContext));
406+
407+
return UnregisterClassForTypeImpl(pCxtInt, isolatedContext: loadContext != IntPtr.Zero);
408+
}
409+
410+
private static unsafe int UnregisterClassForTypeImpl(ComActivationContextInternal* pCxtInt, bool isolatedContext)
411+
{
319412
ref ComActivationContextInternal cxtInt = ref *pCxtInt;
320413

321414
if (IsLoggingEnabled())
@@ -338,7 +431,7 @@ private static unsafe int UnregisterClassForTypeInternal(ComActivationContextInt
338431

339432
try
340433
{
341-
var cxt = ComActivationContext.Create(ref cxtInt);
434+
var cxt = ComActivationContext.Create(ref cxtInt, isolatedContext);
342435
ClassRegistrationScenarioForTypeLocal(cxt, register: false);
343436
}
344437
catch (Exception e)
@@ -370,14 +463,14 @@ private static void Log(string fmt, params object[] args)
370463
}
371464

372465
[RequiresUnreferencedCode("Built-in COM support is not trim compatible", Url = "https://aka.ms/dotnet-illink/com")]
373-
private static Type FindClassType(Guid clsid, string assemblyPath, string assemblyName, string typeName)
466+
private static Type FindClassType(ComActivationContext cxt)
374467
{
375468
try
376469
{
377-
AssemblyLoadContext alc = GetALC(assemblyPath);
378-
var assemblyNameLocal = new AssemblyName(assemblyName);
470+
AssemblyLoadContext alc = GetALC(cxt.AssemblyPath, cxt.IsolatedContext);
471+
var assemblyNameLocal = new AssemblyName(cxt.AssemblyName);
379472
Assembly assem = alc.LoadFromAssemblyName(assemblyNameLocal);
380-
Type? t = assem.GetType(typeName);
473+
Type? t = assem.GetType(cxt.TypeName);
381474
if (t != null)
382475
{
383476
return t;
@@ -387,7 +480,7 @@ private static Type FindClassType(Guid clsid, string assemblyPath, string assemb
387480
{
388481
if (IsLoggingEnabled())
389482
{
390-
Log($"COM Activation of {clsid} failed. {e}");
483+
Log($"COM Activation of {cxt.ClassId} failed. {e}");
391484
}
392485
}
393486

@@ -396,16 +489,39 @@ private static Type FindClassType(Guid clsid, string assemblyPath, string assemb
396489
}
397490

398491
[RequiresUnreferencedCode("The trimmer might remove types which are needed by the assemblies loaded in this method.")]
399-
private static AssemblyLoadContext GetALC(string assemblyPath)
492+
private static AssemblyLoadContext GetALC(string assemblyPath, bool isolatedContext)
400493
{
401494
AssemblyLoadContext? alc;
402-
403-
lock (s_assemblyLoadContexts)
495+
if (isolatedContext)
496+
{
497+
lock (s_assemblyLoadContexts)
498+
{
499+
if (!s_assemblyLoadContexts.TryGetValue(assemblyPath, out alc))
500+
{
501+
alc = new IsolatedComponentLoadContext(assemblyPath);
502+
s_assemblyLoadContexts.Add(assemblyPath, alc);
503+
}
504+
}
505+
}
506+
else
404507
{
405-
if (!s_assemblyLoadContexts.TryGetValue(assemblyPath, out alc))
508+
alc = AssemblyLoadContext.Default;
509+
lock (s_loadedInDefaultContext)
406510
{
407-
alc = new IsolatedComponentLoadContext(assemblyPath);
408-
s_assemblyLoadContexts.Add(assemblyPath, alc);
511+
if (!s_loadedInDefaultContext.Contains(assemblyPath))
512+
{
513+
var resolver = new AssemblyDependencyResolver(assemblyPath);
514+
AssemblyLoadContext.Default.Resolving +=
515+
(context, assemblyName) =>
516+
{
517+
string? assemblyPath = resolver.ResolveAssemblyToPath(assemblyName);
518+
return assemblyPath != null
519+
? context.LoadFromAssemblyPath(assemblyPath)
520+
: null;
521+
};
522+
523+
s_loadedInDefaultContext.Add(assemblyPath);
524+
}
409525
}
410526
}
411527

src/installer/tests/Assets/TestProjects/ComLibrary/ComLibrary.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Reflection;
56
using System.Runtime.InteropServices;
7+
using System.Runtime.Loader;
68

79
namespace ComLibrary
810
{
@@ -25,6 +27,8 @@ public class Server : IServer
2527
{
2628
public Server()
2729
{
30+
Assembly asm = Assembly.GetExecutingAssembly();
31+
Console.WriteLine($"{asm.GetName().Name}: AssemblyLoadContext = {AssemblyLoadContext.GetLoadContext(asm)}");
2832
Console.WriteLine($"New instance of {nameof(Server)} created");
2933
}
3034
}

0 commit comments

Comments
 (0)