Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/design/libraries/LibraryImportGenerator/Compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ Documentation on compatibility guidance and the current state. The version headi

Due to trimming issues with NativeAOT's implementation of `Activator.CreateInstance`, we have decided to change our recommendation of providing a public parameterless constructor for `ref`, `out`, and return scenarios to a requirement. We already required a parameterless constructor of some visibility, so changing to a requirement matches our design principles of taking breaking changes to make interop more understandable and enforce more of our best practices instead of going out of our way to provide backward compatibility at increasing costs.

### `UnmanagedType.Interface`

Support for `MarshalAs(UnmanagedType.Interface)` is added to the interop source generators. `UnmanagedType.Interface` will marshal a parameter/return value of a type `T` to a COM interface pointer the `ComInterfaceMarshaller<T>` type. It will not support marshalling through the built-in COM interop subsystem.

The `ComInterfaceMarshaller<T>` type has the following general behavior: An unmanaged pointer is marshalled to a managed object through `GetOrCreateObjectForComInstance` on a shared `StrategyBasedComWrappers` instance. A managed object is marshalled to an unmanaged pointer through that same shared instance with the `GetOrCreateComInterfaceForObject` method and then calling `QueryInterface` on the returned `IUnknown*` to get the pointer for the unmanaged interface with the IID from the managed type as defined by our default interface details strategy (or the IID of `IUnknown` if the managed type has no IID).

## Version 2 (.NET 7 Release)

The focus of version 2 is to support all repos that make up the .NET Product, including ASP.NET Core and Windows Forms, as well as all packages in dotnet/runtime.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace System.Runtime.InteropServices.Marshalling
/// </remarks>
/// <seealso cref="LibraryImportAttribute" />
/// <seealso cref="CustomMarshallerAttribute" />
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Delegate)]
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Delegate)]
public sealed class NativeMarshallingAttribute : Attribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ public static MarshallingInfoParser Create(StubEnvironment env, IGeneratorDiagno
diagnostics,
new MethodSignatureElementInfoProvider(env.Compilation, diagnostics, method, useSiteAttributeParsers),
useSiteAttributeParsers,
ImmutableArray.Create<IMarshallingInfoAttributeParser>(
ImmutableArray.Create<IMarshallingInfoAttributeParser>(
new MarshalAsAttributeParser(env.Compilation, diagnostics, defaultInfo),
new MarshalUsingAttributeParser(env.Compilation, diagnostics),
new NativeMarshallingAttributeParser(env.Compilation, diagnostics)),
new NativeMarshallingAttributeParser(env.Compilation, diagnostics),
new ComInterfaceMarshallingInfoProvider(env.Compilation)),
ImmutableArray.Create<ITypeBasedMarshallingInfoProvider>(
new SafeHandleMarshallingInfoProvider(env.Compilation, method.ContainingType),
new ArrayMarshallingInfoProvider(env.Compilation),
new CharMarshallingInfoProvider(defaultInfo),
new CharMarshallingInfoProvider(defaultInfo),
new StringMarshallingInfoProvider(env.Compilation, diagnostics, unparsedAttributeData, defaultInfo),
new BooleanMarshallingInfoProvider(),
new BlittableTypeMarshallingInfoProvider(env.Compilation)));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Microsoft.CodeAnalysis;

namespace Microsoft.Interop
{
/// <summary>
/// This class supports generating marshalling info for types with the <c>System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute</c> attribute.
/// </summary>
public class ComInterfaceMarshallingInfoProvider : IMarshallingInfoAttributeParser
{
private readonly Compilation _compilation;

public ComInterfaceMarshallingInfoProvider(Compilation compilation)
{
_compilation = compilation;
}

public bool CanParseAttributeType(INamedTypeSymbol attributeType) => attributeType.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute;

public MarshallingInfo? ParseAttribute(AttributeData attributeData, ITypeSymbol type, int indirectionDepth, UseSiteAttributeProvider useSiteAttributes, GetMarshallingInfoCallback marshallingInfoCallback)
{
return CreateComInterfaceMarshallingInfo(_compilation, type);
}

public static MarshallingInfo CreateComInterfaceMarshallingInfo(
Compilation compilation,
ITypeSymbol interfaceType)
{
INamedTypeSymbol? comInterfaceMarshaller = compilation.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_Marshalling_ComInterfaceMarshaller_Metadata);
if (comInterfaceMarshaller is null)
return new MissingSupportMarshallingInfo();

comInterfaceMarshaller = comInterfaceMarshaller.Construct(interfaceType);

if (ManualTypeMarshallingHelper.HasEntryPointMarshallerAttribute(comInterfaceMarshaller))
{
if (ManualTypeMarshallingHelper.TryGetValueMarshallersFromEntryType(comInterfaceMarshaller, interfaceType, compilation, out CustomTypeMarshallers? marshallers))
{
return new NativeMarshallingAttributeInfo(
EntryPointType: ManagedTypeInfo.CreateTypeInfoForTypeSymbol(comInterfaceMarshaller),
Marshallers: marshallers.Value);
}
}

return new MissingSupportMarshallingInfo();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;

Expand Down Expand Up @@ -98,6 +97,18 @@ UseSiteAttributeData IUseSiteAttributeParser.ParseAttribute(AttributeData attrib
}
}

// We'll support the UnmanagedType.Interface option, but we'll explicitly
// exclude ComImport types as they will not work as expected
// unless they are migrated to [GeneratedComInterface].
if (unmanagedType == UnmanagedType.Interface)
{
if (type is INamedTypeSymbol { IsComImport: true })
{
return new MarshalAsInfo(unmanagedType, _defaultInfo.CharEncoding);
}
return ComInterfaceMarshallingInfoProvider.CreateComInterfaceMarshallingInfo(_compilation, type);
}

if (isArrayType)
{
if (type is not IArrayTypeSymbol { ElementType: ITypeSymbol elementType })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

namespace Microsoft.Interop
{

/// <summary>
/// This class supports generating marshalling info for the <see cref="string"/> type.
/// This includes support for the <c>System.Runtime.InteropServices.StringMarshalling</c> enum.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public static class TypeNames

public const string UnmanagedCallersOnlyAttribute = "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute";

public const string System_Runtime_InteropServices_ComImportAttribute = "System.Runtime.InteropServices.ComImportAttribute";

public const string VirtualMethodIndexAttribute = "System.Runtime.InteropServices.Marshalling.VirtualMethodIndexAttribute";

public const string IUnmanagedVirtualMethodTableProvider = "System.Runtime.InteropServices.Marshalling.IUnmanagedVirtualMethodTableProvider";
Expand Down Expand Up @@ -140,5 +142,7 @@ public static string MarshalEx(InteropGenerationOptions options)
public const string IComExposedClass = "System.Runtime.InteropServices.Marshalling.IComExposedClass";

public const string System_Runtime_InteropServices_Marshalling_SafeHandleMarshaller_Metadata = "System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller`1";

public const string System_Runtime_InteropServices_Marshalling_ComInterfaceMarshaller_Metadata = "System.Runtime.InteropServices.Marshalling.ComInterfaceMarshaller`1";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,17 @@ public sealed partial class ComExposedClassAttribute<T> : System.Attribute, Syst
public ComExposedClassAttribute() { }
public unsafe System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry* GetComInterfaceEntries(out int count) { throw null; }
}
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
[System.CLSCompliantAttribute(false)]
[System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute(typeof(System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute.GenericPlaceholder), System.Runtime.InteropServices.Marshalling.MarshalMode.Default, typeof(System.Runtime.InteropServices.Marshalling.ComInterfaceMarshaller<>))]
public static unsafe class ComInterfaceMarshaller<T>
{
public static void* ConvertToUnmanaged(T? managed) { throw null; }
public static T? ConvertToManaged(void* unmanaged) { throw null; }
}
public sealed partial class ComObject : System.Runtime.InteropServices.IDynamicInterfaceCastable, System.Runtime.InteropServices.Marshalling.IUnmanagedVirtualMethodTableProvider
{
internal ComObject() { }
Expand Down Expand Up @@ -451,6 +462,17 @@ public StrategyBasedComWrappers() { }
protected virtual System.Runtime.InteropServices.Marshalling.IIUnknownStrategy GetOrCreateIUnknownStrategy() { throw null; }
protected sealed override void ReleaseObjects(System.Collections.IEnumerable objects) { }
}
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
[System.CLSCompliantAttribute(false)]
[System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute(typeof(System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute.GenericPlaceholder), System.Runtime.InteropServices.Marshalling.MarshalMode.Default, typeof(System.Runtime.InteropServices.Marshalling.UniqueComInterfaceMarshaller<>))]
public static unsafe class UniqueComInterfaceMarshaller<T>
{
public static void* ConvertToUnmanaged(T? managed) { throw null; }
public static T? ConvertToManaged(void* unmanaged) { throw null; }
}
[System.CLSCompliantAttribute(false)]
public readonly partial struct VirtualMethodTableInfo
{
Expand Down Expand Up @@ -688,10 +710,10 @@ public ComSourceInterfacesAttribute(System.Type sourceInterface1, System.Type so
public ComSourceInterfacesAttribute(System.Type sourceInterface1, System.Type sourceInterface2, System.Type sourceInterface3, System.Type sourceInterface4) { }
public string Value { get { throw null; } }
}
[System.Runtime.Versioning.UnsupportedOSPlatform("android")]
[System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
[System.Runtime.Versioning.UnsupportedOSPlatform("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatform("tvos")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
[System.CLSCompliantAttribute(false)]
public abstract class ComWrappers
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
<Compile Include="System\Runtime\InteropServices\ImportedFromTypeLibAttribute.cs" />
<Compile Include="System\Runtime\InteropServices\ManagedToNativeComInteropStubAttribute.cs" />
<Compile Include="System\Runtime\InteropServices\Marshalling\ComExposedClassAttribute.cs" />
<Compile Include="System\Runtime\InteropServices\Marshalling\UniqueComInterfaceMarshaller.cs" />
<Compile Include="System\Runtime\InteropServices\Marshalling\ComInterfaceMarshaller.cs" />
<Compile Include="System\Runtime\InteropServices\Marshalling\ComObject.cs" />
<Compile Include="System\Runtime\InteropServices\Marshalling\DefaultCaching.cs" />
<Compile Include="System\Runtime\InteropServices\Marshalling\DefaultIUnknownInterfaceDetailsStrategy.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.Versioning;

namespace System.Runtime.InteropServices.Marshalling
{
/// <summary>
/// COM interface marshaller using a StrategyBasedComWrappers instance
/// </summary>
/// <remarks>
/// This marshaller will always pass the <see cref="CreateObjectFlags.Unwrap"/> flag
/// to <see cref="ComWrappers.GetOrCreateObjectForComInstance(IntPtr, CreateObjectFlags)"/>.
/// </remarks>
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[CLSCompliant(false)]
[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder), MarshalMode.Default, typeof(ComInterfaceMarshaller<>))]
public static unsafe class ComInterfaceMarshaller<T>
{
private static readonly Guid? TargetInterfaceIID = StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(T).TypeHandle)?.Iid;

public static void* ConvertToUnmanaged(T? managed)
{
if (managed == null)
{
return null;
}
if (!ComWrappers.TryGetComInstance(managed, out nint unknown))
{
unknown = StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None);
}
return CastIUnknownToInterfaceType(unknown);
}

public static T? ConvertToManaged(void* unmanaged)
{
if (unmanaged == null)
{
return default;
}
return (T)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateObjectForComInstance((nint)unmanaged, CreateObjectFlags.Unwrap);
}

internal static void* CastIUnknownToInterfaceType(nint unknown)
{
if (TargetInterfaceIID is null)
{
// If the managed type isn't a GeneratedComInterface-attributed type, we'll marshal to an IUnknown*.
return (void*)unknown;
}
Guid iid = TargetInterfaceIID.Value;
if (Marshal.QueryInterface(unknown, ref iid, out nint interfacePointer) != 0)
{
Marshal.Release(unknown);
throw new InvalidCastException($"Unable to cast the provided managed object to a COM interface with ID '{iid:B}'");
}
Marshal.Release(unknown);
return (void*)interfacePointer;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace System.Runtime.InteropServices.Marshalling
[CLSCompliant(false)]
public class StrategyBasedComWrappers : ComWrappers
{
internal static StrategyBasedComWrappers DefaultMarshallingInstance { get; } = new();

public static IIUnknownInterfaceDetailsStrategy DefaultIUnknownInterfaceDetailsStrategy { get; } = Marshalling.DefaultIUnknownInterfaceDetailsStrategy.Instance;

public static IIUnknownStrategy DefaultIUnknownStrategy { get; } = FreeThreadedStrategy.Instance;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.Versioning;

namespace System.Runtime.InteropServices.Marshalling
{
/// <summary>
/// COM interface marshaller using a StrategyBasedComWrappers instance
/// that will only create unique native object wrappers (RCW).
/// </summary>
/// <remarks>
/// This marshaller will always pass the <see cref="CreateObjectFlags.Unwrap"/> and <see cref="CreateObjectFlags.UniqueInstance"/> flags
/// to <see cref="ComWrappers.GetOrCreateObjectForComInstance(IntPtr, CreateObjectFlags)"/>.
/// </remarks>
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[CLSCompliant(false)]
[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder), MarshalMode.Default, typeof(UniqueComInterfaceMarshaller<>))]
public static unsafe class UniqueComInterfaceMarshaller<T>
{
public static void* ConvertToUnmanaged(T? managed)
{
if (managed == null)
{
return null;
}
if (!ComWrappers.TryGetComInstance(managed, out nint unknown))
{
unknown = StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None);
}
return ComInterfaceMarshaller<T>.CastIUnknownToInterfaceType(unknown);
}

public static T? ConvertToManaged(void* unmanaged)
{
if (unmanaged == null)
{
return default;
}
return (T)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateObjectForComInstance((nint)unmanaged, CreateObjectFlags.Unwrap | CreateObjectFlags.UniqueInstance);
}
}
}
Loading