microsoft / CsWinRT

C# language projection for the Windows Runtime
MIT License
554 stars 107 forks source link

Proposal: Hope to provide a attribute that connects the COM source generation and CSWINRT generation interfaces #1851

Open Gaoyifei1011 opened 2 weeks ago

Gaoyifei1011 commented 2 weeks ago

Summary

Hope to provide a attribute that connects the COM source generation and CSWINRT generation interfaces

------------------------------------

希望能提供一个连接 COM 源生成和 CSWINRT 生成接口的属性


Rationale

There are some native interop interfaces in WinRT that need to be manually imported and the contents of those interfaces defined. In CppWinRT, this is very easy, but in .NET, the automatically generated interface definitions and use are difficult due to the different rules for CsWinRT and .NET COM source generation definitions. For example, the IGraphicsEffectD2D1Interop COM interface needs to be used with the IGraphicsEffect and IGraphicsEffectSource interfaces. By default, IGraphicsEffect, IGraphicsEffectSource are defined via the WindowsRuntimeType attribute, and IGraphicsEffectD2D1Interop can be defined via both the GeneratedComInterface and WindowsRuntimeType attribute. The first option uses the WindowsRuntimeType attribute definition, which doesn't generate any code for the underlying operations and requires the ABI interface to be written manually, which is somewhat inconvenient. The second scenario uses GeneratedComInterface to define the IGraphicsEffectD2D1Interop interface. When the API is called, there is an incompatibility and you need to introduce the WindowsRuntimeType attribute and manually specify the IID property and the AbiToProjectionVftablePtr property in the IGraphicsEffectD2D1InteropMethods method. A friendly solution would be to have the AbiToProjectionVftablePtr property point directly to the virtual method table using the GeneratedComInterface's ManagedVirtualMethodTable. So hopefully CsWinRT will provide a attribute that can automatically link the GeneratedComInterface and WindowsRuntimeType attribute, making it easier to define and call some native interop interfaces.

------------------------------------

在 WinRT 中有一些本机互操作接口需要手动导入并定义这些接口的内容。在 CppWinRT 中,这样操作非常容易,然而在 .NET 中,由于 CsWinRT 和 .NET COM 源生成定义的规则不同,自动生成的接口定义并使用时遇到了困难。 比如 IGraphicsEffectD2D1Interop COM 接口,需要配合 IGraphicsEffect,IGraphicsEffectSource 接口使用。 默认情况下,IGraphicsEffect,IGraphicsEffectSource 是通过 WindowsRuntimeType 属性定义的,IGraphicsEffectD2D1Interop 既可以通过 GeneratedComInterface 定义,也可以通过 WindowsRuntimeType 属性定义。 第一种方案使用 WindowsRuntimeType 属性定义,这个属性定义不会生成任何底层操作的代码,需要手动写 ABI 接口,这某种程度上非常不方便。 第二种方案使用 GeneratedComInterface 定义 IGraphicsEffectD2D1Interop 接口。调用该接口时,却出现了不兼容,需要引入 WindowsRuntimeType 属性并在 IGraphicsEffectD2D1InteropMethods 方法中手动指定 IID 属性和 AbiToProjectionVftablePtr 属性。 一种友好的方案就是让 AbiToProjectionVftablePtr 属性直接使用 GeneratedComInterface 的 ManagedVirtualMethodTable 指向虚拟方法表的指针。 所以希望 CsWinRT 能提供一个可以自动将 GeneratedComInterface 和 WindowsRuntimeType 属性链接的属性,方便一些本机互操作接口的定义和调用。


Important Notes

New code that simplifies some operations

------------------------------------

新代码,简化了部分操作

    public static unsafe class IGraphicsEffectD2D1InteropMethods
    {
        public static Guid IID { get; } = typeof(IGraphicsEffectD2D1Interop).GUID;

        public static IntPtr AbiToProjectionVftablePtr { get; } = (IntPtr)StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IGraphicsEffectD2D1Interop).TypeHandle).ManagedVirtualMethodTable;
    }
    [GeneratedComInterface, WindowsRuntimeType, Guid("2FC57384-A068-44D7-A331-30982FCF7177")]
    public partial interface IGraphicsEffectD2D1Interop
    {
        [PreserveSig]
        int GetEffectId(out Guid id);

        [PreserveSig]
        int GetNamedPropertyMapping([MarshalAs(UnmanagedType.LPWStr)] string name, out uint index, out GRAPHICS_EFFECT_PROPERTY_MAPPING mapping);

        [PreserveSig]
        int GetPropertyCount(out uint count);

        [PreserveSig]
        int GetProperty(uint index, out IntPtr value);

        [PreserveSig]
        int GetSource(uint index, out IntPtr source);

        [PreserveSig]
        int GetSourceCount(out uint count);
    }

The old code needs to define the content inside Vftbl, which is not very convenient

------------------------------------

旧代码,需要定义 Vftbl 里面的内容,不是很方便

    public static class IGraphicsEffectD2D1InteropMethods
    {
        public static Guid IID { get; } = typeof(IGraphicsEffectD2D1Interop).GUID;
        public static IntPtr AbiToProjectionVftablePtr { get; } = IGraphicsEffectD2D1Interop.Vftbl.InitVtbl();
    }
 [WindowsRuntimeType, Guid("2FC57384-A068-44D7-A331-30982FCF7177")]
    public partial interface IGraphicsEffectD2D1Interop
    {
        int GetEffectId(out Guid id);

        int GetNamedPropertyMapping(IntPtr name, out uint index, out GRAPHICS_EFFECT_PROPERTY_MAPPING mapping);

        int GetPropertyCount(out uint count);

        int GetProperty(uint index, out IntPtr value);

        int GetSource(uint index, out IGraphicsEffectSource source);

        int GetSourceCount(out uint count);

        internal unsafe struct Vftbl
        {
            public static IntPtr InitVtbl()
            {
                Vftbl* lpVtbl = (Vftbl*)ComWrappersSupport.AllocateVtableMemory(typeof(Vftbl), sizeof(Vftbl));

                lpVtbl->IUnknownVftbl = IUnknownVftbl.AbiToProjectionVftbl;
                lpVtbl->GetEffectId = &GetEffectIdFromAbi;
                lpVtbl->GetNamedPropertyMapping = &GetNamedPropertyMappingFromAbi;
                lpVtbl->GetPropertyCount = &GetPropertyCountFromAbi;
                lpVtbl->GetProperty = &GetPropertyFromAbi;
                lpVtbl->GetSource = &GetSourceFromAbi;
                lpVtbl->GetSourceCount = &GetSourceCountFromAbi;
                return (IntPtr)lpVtbl;
            }

            private IUnknownVftbl IUnknownVftbl;

            // interface delegates
            private delegate* unmanaged[MemberFunction]<IntPtr, Guid*, int> GetEffectId;

            private delegate* unmanaged[MemberFunction]<IntPtr, IntPtr, uint*, GRAPHICS_EFFECT_PROPERTY_MAPPING*, int> GetNamedPropertyMapping;
            private delegate* unmanaged[MemberFunction]<IntPtr, uint*, int> GetPropertyCount;
            private delegate* unmanaged[MemberFunction]<IntPtr, uint, IntPtr*, int> GetProperty;
            private delegate* unmanaged[MemberFunction]<IntPtr, uint, IntPtr*, int> GetSource;
            private delegate* unmanaged[MemberFunction]<IntPtr, uint*, int> GetSourceCount;

            // interface implementation
            [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
            private static int GetEffectIdFromAbi(IntPtr thisPtr, Guid* value)
            {
                try
                {
                    if (value != null)
                    {
                        *value = Guid.Empty;
                    }

                    int hr = ComWrappersSupport.FindObject<IGraphicsEffectD2D1Interop>(thisPtr).GetEffectId(out Guid v);
                    if (hr >= 0)
                    {
                        if (value != null)
                        {
                            *value = v;
                        }
                    }
                    return hr;
                }
                catch (Exception e)
                {
                    ExceptionHelpers.SetErrorInfo(e);
                    return Marshal.GetHRForException(e);
                }
            }

            [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
            private static int GetNamedPropertyMappingFromAbi(IntPtr thisPtr, IntPtr name, uint* index, GRAPHICS_EFFECT_PROPERTY_MAPPING* mapping)
            {
                try
                {
                    if (index != null)
                    {
                        *index = 0;
                    }

                    if (mapping != null)
                    {
                        *mapping = 0;
                    }

                    int hr = ComWrappersSupport.FindObject<IGraphicsEffectD2D1Interop>(thisPtr).GetNamedPropertyMapping(name, out uint i, out GRAPHICS_EFFECT_PROPERTY_MAPPING m);
                    if (hr >= 0)
                    {
                        if (index != null)
                        {
                            *index = i;
                        }

                        if (mapping != null)
                        {
                            *mapping = m;
                        }
                    }
                    return hr;
                }
                catch (Exception e)
                {
                    ExceptionHelpers.SetErrorInfo(e);
                    return Marshal.GetHRForException(e);
                }
            }

            [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
            private static int GetPropertyCountFromAbi(IntPtr thisPtr, uint* value)
            {
                try
                {
                    if (value != null)
                    {
                        *value = 0;
                    }

                    int hr = ComWrappersSupport.FindObject<IGraphicsEffectD2D1Interop>(thisPtr).GetPropertyCount(out uint v);
                    if (hr >= 0)
                    {
                        *value = v;
                    }
                    return hr;
                }
                catch (Exception e)
                {
                    ExceptionHelpers.SetErrorInfo(e);
                    return Marshal.GetHRForException(e);
                }
            }

            [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
            private static int GetPropertyFromAbi(IntPtr thisPtr, uint index, IntPtr* value)
            {
                try
                {
                    if (value != null)
                    {
                        *value = 0;
                    }

                    int hr = ComWrappersSupport.FindObject<IGraphicsEffectD2D1Interop>(thisPtr).GetProperty(index, out IntPtr v);
                    if (hr >= 0)
                    {
                        *value = v;
                    }
                    return hr;
                }
                catch (Exception e)
                {
                    ExceptionHelpers.SetErrorInfo(e);
                    return Marshal.GetHRForException(e);
                }
            }

            [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
            private static int GetSourceFromAbi(IntPtr thisPtr, uint index, IntPtr* value)
            {
                try
                {
                    if (value != null)
                    {
                        *value = 0;
                    }

                    int hr = ComWrappersSupport.FindObject<IGraphicsEffectD2D1Interop>(thisPtr).GetSource(index, out IGraphicsEffectSource v);
                    if (hr >= 0)
                    {
                        IntPtr unk = MarshalInspectable<IGraphicsEffectSource>.FromManaged(v!);
                        *value = unk;
                    }
                    return hr;
                }
                catch (Exception e)
                {
                    ExceptionHelpers.SetErrorInfo(e);
                    return Marshal.GetHRForException(e);
                }
            }

            [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
            private static int GetSourceCountFromAbi(IntPtr thisPtr, uint* value)
            {
                try
                {
                    if (value != null)
                    {
                        *value = 0;
                    }

                    int hr = ComWrappersSupport.FindObject<IGraphicsEffectD2D1Interop>(thisPtr).GetSourceCount(out uint v);
                    if (hr >= 0)
                    {
                        *value = v;
                    }
                    return hr;
                }
                catch (Exception e)
                {
                    ExceptionHelpers.SetErrorInfo(e);
                    return Marshal.GetHRForException(e);
                }
            }
        }
    }

Open Questions

https://github.com/microsoft/CsWinRT/issues/1722


Other similar interfaces

------------------------------------

其他相似的接口

IGeometrySource2DInterop
dongle-the-gadget commented 2 weeks ago

I believe a cleaner method is to use the generated entries that [GeneratedComClass] generates and use it to supplant our existing DefaultComWrappers.ComputeVtables method.

Gaoyifei1011 commented 2 weeks ago

I believe a cleaner method is to use the generated entries that [GeneratedComClass] generates and use it to supplant our existing DefaultComWrappers.ComputeVtables method.

I am also looking forward to it. Is there any plan for this? @mangod9 @Sergio0694