microsoft / CsWinRT

C# language projection for the Windows Runtime
MIT License
541 stars 103 forks source link

How to definition event with ComWrappers? #1789

Open Gaoyifei1011 opened 2 weeks ago

Gaoyifei1011 commented 2 weeks ago

Description

How to definition event with ComWrappers? 如何使用 ComWrappers 定义事件?

As you can see in the image, I have a COM interface for IInternalCoreWindow2 (the interface is derived from the IInspectable interface) that contains some events. How do I define these events in ComWrappers? How do I call it after the definition is complete? 如图所示,我有一个 IInternalCoreWindow2 的 COM 接口(接口派生于 IInspectable 接口),其中包含了一些事件。如何在 ComWrappers 中定义这些事件?在定义完成后,如何调用呢?

https://github.com/dillydylann/WindowsXamlHosting/blob/a4c8f95759c866d2b724394b0d1bbf866b94d087/CoreWindowAPI/CoreWindow.Interfaces.cs#L23 {98AB77B2-1CBB-4D85-B819-C800DA21E938}

In the .NET Framework we can define it like this (using COM Interop) 在 .NET Framework 中我们可以这样定义(使用了 COM Interop) {A6E30DB2-ECAE-46E0-ABBC-41C5420C2DBB}

Reproduction Steps

None

Expected behavior

Provide an example of defining and calling the interface 提供一个定义并调用该接口的例子

Actual behavior

There are only examples of cpp native and .NET Framework COM Interop 只有 cpp native 和 .NET Framework COM Interop 的示例

Regression?

None

Known Workarounds

None

Configuration

.NET 9 With UWP 10.0.26100.0 x64 None

Other information

Test UWP App App1.zip

https://github.com/dotnet/runtime/issues/107779

dongle-the-gadget commented 2 weeks ago

ComWrappers doesn't support that scenario (actually, nor does [ComInterop] in .NET 5 and above), as it lacks WinRT knowledge, so you'll need to make a WinMD for that.

Sergio0694 commented 2 weeks ago

This is correct. You need to make a WinMD with that type definition and then create projections for it. Technically speaking I suppose you could do all of this with just C# tooling: create a C# authoring project with that type definition and then create another project to project the resulting WinMD produced by the first project. Otherwise you can use IDL and C++ tooling.

Gaoyifei1011 commented 2 weeks ago

ComWrappers doesn't support that scenario (actually, nor does [ComInterop] in .NET 5 and above), as it lacks WinRT knowledge, so you'll need to make a WinMD for that.

Personally, I don't think winmd and Projection.dll should be used to accomplish this if there is a way to define events in ComWrappers. Yesterday I had an in-depth conversation with https://github.com/driver1998 and https://github.com/cnbluefire Z, and the event can be managed by manually defining the add and remove methods, and using the EventRegistrationToken to manage the event. 就我个人而言,我认为如果有可能存在可以在 ComWrappers 中定义事件的方式,就不要使用 winmd 和 Projection.dll 来完成这一操作。昨天我和 https://github.com/driver1998https://github.com/cnbluefire Z进行了深度交流,事件可以通过手动定义 add 和 remove 方法,并使用 EventRegistrationToken 管理事件。

Gaoyifei1011 commented 2 weeks ago

This is correct. You need to make a WinMD with that type definition and then create projections for it. Technically speaking I suppose you could do all of this with just C# tooling: create a C# authoring project with that type definition and then create another project to project the resulting WinMD produced by the first project. Otherwise you can use IDL and C++ tooling.

Personally, I don't think winmd and Projection.dll should be used to accomplish this if there is a way to define events in ComWrappers. Yesterday I had an in-depth conversation with https://github.com/driver1998 and https://github.com/cnbluefire Z, and the event can be managed by manually defining the add and remove methods, and using the EventRegistrationToken to manage the event. 就我个人而言,我认为如果有可能存在可以在 ComWrappers 中定义事件的方式,就不要使用 winmd 和 Projection.dll 来完成这一操作。昨天我和 https://github.com/driver1998https://github.com/cnbluefire Z进行了深度交流,事件可以通过手动定义 add 和 remove 方法,并使用 EventRegistrationToken 管理事件。

Gaoyifei1011 commented 2 weeks ago

Here's sample code to define an event 这是定义事件的示例代码

C++ way c++ 方式

    [uuid("c12779d8-85d2-43e5-901a-95dd4f8ecba3")]
        private interface class IInternalCoreWindow2
    {
        property Rect LayoutBounds { Rect get(); }
        property Rect VisibleBounds { Rect get(); }
        property ApplicationViewBoundsMode DesiredBoundsMode { ApplicationViewBoundsMode get(); }
        bool SetDesiredBoundsMode(ApplicationViewBoundsMode mode);
        void OnVisibleBoundsChange();
        event TypedEventHandler<IInternalCoreWindow2^, Platform::Object^>^ LayoutBoundsChanged;
        event TypedEventHandler<IInternalCoreWindow2^, Platform::Object^>^ VisibleBoundsChanged;
        event TypedEventHandler<IInternalCoreWindow2^, KeyEventArgs^>^ SysKeyDown;
        event TypedEventHandler<IInternalCoreWindow2^, KeyEventArgs^>^ SysKeyUp;
        event TypedEventHandler<IInternalCoreWindow2^, Platform::Object^>^ WindowPositionChanged;
        event TypedEventHandler<IInternalCoreWindow2^, Platform::Object^>^ SettingChanged;
        event TypedEventHandler<IInternalCoreWindow2^, Platform::Object^>^ ViewStateChanged;
        event TypedEventHandler<IInternalCoreWindow2^, CoreWindowEventArgs^>^ Destroying;
    };

Convert to C# 转换成 c# 方式

    [GeneratedComInterface, Guid("C12779D8-85D2-43E5-901A-95DD4F8ECBA3")]
    public partial interface IInternalCoreWindow2
    {
        int GetIids(out ulong iidCount, out IntPtr iids);

        int GetRuntimeClassName(out IntPtr className);

        int GetTrustLevel(out TrustLevel trustLevel);

        int GetLayoutBounds(out Rect rect);

        int GetVisibleBounds(out Rect rect);

        int GetDesiredBoundsMode(out ApplicationViewBoundsMode applicationViewBoundsMode);

        [return: MarshalAs(UnmanagedType.Bool)]
        bool SetDesiredBoundsMode(ApplicationViewBoundsMode desiredBoundsMode);

        int OnVisibleBoundsChange();

        int add_LayoutBoundsChanged(IntPtr handler, out EventRegistrationToken token);

        int remove_LayoutBoundsChanged(EventRegistrationToken token);

        int add_VisibleBoundsChanged(IntPtr handler, out EventRegistrationToken token);

        int remove_VisibleBoundsChanged(EventRegistrationToken token);

        int add_SysKeyDown(IntPtr handler, out EventRegistrationToken token);

        int remove_SysKeyDown(EventRegistrationToken token);

        int add_SysKeyUp(IntPtr handler, out EventRegistrationToken token);

        int remove_SysKeyUp(EventRegistrationToken token);

        int add_WindowPositionChanged(IntPtr handler, out EventRegistrationToken token);

        int remove_WindowPositionChanged(EventRegistrationToken token);

        int add_SettingsChanged(IntPtr handler, out EventRegistrationToken token);

        int remove_SettingsChanged(EventRegistrationToken token);

        int add_ViewStateChanged(IntPtr handler, out EventRegistrationToken token);

        int remove_ViewStateChanged(EventRegistrationToken token);

        int add_Destroying(IntPtr handler, out EventRegistrationToken token);

        int remove_Destroying(EventRegistrationToken token);
    }

invoke 调用

                EventHandler eventHandler = new(OnWindowPositionChanged);
                IObjectReference objectReference = MarshalInterface<EventHandler>.CreateMarshaler(eventHandler);
                internalCoreWindow2.add_WindowPositionChanged(objectReference.ThisPtr, out EventRegistrationToken token);
                EventRegistrationToken = token;

ScreenShots 屏幕截图 {C1B56FBB-C55E-436C-ABA1-08BFB77AB80F}

As you can see, I use an EventHandler without generics (corresponding to object sender and EventArgs args) and the event fires normally. Since the sender type is CoreWindow and args is object, it doesn't have much impact. 如图所示,我使用不带有泛型的 EventHandler(对应 object sender 和 EventArgs args),事件正常触发。因为这个事件对应 sender 类型为 CoreWindow,args 为 object,故没有太大影响。

However when I use TypedEventHandler with generics, an exception occurs, so I would like to ask you, is there any workaround? 然而当我使用带有泛型的 TypedEventHandler 时,却发生了异常,所以想问您一下,有什么解决方法吗?

{6DD34C96-E1FD-4A61-B8F4-FD64F63ECCC2}

Test Samples 测试示例 App1.zip

dongle-the-gadget commented 2 weeks ago

Yes, because there is no WinRT marshaller for ComWrappers. I'm not too sure where the team stands with this, but I personally would prefer the WinRT way. What you're effectively doing is redefining the ABI, which can be quite fragile.

dongle-the-gadget commented 2 weeks ago

@Sergio0694 it looks like we can't use CsWinRT to generate the WinMD currently, as the authoring generator currently ignores the GuidAttribute, always attaching a generated Guid from the name hashed.

Gaoyifei1011 commented 2 weeks ago

.NET 6 will cease to be supported in November 2024, at which point only.NET 8 and.NET 9 will be supported. The.NET team has provided ComWrappers source generation support since.NET 8, so I'm curious to know if the CsWinRT team will make CsWinRT more compatible with ComWrappers source generation support after.NET 6 stops supporting it? For example, using StrategyBasedComWrappers provided by.NET 8+, modify and delete their own implementation in StrategyBasedComWrappers, define the interface using GeneratedComInterface, GeneratedComClass Implements the corresponding class of the interface. In.NET Core 3.1 or the.NET Framework, the interface generated by WinRT is a built-in Com type, which is then compatible with the built-in COM provided by.NET. With the release of.NET 5+ and ComWrappers in both CsWinRT and.NET teams, this very strong compatibility has been broken.

.NET 6 将于 2024 年 11 月停止支持,这时受支持的 .NET 版本仅有 .NET 8 和 .NET 9。从 .NET 8 开始 .NET 团队提供了 ComWrappers 源生成的支持,所以我很想知道 CsWinRT 团队是否会在 .NET 6 停止支持后,CsWinRT 会做与 ComWrappers 源生成支持更多的兼容?比如使用 .NET 8+ 提供的 StrategyBasedComWrappers,在 StrategyBasedComWrappers 做出修改并删除自己的实现,使用 GeneratedComInterface 定义接口,GeneratedComClass 实现接口对应的类。在 .NET Core 3.1 或 .NET Framework 中,WinRT 生成的接口是内置的 Com 类型,那时与 .NET 提供的内置的 COM 保持兼容。而随着 .NET 5+ 的发布,以及 CsWinRT 和 .NET 团队两套方式的 ComWrappers 实现,这种非常强的兼容被打破了。

Sergio0694 commented 2 weeks ago

"it looks like we can't use CsWinRT to generate the WinMD currently, as the authoring generator currently ignores the GuidAttribute, always attaching a generated Guid from the name hashed."

@dongle-the-gadget I definitely want to fix that. I want to make it possible to just use C# to create WinMD-s. Can you please open an issue for that if there isn't one already? That would be another step towards no longer needing any C++ projects 🙂