-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: COM source generator APIs #79121
Comments
Tagging subscribers to this area: @dotnet/interop-contrib Issue DetailsBackground and motivationSource generation of interop code have proven successful in various projects, especially the build-in VTable based source generator design. API Proposalusing System.Runtime.InteropServices;
/// <summary>
/// Type used to uniquely identify a COM interface.
/// </summary>
/// <param name="Iid">Interface ID</param>
public readonly record struct InterfaceId(Guid Iid);
/// <summary>
/// Details for the IUnknown derived interface.
/// </summary>
public interface IUnknownDerivedDetails
{
/// <summary>
/// Interface ID.
/// </summary>
public Guid Iid { get; }
/// <summary>
/// Managed typed used to project the IUnknown derived interface.
/// </summary>
public Type Implementation { get; }
/// <summary>
/// Total length of the vtable.
/// </summary>
public int VTableTotalLength { get; }
}
/// <summary>
/// Attribute used to indicate an interface derives from IUnknown.
/// </summary>
/// <typeparam name="T">The managed definition of the derived interface.</typeparam>
/// <typeparam name="TImpl">The managed implementation of the derived interface.</typeparam>
[AttributeUsage(AttributeTargets.Interface)]
public class IUnknownDerivedAttribute<T, TImpl> : Attribute, IUnknownDerivedDetails
where T : IUnmanagedInterfaceType<InterfaceId>
where TImpl : T
{
public IUnknownDerivedAttribute();
/// <inheritdoc />
public Guid Iid => T.TypeKey.Iid;
/// <inheritdoc />
public Type Implementation => typeof(TImpl);
/// <inheritdoc />
public int VTableTotalLength => T.VTableLength;
}
/// <summary>
/// IUnknown interaction strategy.
/// </summary>
public unsafe interface IIUnknownStrategy
{
/// <summary>
/// Perform a QueryInterface() for an IID on the unmanaged IUnknown.
/// </summary>
/// <param name="thisPtr">The IUnknown instance.</param>
/// <param name="iid">The IID (Interface ID) to query for.</param>
/// <param name="ppObj">The resulting interface</param>
/// <returns>Returns an HRESULT represents the success of the operation</returns>
/// <seealso cref="Marshal.QueryInterface(nint, ref Guid, out nint)"/>
public int QueryInterface(void* thisPtr, in Guid iid, out void* ppObj);
/// <summary>
/// Perform a Release() call on the supplied IUnknown instance.
/// </summary>
/// <param name="thisPtr">The IUnknown instance.</param>
/// <returns>The current reference count.</returns>
/// <seealso cref="Marshal.Release(nint)"/>
public int Release(void* thisPtr);
}
/// <summary>
/// Strategy for acquiring interface details.
/// </summary>
public interface IIUnknownInterfaceDetailsStrategy
{
/// <summary>
/// Given a <see cref="RuntimeTypeHandle"/> get the IUnknown details.
/// </summary>
/// <param name="type">RuntimeTypeHandle instance</param>
/// <returns>Details if type is known.</returns>
IUnknownDerivedDetails? GetIUnknownDerivedDetails(RuntimeTypeHandle type);
}
/// <summary>
/// Unmanaged virtual method table look up strategy.
/// </summary>
public unsafe interface IIUnknownCacheStrategy
{
public readonly struct TableInfo
{
public void* ThisPtr { get; init; }
public void** Table { get; init; }
public int TableLength { get; init; }
public RuntimeTypeHandle ManagedType { get; init; }
}
/// <summary>
/// Construct a <see cref="TableInfo"/> instance.
/// </summary>
/// <param name="handle">RuntimeTypeHandle instance</param>
/// <param name="ptr">Pointer to the instance to query</param>
/// <param name="info">A <see cref="TableInfo"/> instance</param>
/// <returns>True if success, otherwise false.</returns>
TableInfo ConstructTableInfo(RuntimeTypeHandle handle, IUnknownDerivedDetails interfaceDetails, void* ptr);
/// <summary>
/// Get associated <see cref="TableInfo"/>.
/// </summary>
/// <param name="handle">RuntimeTypeHandle instance</param>
/// <param name="info">A <see cref="TableInfo"/> instance</param>
/// <returns>True if found, otherwise false.</returns>
bool TryGetTableInfo(RuntimeTypeHandle handle, out TableInfo info);
/// <summary>
/// Set associated <see cref="TableInfo"/>.
/// </summary>
/// <param name="handle">RuntimeTypeHandle instance</param>
/// <param name="info">A <see cref="TableInfo"/> instance</param>
/// <returns>True if set, otherwise false.</returns>
bool TrySetTableInfo(RuntimeTypeHandle handle, TableInfo info);
/// <summary>
/// Clear the cache
/// </summary>
/// <param name="unknownStrategy">The <see cref="IIUnknownStrategy"/> to use for clearing</param>
void Clear(IIUnknownStrategy unknownStrategy);
}
/// <summary>
/// Base class for all COM source generated Runtime Callable Wrapper (RCWs).
/// </summary>
public abstract class ComObject : IDynamicInterfaceCastable, IUnmanagedVirtualMethodTableProvider<InterfaceId>
{
/// <summary>
/// Initialize ComObject instance.
/// </summary>
/// <param name="interfaceDetailsStrategy">Strategy for getting details</param>
/// <param name="iunknownStrategy">Interaction strategy for IUnknown</param>
/// <param name="cacheStrategy">Caching strategy</param>
protected ComObject(IIUnknownInterfaceDetailsStrategy interfaceDetailsStrategy, IIUnknownStrategy iunknownStrategy, IIUnknownCacheStrategy cacheStrategy);
~ComObject();
/// <summary>
/// Pointer to the unmanaged instance.
/// </summary>
protected void* ThisPtr { get; init; }
/// <summary>
/// Interface details strategy.
/// </summary>
protected IIUnknownInterfaceDetailsStrategy InterfaceDetailsStrategy { get; init; }
/// <summary>
/// IUnknown interaction strategy.
/// </summary>
protected IIUnknownStrategy IUnknownStrategy { get; init; }
/// <summary>
/// Caching strategy.
/// </summary>
protected IIUnknownCacheStrategy CacheStrategy { get; init; }
/// <inheritdoc />
RuntimeTypeHandle IDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTypeHandle interfaceType);
/// <inheritdoc />
bool IDynamicInterfaceCastable.IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented);
/// <inheritdoc />
VirtualMethodTableInfo IUnmanagedVirtualMethodTableProvider<InterfaceId>.GetVirtualMethodTableInfoForKey(InterfaceId typeKey, Type type);
} API UsageWorking example: https://github.com/AaronRobinsonMSFT/ComObjectRedux Alternative DesignsNo response RisksNo response
|
A few questions:
|
This seems to be introducing number of abstractions in attempt to cover potential future non-COM scenarios. What would be the bare minimal API that is just for COM and nothing else? |
All of the types here are focused exclusively on COM. The different strategy interfaces allow developers to pay-for-play for each of the COM features they want to use. For example:
All of these statements are statements that customers could easily decide they want to do for performance that each make assumptions that we can't guarantee for all platforms. In the built-in COM support, nothing is pay-for-play. Everyone pays for the costs of every feature of COM. We'd like to reduce some of these costs and complexity in the new system by allowing people to pay for what they use (and for us to not need to layer on all of the support at once). Additionally, some of the support (in particular The only types mentioned in this proposal that is for "non-COM" scenarios are |
Given recent discussions this might not ever be a priority. Going forward do we think this level of support is necessary ever? I can think of WinRT, but are there others? |
Another way of saying this is, if .NET 8+ is continuing to be a cross-platform first platform should supporting/enabling a COM thread affinity scenario ever be a priority? |
I think we should provide a mechanism for developers to be able to use thread-affinized COM objects safely with source-generated COM, but I definitely agree that it shouldn't be a feature that everyone gets by default. It should be something that is either not in-box or is marked with There are enough commonly-used STA-thread-affinized COM objects that I think allowing people to opt-in to using them and still use source-generated COM for the marshalling stubs is a good idea. |
I think this really boils down to the question of what we're building. Are we building something that is meant to replace MCG and nothing else or are we building something that we want to be the future of COM interop in the way that I know we won't get 100% feature parity any time soon if ever, but explicitly removing elements of the design in a way that blocks off users from adding the features we don't want to build in, that feels wrong to me and that we're selling ourselves short of what we could build for our users. |
This would also include the debate over /// <summary>
/// Total length of the vtable.
/// </summary>
public int VTableTotalLength { get; } This could also be removed. There is no obvious reason for it other than the use of the |
What is the set of behaviors that this covers and how can these behaviors be achieved using the proposed interfaces? |
The free-threaded vs thread-affinity issue basically falls to how you get a pointer to use for a given COM interface. Do you just call QueryInterface and cache the pointer to be used for all threads, or do you capture the context or add the object to the GIT (or get an AgileReference) for the object so calls to it from different threads can be safely marshalled back to the correct thread apartment and have the results marshalled back? It's much cheaper to not handle any of this complexity, but sometimes this complexity is required by the unmanged COM object (most if not all Windows Shell APIs come to mind). The support could be achieved by providing a customized |
Does it mean that there will be multiple calls of these strategy interfaces for each actual .NET -> COM call? It looks expensive. |
It can get expensive for sure. If we assume the interface has registered proxy/stubs, then we could rely on COM apartment marshalling. This would reduce our need for |
Once there's an entry in the cache for whatever the user has defined as the requirements (once per Type or once per Type+apartment), there will only be one call to a strategy interface to get the table info from the |
I've updated this proposal based on our experiments in the ComObjectRedux repo (referenced in the API usage section). |
Moved the "vtable"-level APIs that are absolutely required for the COM generator into this issue and out of #80204 to ensure we get to all of the COM APIs first before we get to the generalized vtable APIs. |
using System.Collections;
namespace System.Runtime.InteropServices.Marshalling;
public readonly unsafe struct VirtualMethodTableInfo
{
public VirtualMethodTableInfo(void* thisPointer, void** virtualMethodTable);
public void* ThisPointer { get; }
public void** VirtualMethodTable { get; }
public void Deconstruct(out void* thisPointer, out void** virtualMethodTable);
}
public interface IUnmanagedVirtualMethodTableProvider
{
VirtualMethodTableInfo GetVirtualMethodTableInfoForKey(Type type);
}
public interface IUnmanagedInterfaceType
{
public abstract static unsafe void* VirtualMethodTableManagedImplementation { get; }
}
public interface IIUnknownInterfaceType : IUnmanagedInterfaceType
{
public abstract static Guid Iid { get; }
}
public interface IUnknownDerivedDetails
{
Guid Iid { get; }
Type Implementation { get; }
unsafe void* VirtualMethodTableManagedImplementation { get; }
}
[AttributeUsage(AttributeTargets.Interface)]
public class IUnknownDerivedAttribute<T, TImpl> : Attribute, IUnknownDerivedDetails
where T : IIUnknownInterfaceType
where TImpl : T
{
public Guid Iid { get; }
public Type Implementation { get; }
public unsafe void* VirtualMethodTableManagedImplementation { get; }
}
public unsafe interface IIUnknownStrategy
{
void* CreateInstancePointer(void* unknown);
int QueryInterface(void* instancePtr, in Guid iid, out void* ppObj);
int Release(void* instancePtr);
}
public interface IIUnknownInterfaceDetailsStrategy
{
IUnknownDerivedDetails? GetIUnknownDerivedDetails(RuntimeTypeHandle type);
}
public unsafe interface IIUnknownCacheStrategy
{
public readonly struct TableInfo
{
public void* ThisPtr { get; init; }
public void** Table { get; init; }
public RuntimeTypeHandle ManagedType { get; init; }
}
TableInfo ConstructTableInfo(RuntimeTypeHandle handle, IUnknownDerivedDetails interfaceDetails, void* ptr);
bool TryGetTableInfo(RuntimeTypeHandle handle, out TableInfo info);
bool TrySetTableInfo(RuntimeTypeHandle handle, TableInfo info);
void Clear(IIUnknownStrategy unknownStrategy);
}
public sealed class ComObject : IDynamicInterfaceCastable, IUnmanagedVirtualMethodTableProvider
{
~ComObject();
public IDisposable? FinalRelease { get; }
RuntimeTypeHandle IDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTypeHandle interfaceType);
bool IDynamicInterfaceCastable.IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented);
VirtualMethodTableInfo IUnmanagedVirtualMethodTableProvider.GetVirtualMethodTableInfoForKey(Type type);
}
[AttributeUsage(AttributeTargets.Interface)]
public sealed class GeneratedComInterfaceAttribute<TComWrappers> : Attribute
where TComWrappers : GeneratedComWrappersBase
{
}
public abstract class GeneratedComWrappersBase : ComWrappers
{
protected virtual IIUnknownInterfaceDetailsStrategy CreateInterfaceDetailsStrategy() => DefaultIUnknownInterfaceDetailsStrategy.Instance;
protected virtual IIUnknownStrategy CreateIUnknownStrategy() => FreeThreadedStrategy.Instance;
protected virtual IIUnknownCacheStrategy CreateCacheStrategy() => new DefaultCaching();
protected override sealed unsafe object CreateObject(nint externalComObject, CreateObjectFlags flags);
protected override sealed void ReleaseObjects(IEnumerable objects);
public ComObject GetOrCreateUniqueObjectForComInstance(nint comInstance, CreateObjectFlags flags);
}
public sealed class DefaultIUnknownInterfaceDetailsStrategy : IIUnknownInterfaceDetailsStrategy
{
public static readonly IIUnknownInterfaceDetailsStrategy Instance;
public IUnknownDerivedDetails? GetIUnknownDerivedDetails(RuntimeTypeHandle type);
}
public sealed unsafe class FreeThreadedStrategy : IIUnknownStrategy
{
public static readonly IIUnknownStrategy Instance;
void* IIUnknownStrategy.CreateInstancePointer(void* unknown);
unsafe int IIUnknownStrategy.QueryInterface(void* thisPtr, in Guid handle, out void* ppObj);
unsafe int IIUnknownStrategy.Release(void* thisPtr);
}
public sealed unsafe class DefaultCaching : IIUnknownCacheStrategy
{
private readonly Dictionary<RuntimeTypeHandle, IIUnknownCacheStrategy.TableInfo> _cache = new();
IIUnknownCacheStrategy.TableInfo IIUnknownCacheStrategy.ConstructTableInfo(RuntimeTypeHandle handle, IUnknownDerivedDetails details, void* ptr);
bool IIUnknownCacheStrategy.TryGetTableInfo(RuntimeTypeHandle handle, out IIUnknownCacheStrategy.TableInfo info);
bool IIUnknownCacheStrategy.TrySetTableInfo(RuntimeTypeHandle handle, IIUnknownCacheStrategy.TableInfo info);
void IIUnknownCacheStrategy.Clear(IIUnknownStrategy unknownStrategy);
} |
(Some updates from the previous session may not have been applied to the surface here, but it should be pretty close to correct.) using System.Collections;
namespace System.Runtime.InteropServices.Marshalling;
/// <summary>
/// Information about a virtual method table and the unmanaged instance pointer.
/// </summary>
public readonly unsafe struct VirtualMethodTableInfo
{
/// <summary>
/// Construct a <see cref="VirtualMethodTableInfo"/> from a given instance pointer and table memory.
/// </summary>
/// <param name="instance">The pointer to the instance.</param>
/// <param name="virtualMethodTable">The block of memory that represents the virtual method table.</param>
public VirtualMethodTableInfo(void* instance, void** virtualMethodTable);
/// <summary>
/// The unmanaged instance pointer
/// </summary>
public void* Instance { get; }
/// <summary>
/// The virtual method table.
/// </summary>
public void** VirtualMethodTable { get; }
/// <summary>
/// Deconstruct this structure into its two fields.
/// </summary>
/// <param name="instance">The <see cref="Instance"/> result</param>
/// <param name="virtualMethodTable">The <see cref="VirtualMethodTable"/> result</param>
public void Deconstruct(out void* instance, out void** virtualMethodTable);
}
/// <summary>
/// This interface allows an object to provide information about a virtual method table for a managed interface to enable invoking methods in the virtual method table.
/// </summary>
public interface IUnmanagedVirtualMethodTableProvider
{
/// <summary>
/// Get the information about the virtual method table for a given unmanaged interface type represented by <paramref name="type"/>.
/// </summary>
/// <param name="type">The managed type for the unmanaged interface.</param>
/// <returns>The virtual method table information for the unmanaged interface.</returns>
VirtualMethodTableInfo GetVirtualMethodTableInfoForKey(Type type);
}
/// <summary>
/// Identify the Interface ID (IID) for an IUnknown based interface.
/// </summary>
public interface IIUnknownInterfaceType : IUnmanagedInterfaceType
{
public abstract static Guid Iid { get; }
/// <summary>
/// Get a pointer to the virtual method table of managed implementations of the unmanaged interface type.
/// </summary>
/// <returns>A pointer to the virtual method table of managed implementations of the unmanaged interface type</returns>
/// <remarks>
/// Implementation will be provided by a source generator if not explicitly implemented.
/// This property can return <c>null</c>. If it does, then the interface is not supported for passing managed implementations to unmanaged code.
/// </remarks>
public abstract static unsafe void** ManagedVirtualMethodTable { get; }
}
/// <summary>
/// Details for the IUnknown derived interface.
/// </summary>
public interface IIUnknownDerivedDetails
{
/// <summary>
/// Interface ID.
/// </summary>
Guid Iid { get; }
/// <summary>
/// Managed type used to project the IUnknown derived interface.
/// </summary>
Type Implementation { get; }
/// <summary>
/// A pointer to the virtual method table to enable unmanaged callers to call a managed implementation of the interface.
/// </summary>
unsafe void** ManagedVirtualMethodTable { get; }
}
[AttributeUsage(AttributeTargets.Interface)]
public sealed class IIUnknownDerivedAttribute<T, TImpl> : Attribute, IIUnknownDerivedDetails
where T : IIUnknownInterfaceType
where TImpl : T
{
/// <inheritdoc />
public Guid Iid { get; }
/// <inheritdoc />
public Type Implementation { get; }
/// <inheritdoc />
public unsafe void** ManagedVirtualMethodTable { get; }
}
/// <summary>
/// IUnknown interaction strategy.
/// </summary>
public unsafe interface IIUnknownStrategy
{
/// <summary>
/// Create an instance pointer that represents the provided IUnknown instance.
/// </summary>
/// <param name="unknown">The IUnknown instance.</param>
/// <returns>A pointer representing the unmanaged instance.</returns>
/// <remarks>
/// This method is used to create an instance pointer that can be used to interact with the other members of this interface.
/// For example, this method can return an IAgileReference instance for the provided IUnknown instance
/// that can be used in the QueryInterface and Release methods to enable creating thread-local instance pointers to us
/// through the IAgileReference APIs instead of directly calling QueryInterface on the IUnknown.
/// </remarks>
public void* CreateInstancePointer(void* unknown);
/// <summary>
/// Perform a QueryInterface() for an IID on the unmanaged instance.
/// </summary>
/// <param name="instance">A pointer representing the unmanaged instance.</param>
/// <param name="interfaceId">The Interface ID (IID) to query for.</param>
/// <param name="obj">The resulting interface</param>
/// <returns>Returns an HRESULT represents the success of the operation</returns>
/// <seealso cref="Marshal.QueryInterface(nint, ref Guid, out nint)"/>
public int QueryInterface(void* instance, Guid interfaceId, out void* obj);
/// <summary>
/// Perform a Release() call on the supplied unmanaged instance.
/// </summary>
/// <param name="instance">A pointer representing the unmanaged instance.</param>
/// <returns>The current reference count.</returns>
/// <seealso cref="Marshal.Release(nint)"/>
public uint Release(void* instance);
}
/// <summary>
/// Strategy for acquiring interface details.
/// </summary>
public interface IIUnknownInterfaceDetailsStrategy
{
/// <summary>
/// Given a <see cref="RuntimeTypeHandle"/> get the IUnknown details.
/// </summary>
/// <param name="type">RuntimeTypeHandle instance</param>
/// <returns>Details if type is known.</returns>
IUnknownDerivedDetails? GetIUnknownDerivedDetails(RuntimeTypeHandle type);
}
/// <summary>
/// Unmanaged virtual method table look up strategy.
/// </summary>
public unsafe interface IIUnknownCacheStrategy
{
public readonly struct TableInfo
{
public void* Instance { get; init; }
public void** VirtualMethodTable { get; init; }
public RuntimeTypeHandle Implementation { get; init; }
}
/// <summary>
/// Construct a <see cref="TableInfo"/> instance.
/// </summary>
/// <param name="handle">RuntimeTypeHandle instance</param>
/// <param name="ptr">Pointer to the instance to query</param>
/// <param name="info">A <see cref="TableInfo"/> instance</param>
/// <returns>True if success, otherwise false.</returns>
TableInfo ConstructTableInfo(RuntimeTypeHandle handle, IUnknownDerivedDetails interfaceDetails, void* ptr);
/// <summary>
/// Get associated <see cref="TableInfo"/>.
/// </summary>
/// <param name="handle">RuntimeTypeHandle instance</param>
/// <param name="info">A <see cref="TableInfo"/> instance</param>
/// <returns>True if found, otherwise false.</returns>
bool TryGetTableInfo(RuntimeTypeHandle handle, out TableInfo info);
/// <summary>
/// Set associated <see cref="TableInfo"/>.
/// </summary>
/// <param name="handle">RuntimeTypeHandle instance</param>
/// <param name="info">A <see cref="TableInfo"/> instance</param>
/// <returns>True if set, otherwise false.</returns>
bool TrySetTableInfo(RuntimeTypeHandle handle, TableInfo info);
/// <summary>
/// Clear the cache
/// </summary>
/// <param name="unknownStrategy">The <see cref="IIUnknownStrategy"/> to use for clearing</param>
void Clear(IIUnknownStrategy unknownStrategy);
}
/// <summary>
/// Base class for all COM source generated Runtime Callable Wrapper (RCWs).
/// </summary>
public sealed class ComObject : IDynamicInterfaceCastable, IUnmanagedVirtualMethodTableProvider
{
~ComObject();
/// <summary>
/// Returns an IDisposable that can be used to perform a final release
/// on this COM object wrapper.
/// </summary>
/// <remarks>
/// This property will only be non-null if the ComObject was created using
/// CreateObjectFlags.UniqueInstance.
/// </remarks>
public void FinalRelease();
/// <inheritdoc />
RuntimeTypeHandle IDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTypeHandle interfaceType);
/// <inheritdoc />
bool IDynamicInterfaceCastable.IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented);
/// <inheritdoc />
VirtualMethodTableInfo IUnmanagedVirtualMethodTableProvider.GetVirtualMethodTableInfoForKey(Type type);
}
[AttributeUsage(AttributeTargets.Interface)]
public sealed class GeneratedComInterfaceAttribute : Attribute
{
}
public abstract class StrategyBasedComWrappers : ComWrappers
{
public static IIUnknownInterfaceDetailsStrategy DefaultIUnknownInterfaceDetailsStrategy { get; }
public static IIUnknownStrategy DefaultIIUnknownStrategy { get; }
protected static IIUnknownCacheStrategy CreateDefaultCacheStrategy();
protected virtual IIUnknownInterfaceDetailsStrategy GetOrCreateInterfaceDetailsStrategy() => DefaultIUnknownInterfaceDetailsStrategy;
protected virtual IIUnknownStrategy GetOrCreateIUnknownStrategy() => FreeThreadedStrategy;
protected virtual IIUnknownCacheStrategy CreateCacheStrategy() => CreateDefaultCacheStrategy();
protected override sealed unsafe object CreateObject(nint externalComObject, CreateObjectFlags flags);
protected override sealed void ReleaseObjects(IEnumerable objects);
} |
Is there a possibility to support .NET inheritance for COM inheritance? i.e. [ComImport, Guid(...), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IFoo
{
void Method1();
}
[ComImport, Guid(...), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IFoo2 : IFoo
{
void Method2();
} with the vtable for
|
Yes, we plan to support using inheritance to represent COM inheritance. |
Background and motivation
Source generation of interop code has proven successful in various projects, especially the build-in
LibraryImport
. Supporting this for COM interop is the next logical step but requires new APIs for defining how the source is generated, UX for requesting generation, and hooks for customization.VTable based source generator design.
Work item for COM source generator: #76767
API Proposal
Original API proposal for reference from comments
API Proposal
API Usage
Working example: https://github.com/AaronRobinsonMSFT/ComObjectRedux
Alternative Designs
No response
Risks
No response
The text was updated successfully, but these errors were encountered: