Skip to content

[API Proposal]: COM source generator APIs #79121

Description

@AaronRobinsonMSFT

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

usingSystem.Collections;namespaceSystem.Runtime.InteropServices.Marshalling;/// <summary>/// Information about a virtual method table and the unmanaged instance pointer./// </summary>publicreadonlyunsafestructVirtualMethodTableInfo{/// <summary>/// Construct a <see cref="VirtualMethodTableInfo"/> from a given instance pointer and table memory./// </summary>/// <param name="thisPointer">The pointer to the instance.</param>/// <param name="virtualMethodTable">The block of memory that represents the virtual method table.</param>publicVirtualMethodTableInfo(void*thisPointer,void**virtualMethodTable);/// <summary>/// The unmanaged instance pointer/// </summary>publicvoid*ThisPointer{get;}/// <summary>/// The virtual method table./// </summary>publicvoid**VirtualMethodTable{get;}/// <summary>/// Deconstruct this structure into its two fields./// </summary>/// <param name="thisPointer">The <see cref="ThisPointer"/> result</param>/// <param name="virtualMethodTable">The <see cref="VirtualMethodTable"/> result</param>publicvoidDeconstruct(outvoid*thisPointer,outvoid**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>publicinterfaceIUnmanagedVirtualMethodTableProvider{/// <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>publicVirtualMethodTableInfoGetVirtualMethodTableInfoForKey(Typetype);}/// <summary>/// This interface allows another interface to define that it represents a managed projection of an unmanaged interface from some unmanaged type system and supports passing managed implementations of unmanaged interfaces to unmanaged code./// </summary>publicinterfaceIUnmanagedInterfaceType{/// <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>publicabstractstaticunsafevoid*VirtualMethodTableManagedImplementation{get;}}/// <summary>/// Identify the Interface ID (IID) for an IUnknown based interface./// </summary>publicinterfaceIIUnknownInterfaceType:IUnmanagedInterfaceType{publicabstractstaticGuidIid{get;}}/// <summary>/// Details for the IUnknown derived interface./// </summary>publicinterfaceIUnknownDerivedDetails{/// <summary>/// Interface ID./// </summary>publicGuidIid{get;}/// <summary>/// Managed type used to project the IUnknown derived interface./// </summary>publicTypeImplementation{get;}/// <summary>/// A pointer to the virtual method table to enable unmanaged callers to call a managed implementation of the interface./// </summary>publicunsafevoid*VirtualMethodTableManagedImplementation{get;}}[AttributeUsage(AttributeTargets.Interface)]publicclassIUnknownDerivedAttribute<T,TImpl>:Attribute,IUnknownDerivedDetailswhereT:IIUnknownInterfaceTypewhereTImpl:T{/// <inheritdoc />publicGuidIid{get;}/// <inheritdoc />publicTypeImplementation{get;}/// <inheritdoc />publicunsafevoid*VirtualMethodTableManagedImplementation{get;}}/// <summary>/// IUnknown interaction strategy./// </summary>publicunsafeinterfaceIIUnknownStrategy{/// <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>publicvoid*CreateInstancePointer(void*unknown);/// <summary>/// Perform a QueryInterface() for an IID on the unmanaged instance./// </summary>/// <param name="instancePtr">A pointer representing the unmanaged 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)"/>publicintQueryInterface(void*instancePtr,inGuidiid,outvoid*ppObj);/// <summary>/// Perform a Release() call on the supplied unmanaged instance./// </summary>/// <param name="instancePtr">A pointer representing the unmanaged instance.</param>/// <returns>The current reference count.</returns>/// <seealso cref="Marshal.Release(nint)"/>publicintRelease(void*instancePtr);}/// <summary>/// Strategy for acquiring interface details./// </summary>publicinterfaceIIUnknownInterfaceDetailsStrategy{/// <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(RuntimeTypeHandletype);}/// <summary>/// Unmanaged virtual method table look up strategy./// </summary>publicunsafeinterfaceIIUnknownCacheStrategy{publicreadonlystructTableInfo{publicvoid*ThisPtr{get;init;}publicvoid**Table{get;init;}publicRuntimeTypeHandleManagedType{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>TableInfoConstructTableInfo(RuntimeTypeHandlehandle,IUnknownDerivedDetailsinterfaceDetails,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>boolTryGetTableInfo(RuntimeTypeHandlehandle,outTableInfoinfo);/// <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>boolTrySetTableInfo(RuntimeTypeHandlehandle,TableInfoinfo);/// <summary>/// Clear the cache/// </summary>/// <param name="unknownStrategy">The <see cref="IIUnknownStrategy"/> to use for clearing</param>voidClear(IIUnknownStrategyunknownStrategy);}/// <summary>/// Base class for all COM source generated Runtime Callable Wrapper (RCWs)./// </summary>publicsealedclassComObject: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>publicIDisposable?FinalRelease{get;}/// <inheritdoc />RuntimeTypeHandleIDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTypeHandleinterfaceType);/// <inheritdoc />boolIDynamicInterfaceCastable.IsInterfaceImplemented(RuntimeTypeHandleinterfaceType,boolthrowIfNotImplemented);/// <inheritdoc />VirtualMethodTableInfoIUnmanagedVirtualMethodTableProvider.GetVirtualMethodTableInfoForKey(Typetype);}[AttributeUsage(AttributeTargets.Interface)]publicsealedclassGeneratedComInterfaceAttribute<TComWrappers>:AttributewhereTComWrappers:GeneratedComWrappersBase{}publicabstractclassGeneratedComWrappersBase:ComWrappers{protectedvirtualIIUnknownInterfaceDetailsStrategyCreateInterfaceDetailsStrategy()=>DefaultIUnknownInterfaceDetailsStrategy.Instance;protectedvirtualIIUnknownStrategyCreateIUnknownStrategy()=>FreeThreadedStrategy.Instance;protectedvirtualIIUnknownCacheStrategyCreateCacheStrategy()=>newDefaultCaching();protectedoverridesealedunsafeobjectCreateObject(nintexternalComObject,CreateObjectFlagsflags);protectedoverridesealedvoidReleaseObjects(IEnumerableobjects);publicComObjectGetOrCreateUniqueObjectForComInstance(nintcomInstance,CreateObjectFlagsflags);}publicsealedclassDefaultIUnknownInterfaceDetailsStrategy:IIUnknownInterfaceDetailsStrategy{publicstaticreadonlyIIUnknownInterfaceDetailsStrategyInstance;publicIUnknownDerivedDetails?GetIUnknownDerivedDetails(RuntimeTypeHandletype);}publicsealedunsafeclassFreeThreadedStrategy:IIUnknownStrategy{publicstaticreadonlyIIUnknownStrategyInstance;void*IIUnknownStrategy.CreateInstancePointer(void*unknown);unsafeintIIUnknownStrategy.QueryInterface(void*thisPtr,inGuidhandle,outvoid*ppObj);unsafeintIIUnknownStrategy.Release(void*thisPtr);}publicsealedunsafeclassDefaultCaching:IIUnknownCacheStrategy{privatereadonlyDictionary<RuntimeTypeHandle,IIUnknownCacheStrategy.TableInfo>_cache=new();IIUnknownCacheStrategy.TableInfoIIUnknownCacheStrategy.ConstructTableInfo(RuntimeTypeHandlehandle,IUnknownDerivedDetailsdetails,void*ptr);boolIIUnknownCacheStrategy.TryGetTableInfo(RuntimeTypeHandlehandle,outIIUnknownCacheStrategy.TableInfoinfo);boolIIUnknownCacheStrategy.TrySetTableInfo(RuntimeTypeHandlehandle,IIUnknownCacheStrategy.TableInfoinfo);voidIIUnknownCacheStrategy.Clear(IIUnknownStrategyunknownStrategy);}
Original API proposal for reference from comments

API Proposal

usingSystem.Runtime.InteropServices;/// <summary>/// Details for the IUnknown derived interface./// </summary>publicinterfaceIUnknownDerivedDetails{/// <summary>/// Interface ID./// </summary>publicGuidIid{get;}/// <summary>/// Managed typed used to project the IUnknown derived interface./// </summary>publicTypeImplementation{get;}/// <summary>/// Total length of the vtable./// </summary>publicintVTableTotalLength{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)]publicclassIUnknownDerivedAttribute<T,TImpl>:Attribute,IUnknownDerivedDetailswhereT:IUnmanagedInterfaceType,IIUnknownInterfaceTypewhereTImpl:T{publicIUnknownDerivedAttribute();/// <inheritdoc />publicGuidIid=>T.Iid;/// <inheritdoc />publicTypeImplementation=>typeof(TImpl);/// <inheritdoc />publicintVTableTotalLength=>T.VTableLength;}/// <summary>/// IUnknown interaction strategy./// </summary>publicunsafeinterfaceIIUnknownStrategy{/// <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)"/>publicintQueryInterface(void*thisPtr,inGuidiid,outvoid*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)"/>publicintRelease(void*thisPtr);}/// <summary>/// Strategy for acquiring interface details./// </summary>publicinterfaceIIUnknownInterfaceDetailsStrategy{/// <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(RuntimeTypeHandletype);}/// <summary>/// Unmanaged virtual method table look up strategy./// </summary>publicunsafeinterfaceIIUnknownCacheStrategy{publicreadonlystructTableInfo{publicvoid*ThisPtr{get;init;}publicvoid**Table{get;init;}publicintTableLength{get;init;}publicRuntimeTypeHandleManagedType{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>TableInfoConstructTableInfo(RuntimeTypeHandlehandle,IUnknownDerivedDetailsinterfaceDetails,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>boolTryGetTableInfo(RuntimeTypeHandlehandle,outTableInfoinfo);/// <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>boolTrySetTableInfo(RuntimeTypeHandlehandle,TableInfoinfo);/// <summary>/// Clear the cache/// </summary>/// <param name="unknownStrategy">The <see cref="IIUnknownStrategy"/> to use for clearing</param>voidClear(IIUnknownStrategyunknownStrategy);}/// <summary>/// Base class for all COM source generated Runtime Callable Wrapper (RCWs)./// </summary>publicabstractclassComObject:IDynamicInterfaceCastable,IUnmanagedVirtualMethodTableProvider{/// <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>protectedComObject(IIUnknownInterfaceDetailsStrategyinterfaceDetailsStrategy,IIUnknownStrategyiunknownStrategy,IIUnknownCacheStrategycacheStrategy);~ComObject();/// <summary>/// Pointer to the unmanaged instance./// </summary>protectedvoid*ThisPtr{get;init;}/// <summary>/// Interface details strategy./// </summary>protectedIIUnknownInterfaceDetailsStrategyInterfaceDetailsStrategy{get;init;}/// <summary>/// IUnknown interaction strategy./// </summary>protectedIIUnknownStrategyIUnknownStrategy{get;init;}/// <summary>/// Caching strategy./// </summary>protectedIIUnknownCacheStrategyCacheStrategy{get;init;}/// <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>publicIDisposable?FinalRelease{get;}/// <inheritdoc />RuntimeTypeHandleIDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTypeHandleinterfaceType);/// <inheritdoc />boolIDynamicInterfaceCastable.IsInterfaceImplemented(RuntimeTypeHandleinterfaceType,boolthrowIfNotImplemented);/// <inheritdoc />VirtualMethodTableInfoIUnmanagedVirtualMethodTableProvider.GetVirtualMethodTableInfoForKey(Typetype);}

API Usage

Working example: https://github.com/AaronRobinsonMSFT/ComObjectRedux

Alternative Designs

No response

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions