C++/WinRT 使って実装したときのハマりポイントとか書いておきます。
- 1. 現代の Windows Runtime の実装とは
- 2. C++/WinRT や C#/WinRT の特徴
- 3. Windows Runtime Component を C++/WinRT で作成する場合のハマりポイント
- 4. 最後に
1. 現代の Windows Runtime の実装とは
C++、C# に関しては「投影型」(projection) と「実装型」 (implementation) が存在します(Python や Rust の WinRT に関しては追っかけていないので、言語を限定しておきます)。とりあえずこれだけアタマに入れておいてください。
1.1 API 定義のプロセス
【C++/WinRTの場合】
- Windows Metadata (winmd) または MIDL 3.0 によって、ABI*1 を定義
- CppWinRT によって、ABI の生成 (0)、インターフェースの生成 (1)、プロジェクションの生成 (2)
- 利用 (両方) / 実装 (MIDL からのみ)
【C#/WinRTの利用の場合】
- Windows Metadata (winmd) から ABI の読み取り、CsWinRT によってプロジェクションの生成 (ただし、プロジェクション DLL は一般に NuGet package に同梱されているため、このプロセスは省略する)
- 利用
このように、MIDL または Windows Metadata によって定義された ABI からソースコードを生成するのが、現代の Windows Runtime の最大の特徴です。
1.2 自動生成されたソースコードのリーディング(C++/WinRT 編)
利用するソースコードは、xaml のコントロール SettingsCard
でお話していきたいと思います。ただし、CppWinRT では、名前空間ごとにプロジェクションコードが生成されるのですが、今回はこのクラスだけを抽出してお話していきます。
また、実装環境では、プロジェクションの生成コード以外に、実装のための生成コードも存在しますが、今回の記事ではこちらは取り扱いません。
namespace Mntone.AngelUmbrella.Controls { [constructor_name("ISettingsCardFactory", CEA37F79-B848-562C-BC32-4046419F28A7)] [interface_name("ISettingsCard", A98BF3E6-1595-4879-A198-93B5F6242233)] [static_name("ISettingsCardStatics", 34C8763E-7086-5B2F-9117-4A2C78455171)] [webhosthidden] unsealed runtimeclass SettingsCard : Microsoft.UI.Xaml.Controls.Button { SettingsCard(); Object Header; static Microsoft.UI.Xaml.DependencyProperty HeaderProperty { get; }; } }
※ すべてのプロパティーのソースコードを掲載すると、長くなるため、Header
プロパティーに限って抽出します。
【winrt/impl/Mntone.AngelUmbrella.Converters.0.h
ABI と consume の定義】
// WARNING: Please don't edit this file. It was generated by C++/WinRT v2.0.221121.5 #pragma once #ifndef WINRT_Mntone_AngelUmbrella_Controls_0_H #define WINRT_Mntone_AngelUmbrella_Controls_0_H WINRT_EXPORT namespace winrt::Microsoft::UI::Xaml { struct DependencyProperty; } WINRT_EXPORT namespace winrt::Mntone::AngelUmbrella::Controls { struct ISettingsCard; struct ISettingsCardFactory; struct ISettingsCardStatics; struct SettingsCard; } namespace winrt::impl { template <> struct category<winrt::Mntone::AngelUmbrella::Controls::ISettingsCard>{ using type = interface_category; }; template <> struct category<winrt::Mntone::AngelUmbrella::Controls::ISettingsCardFactory>{ using type = interface_category; }; template <> struct category<winrt::Mntone::AngelUmbrella::Controls::ISettingsCardStatics>{ using type = interface_category; }; template <> struct category<winrt::Mntone::AngelUmbrella::Controls::SettingsCard>{ using type = class_category; }; template <> inline constexpr auto& name_v<winrt::Mntone::AngelUmbrella::Controls::SettingsCard> = L"Mntone.AngelUmbrella.Controls.SettingsCard"; template <> inline constexpr auto& name_v<winrt::Mntone::AngelUmbrella::Controls::ISettingsCard> = L"Mntone.AngelUmbrella.Controls.ISettingsCard"; template <> inline constexpr auto& name_v<winrt::Mntone::AngelUmbrella::Controls::ISettingsCardFactory> = L"Mntone.AngelUmbrella.Controls.ISettingsCardFactory"; template <> inline constexpr auto& name_v<winrt::Mntone::AngelUmbrella::Controls::ISettingsCardStatics> = L"Mntone.AngelUmbrella.Controls.ISettingsCardStatics"; template <> inline constexpr guid guid_v<winrt::Mntone::AngelUmbrella::Controls::ISettingsCard>{ 0xA98BF3E6,0x1595,0x4879,{ 0xA1,0x98,0x93,0xB5,0xF6,0x24,0x22,0x33 } }; // A98BF3E6-1595-4879-A198-93B5F6242233 template <> inline constexpr guid guid_v<winrt::Mntone::AngelUmbrella::Controls::ISettingsCardFactory>{ 0xCEA37F79,0xB848,0x562C,{ 0xBC,0x32,0x40,0x46,0x41,0x9F,0x28,0xA7 } }; // CEA37F79-B848-562C-BC32-4046419F28A7 template <> inline constexpr guid guid_v<winrt::Mntone::AngelUmbrella::Controls::ISettingsCardStatics>{ 0x34C8763E,0x7086,0x5B2F,{ 0x91,0x17,0x4A,0x2C,0x78,0x45,0x51,0x71 } }; // 34C8763E-7086-5B2F-9117-4A2C78455171 template <> struct default_interface<winrt::Mntone::AngelUmbrella::Controls::SettingsCard>{ using type = winrt::Mntone::AngelUmbrella::Controls::ISettingsCard; };; template <> struct abi<winrt::Mntone::AngelUmbrella::Controls::ISettingsCard> { struct WINRT_IMPL_NOVTABLE type : inspectable_abi { virtual int32_t __stdcall get_Header(void**) noexcept = 0; virtual int32_t __stdcall put_Header(void*) noexcept = 0; }; }; template <> struct abi<winrt::Mntone::AngelUmbrella::Controls::ISettingsCardFactory> { struct WINRT_IMPL_NOVTABLE type : inspectable_abi { virtual int32_t __stdcall CreateInstance(void*, void**, void**) noexcept = 0; }; }; template <> struct abi<winrt::Mntone::AngelUmbrella::Controls::ISettingsCardStatics> { struct WINRT_IMPL_NOVTABLE type : inspectable_abi { virtual int32_t __stdcall get_HeaderProperty(void**) noexcept = 0; }; }; template <typename D> struct consume_Mntone_AngelUmbrella_Controls_ISettingsCard { [[nodiscard]] auto Header() const; auto Header(winrt::Windows::Foundation::IInspectable const& value) const; }; template <> struct consume<winrt::Mntone::AngelUmbrella::Controls::ISettingsCard> { template <typename D> using type = consume_Mntone_AngelUmbrella_Controls_ISettingsCard<D>; }; template <typename D> struct consume_Mntone_AngelUmbrella_Controls_ISettingsCardFactory { auto CreateInstance(winrt::Windows::Foundation::IInspectable const& baseInterface, winrt::Windows::Foundation::IInspectable& innerInterface) const; }; template <> struct consume<winrt::Mntone::AngelUmbrella::Controls::ISettingsCardFactory> { template <typename D> using type = consume_Mntone_AngelUmbrella_Controls_ISettingsCardFactory<D>; }; template <typename D> struct consume_Mntone_AngelUmbrella_Controls_ISettingsCardStatics { [[nodiscard]] auto HeaderProperty() const; }; template <> struct consume<winrt::Mntone::AngelUmbrella::Controls::ISettingsCardStatics> { template <typename D> using type = consume_Mntone_AngelUmbrella_Controls_ISettingsCardStatics<D>; }; } #endif
【winrt/impl/Mntone.AngelUmbrella.Converters.1.h
interface の定義】
// WARNING: Please don't edit this file. It was generated by C++/WinRT v2.0.221121.5 #pragma once #ifndef WINRT_Mntone_AngelUmbrella_Controls_1_H #define WINRT_Mntone_AngelUmbrella_Controls_1_H #include "winrt/impl/Mntone.AngelUmbrella.Controls.0.h" WINRT_EXPORT namespace winrt::Mntone::AngelUmbrella::Controls { struct WINRT_IMPL_EMPTY_BASES ISettingsCard : winrt::Windows::Foundation::IInspectable, impl::consume_t<ISettingsCard> { ISettingsCard(std::nullptr_t = nullptr) noexcept {} ISettingsCard(void* ptr, take_ownership_from_abi_t) noexcept : winrt::Windows::Foundation::IInspectable(ptr, take_ownership_from_abi) {} }; struct WINRT_IMPL_EMPTY_BASES ISettingsCardFactory : winrt::Windows::Foundation::IInspectable, impl::consume_t<ISettingsCardFactory> { ISettingsCardFactory(std::nullptr_t = nullptr) noexcept {} ISettingsCardFactory(void* ptr, take_ownership_from_abi_t) noexcept : winrt::Windows::Foundation::IInspectable(ptr, take_ownership_from_abi) {} }; struct WINRT_IMPL_EMPTY_BASES ISettingsCardStatics : winrt::Windows::Foundation::IInspectable, impl::consume_t<ISettingsCardStatics> { ISettingsCardStatics(std::nullptr_t = nullptr) noexcept {} ISettingsCardStatics(void* ptr, take_ownership_from_abi_t) noexcept : winrt::Windows::Foundation::IInspectable(ptr, take_ownership_from_abi) {} }; } #endif
【winrt/impl/Mntone.AngelUmbrella.Converters.2.h
projection class の定義】
// WARNING: Please don't edit this file. It was generated by C++/WinRT v2.0.221121.5 #pragma once #ifndef WINRT_Mntone_AngelUmbrella_Controls_2_H #define WINRT_Mntone_AngelUmbrella_Controls_2_H #include "winrt/impl/Microsoft.UI.Composition.1.h" #include "winrt/impl/Microsoft.UI.Xaml.1.h" #include "winrt/impl/Microsoft.UI.Xaml.Controls.1.h" #include "winrt/impl/Microsoft.UI.Xaml.Controls.Primitives.1.h" #include "winrt/impl/Mntone.AngelUmbrella.Controls.1.h" WINRT_EXPORT namespace winrt::Mntone::AngelUmbrella::Controls { struct WINRT_IMPL_EMPTY_BASES SettingsCard : winrt::Mntone::AngelUmbrella::Controls::ISettingsCard, impl::base<SettingsCard, winrt::Microsoft::UI::Xaml::Controls::Button, winrt::Microsoft::UI::Xaml::Controls::Primitives::ButtonBase, winrt::Microsoft::UI::Xaml::Controls::ContentControl, winrt::Microsoft::UI::Xaml::Controls::Control, winrt::Microsoft::UI::Xaml::FrameworkElement, winrt::Microsoft::UI::Xaml::UIElement, winrt::Microsoft::UI::Xaml::DependencyObject>, impl::require<SettingsCard, winrt::Microsoft::UI::Xaml::Controls::IButton, winrt::Microsoft::UI::Xaml::Controls::Primitives::IButtonBase, winrt::Microsoft::UI::Xaml::Controls::IContentControl, winrt::Microsoft::UI::Xaml::Controls::IContentControlOverrides, winrt::Microsoft::UI::Xaml::Controls::IControl, winrt::Microsoft::UI::Xaml::Controls::IControlProtected, winrt::Microsoft::UI::Xaml::Controls::IControlOverrides, winrt::Microsoft::UI::Xaml::IFrameworkElement, winrt::Microsoft::UI::Xaml::IFrameworkElementProtected, winrt::Microsoft::UI::Xaml::IFrameworkElementOverrides, winrt::Microsoft::UI::Xaml::IUIElement, winrt::Microsoft::UI::Xaml::IUIElementProtected, winrt::Microsoft::UI::Xaml::IUIElementOverrides, winrt::Microsoft::UI::Composition::IAnimationObject, winrt::Microsoft::UI::Composition::IVisualElement, winrt::Microsoft::UI::Composition::IVisualElement2, winrt::Microsoft::UI::Xaml::IDependencyObject> { SettingsCard(std::nullptr_t) noexcept {} SettingsCard(void* ptr, take_ownership_from_abi_t) noexcept : winrt::Mntone::AngelUmbrella::Controls::ISettingsCard(ptr, take_ownership_from_abi) {} SettingsCard(); [[nodiscard]] static winrt::Microsoft::UI::Xaml::DependencyProperty HeaderProperty(); }; } #endif
1.3 自動生成されたソースコードのリーディング(C#/WinRT 編)
【Mntone.AngelUmbrella.Controls.cs
上段: projection】
//------------------------------------------------------------------------------ // <auto-generated> // This file was generated by cswinrt.exe version 2.0.1.221115.1 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using WinRT; using WinRT.Interop; #pragma warning disable 0169 // warning CS0169: The field '...' is never used #pragma warning disable 0649 // warning CS0169: Field '...' is never assigned to #pragma warning disable CA2207, CA1063, CA1033, CA1001, CA2213 namespace Mntone.AngelUmbrella.Controls { [global::WinRT.WindowsRuntimeType("Mntone.AngelUmbrella")][Guid("A98BF3E6-1595-4879-A198-93B5F6242233")][global::WinRT.WindowsRuntimeHelperType(typeof(global::ABI.Mntone.AngelUmbrella.Controls.ISettingsCard))] internal interface ISettingsCard { object Header { get; set; } } [global::WinRT.WindowsRuntimeType("Mntone.AngelUmbrella")][Guid("CEA37F79-B848-562C-BC32-4046419F28A7")][global::WinRT.WindowsRuntimeHelperType(typeof(global::ABI.Mntone.AngelUmbrella.Controls.ISettingsCardFactory))] internal interface ISettingsCardFactory { SettingsCard CreateInstance(object baseInterface, out object innerInterface); } [global::WinRT.WindowsRuntimeType("Mntone.AngelUmbrella")][Guid("34C8763E-7086-5B2F-9117-4A2C78455171")][global::WinRT.WindowsRuntimeHelperType(typeof(global::ABI.Mntone.AngelUmbrella.Controls.ISettingsCardStatics))] internal interface ISettingsCardStatics { global::Microsoft.UI.Xaml.DependencyProperty HeaderProperty { get; } } [global::WinRT.WindowsRuntimeType("Mntone.AngelUmbrella")][global::WinRT.WindowsRuntimeHelperType(typeof(global::ABI.Mntone.AngelUmbrella.Controls.SettingsCard))] [global::WinRT.ProjectedRuntimeClass(typeof(ISettingsCard))] [global::WinRT.ObjectReferenceWrapper(nameof(_inner))] public class SettingsCard : global::Microsoft.UI.Xaml.Controls.Button, global::System.Runtime.InteropServices.ICustomQueryInterface, IWinRTObject, IEquatable<SettingsCard> { private IntPtr ThisPtr => _inner == null ? (((IWinRTObject)this).NativeObject).ThisPtr : _inner.ThisPtr; private IObjectReference _inner = null; private volatile IObjectReference ___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCard; private IObjectReference Make___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCard() { global::System.Threading.Interlocked.CompareExchange(ref ___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCard, ((IWinRTObject)this).NativeObject.As<IUnknownVftbl>(GuidGenerator.GetIID(typeof(ISettingsCard).GetHelperType())), null); return ___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCard; } private IObjectReference _objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCard => ___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCard ?? Make___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCard(); private ISettingsCard _default => null; internal sealed class _ISettingsCardFactory : IWinRTObject { private IObjectReference _obj; private IntPtr ThisPtr => _obj.ThisPtr; public _ISettingsCardFactory() { _obj = ActivationFactory<SettingsCard>.As(GuidGenerator.GetIID(typeof(Mntone.AngelUmbrella.Controls.ISettingsCardFactory).GetHelperType())); } private static _ISettingsCardFactory _instance = new _ISettingsCardFactory(); internal static _ISettingsCardFactory Instance => _instance; IObjectReference IWinRTObject.NativeObject => _obj; bool IWinRTObject.HasUnwrappableNativeObject => false; private volatile global::System.Collections.Concurrent.ConcurrentDictionary<RuntimeTypeHandle, IObjectReference> _queryInterfaceCache; private global::System.Collections.Concurrent.ConcurrentDictionary<RuntimeTypeHandle, IObjectReference> MakeQueryInterfaceCache() { global::System.Threading.Interlocked.CompareExchange(ref _queryInterfaceCache, new global::System.Collections.Concurrent.ConcurrentDictionary<RuntimeTypeHandle, IObjectReference>(), null); return _queryInterfaceCache; } global::System.Collections.Concurrent.ConcurrentDictionary<RuntimeTypeHandle, IObjectReference> IWinRTObject.QueryInterfaceCache => _queryInterfaceCache ?? MakeQueryInterfaceCache(); private volatile global::System.Collections.Concurrent.ConcurrentDictionary<RuntimeTypeHandle, object> _additionalTypeData; private global::System.Collections.Concurrent.ConcurrentDictionary<RuntimeTypeHandle, object> MakeAdditionalTypeData() { global::System.Threading.Interlocked.CompareExchange(ref _additionalTypeData, new global::System.Collections.Concurrent.ConcurrentDictionary<RuntimeTypeHandle, object>(), null); return _additionalTypeData; } global::System.Collections.Concurrent.ConcurrentDictionary<RuntimeTypeHandle, object> IWinRTObject.AdditionalTypeData => _additionalTypeData ?? MakeAdditionalTypeData(); public unsafe IntPtr CreateInstance(object baseInterface, out IntPtr innerInterface) { ObjectReferenceValue __baseInterface = default; IntPtr __innerInterface = default; IntPtr __retval = default; try { __baseInterface = MarshalInspectable<object>.CreateMarshaler2(baseInterface); global::WinRT.ExceptionHelpers.ThrowExceptionForHR((*(delegate* unmanaged[Stdcall]<IntPtr, IntPtr, out IntPtr, out IntPtr, int>**)ThisPtr)[6](ThisPtr, MarshalInspectable<object>.GetAbi(__baseInterface), out __innerInterface, out __retval)); innerInterface = __innerInterface; return __retval; } finally { MarshalInspectable<object>.DisposeMarshaler(__baseInterface); } } } public SettingsCard():base(global::WinRT.DerivedComposed.Instance) { bool isAggregation = this.GetType() != typeof(SettingsCard); object baseInspectable = isAggregation ? this : null; IntPtr composed = _ISettingsCardFactory.Instance.CreateInstance( baseInspectable, out IntPtr inner); try { ComWrappersHelper.Init(isAggregation, this, composed, inner, out _inner); } finally { Marshal.Release(inner); } } public static new I As<I>() => ActivationFactory<SettingsCard>.AsInterface<I>(); private static volatile IObjectReference ___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCardStatics; private static IObjectReference Make___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCardStatics() { global::System.Threading.Interlocked.CompareExchange(ref ___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCardStatics, ActivationFactory<SettingsCard>.As(GuidGenerator.GetIID(typeof(ISettingsCardStatics).GetHelperType())), null); return ___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCardStatics; } private static IObjectReference _objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCardStatics => ___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCardStatics ?? Make___objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCardStatics(); public static global::Microsoft.UI.Xaml.DependencyProperty HeaderProperty => global::ABI.Mntone.AngelUmbrella.Controls.ISettingsCardStaticsMethods.get_HeaderProperty(_objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCardStatics); public static new SettingsCard FromAbi(IntPtr thisPtr) { if (thisPtr == IntPtr.Zero) return null; return MarshalInspectable<SettingsCard>.FromAbi(thisPtr); } protected internal SettingsCard(IObjectReference objRef) : base(global::WinRT.DerivedComposed.Instance) { _inner = objRef.As(GuidGenerator.GetIID(typeof(ISettingsCard).GetHelperType())); } public static bool operator ==(SettingsCard x, SettingsCard y) => (x?.ThisPtr ?? IntPtr.Zero) == (y?.ThisPtr ?? IntPtr.Zero); public static bool operator !=(SettingsCard x, SettingsCard y) => !(x == y); public bool Equals(SettingsCard other) => this == other; public override bool Equals(object obj) => obj is SettingsCard that && this == that; public override int GetHashCode() => ThisPtr.GetHashCode(); protected SettingsCard(global::WinRT.DerivedComposed _):base(_) { } bool IWinRTObject.HasUnwrappableNativeObject => this.GetType() == typeof(SettingsCard); IObjectReference IWinRTObject.NativeObject => _inner; private struct InterfaceTag<I>{}; public object Header { get => global::ABI.Mntone.AngelUmbrella.Controls.ISettingsCardMethods.get_Header(_objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCard); set => global::ABI.Mntone.AngelUmbrella.Controls.ISettingsCardMethods.set_Header(_objRef_global__Mntone_AngelUmbrella_Controls_ISettingsCard, value); } protected override bool IsOverridableInterface(Guid iid) => base.IsOverridableInterface(iid); global::System.Runtime.InteropServices.CustomQueryInterfaceResult global::System.Runtime.InteropServices.ICustomQueryInterface.GetInterface(ref Guid iid, out IntPtr ppv) { ppv = IntPtr.Zero; if (IsOverridableInterface(iid) || global::WinRT.InterfaceIIDs.IInspectable_IID == iid) { return global::System.Runtime.InteropServices.CustomQueryInterfaceResult.NotHandled; } if (((IWinRTObject)this).NativeObject.TryAs(iid, out ppv) >= 0) { return global::System.Runtime.InteropServices.CustomQueryInterfaceResult.Handled; } return global::System.Runtime.InteropServices.CustomQueryInterfaceResult.NotHandled; } } }
【Mntone.AngelUmbrella.Controls.cs
下段: ABI の定義、HRESULT の C# 例外への変換】
#pragma warning disable CA1416 namespace ABI.Mntone.AngelUmbrella.Controls { internal static class ISettingsCardMethods { public static unsafe object get_Header(IObjectReference _obj) { var ThisPtr = _obj.ThisPtr; IntPtr __retval = default; try { global::WinRT.ExceptionHelpers.ThrowExceptionForHR((*(delegate* unmanaged[Stdcall]<IntPtr, out IntPtr, int>**)ThisPtr)[14](ThisPtr, out __retval)); return MarshalInspectable<object>.FromAbi(__retval); } finally { MarshalInspectable<object>.DisposeAbi(__retval); } } public static unsafe void set_Header(IObjectReference _obj, object value) { var ThisPtr = _obj.ThisPtr; ObjectReferenceValue __value = default; try { __value = MarshalInspectable<object>.CreateMarshaler2(value); global::WinRT.ExceptionHelpers.ThrowExceptionForHR((*(delegate* unmanaged[Stdcall]<IntPtr, IntPtr, int>**)ThisPtr)[15](ThisPtr, MarshalInspectable<object>.GetAbi(__value))); } finally { MarshalInspectable<object>.DisposeMarshaler(__value); } } } [Guid("A98BF3E6-1595-4879-A198-93B5F6242233")] internal interface ISettingsCard : global::Mntone.AngelUmbrella.Controls.ISettingsCard { } internal static class ISettingsCardFactoryMethods { public static unsafe global::Mntone.AngelUmbrella.Controls.SettingsCard CreateInstance(IObjectReference _obj, object baseInterface, out object innerInterface) { var ThisPtr = _obj.ThisPtr; ObjectReferenceValue __baseInterface = default; IntPtr __innerInterface = default; IntPtr __retval = default; try { __baseInterface = MarshalInspectable<object>.CreateMarshaler2(baseInterface); global::WinRT.ExceptionHelpers.ThrowExceptionForHR((*(delegate* unmanaged[Stdcall]<IntPtr, IntPtr, out IntPtr, out IntPtr, int>**)ThisPtr)[6](ThisPtr, MarshalInspectable<object>.GetAbi(__baseInterface), out __innerInterface, out __retval)); innerInterface = MarshalInspectable<object>.FromAbi(__innerInterface); return global::ABI.Mntone.AngelUmbrella.Controls.SettingsCard.FromAbi(__retval); } finally { MarshalInspectable<object>.DisposeMarshaler(__baseInterface); MarshalInspectable<object>.DisposeAbi(__innerInterface); global::ABI.Mntone.AngelUmbrella.Controls.SettingsCard.DisposeAbi(__retval); } } } [Guid("CEA37F79-B848-562C-BC32-4046419F28A7")] internal interface ISettingsCardFactory : global::Mntone.AngelUmbrella.Controls.ISettingsCardFactory { } internal static class ISettingsCardStaticsMethods { public static unsafe global::Microsoft.UI.Xaml.DependencyProperty get_ActionIconProperty(IObjectReference _obj) { var ThisPtr = _obj.ThisPtr; IntPtr __retval = default; try { global::WinRT.ExceptionHelpers.ThrowExceptionForHR((*(delegate* unmanaged[Stdcall]<IntPtr, out IntPtr, int>**)ThisPtr)[6](ThisPtr, out __retval)); return global::ABI.Microsoft.UI.Xaml.DependencyProperty.FromAbi(__retval); } finally { global::ABI.Microsoft.UI.Xaml.DependencyProperty.DisposeAbi(__retval); } } public static unsafe global::Microsoft.UI.Xaml.DependencyProperty get_HeaderProperty(IObjectReference _obj) { var ThisPtr = _obj.ThisPtr; IntPtr __retval = default; try { global::WinRT.ExceptionHelpers.ThrowExceptionForHR((*(delegate* unmanaged[Stdcall]<IntPtr, out IntPtr, int>**)ThisPtr)[10](ThisPtr, out __retval)); return global::ABI.Microsoft.UI.Xaml.DependencyProperty.FromAbi(__retval); } finally { global::ABI.Microsoft.UI.Xaml.DependencyProperty.DisposeAbi(__retval); } } } [Guid("34C8763E-7086-5B2F-9117-4A2C78455171")] internal interface ISettingsCardStatics : global::Mntone.AngelUmbrella.Controls.ISettingsCardStatics { } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] public struct SettingsCard { public static IObjectReference CreateMarshaler(global::Mntone.AngelUmbrella.Controls.SettingsCard obj) => obj is null ? null : MarshalInspectable<global::Mntone.AngelUmbrella.Controls.SettingsCard>.CreateMarshaler<IUnknownVftbl>(obj, GuidGenerator.GetIID(typeof(global::Mntone.AngelUmbrella.Controls.ISettingsCard).GetHelperType())); public static ObjectReferenceValue CreateMarshaler2(global::Mntone.AngelUmbrella.Controls.SettingsCard obj) => MarshalInspectable<object>.CreateMarshaler2(obj, GuidGenerator.GetIID(typeof(global::Mntone.AngelUmbrella.Controls.ISettingsCard).GetHelperType())); public static IntPtr GetAbi(IObjectReference value) => value is null ? IntPtr.Zero : MarshalInterfaceHelper<object>.GetAbi(value); public static global::Mntone.AngelUmbrella.Controls.SettingsCard FromAbi(IntPtr thisPtr) => global::Mntone.AngelUmbrella.Controls.SettingsCard.FromAbi(thisPtr); public static IntPtr FromManaged(global::Mntone.AngelUmbrella.Controls.SettingsCard obj) => obj is null ? IntPtr.Zero : CreateMarshaler2(obj).Detach(); public static unsafe MarshalInterfaceHelper<global::Mntone.AngelUmbrella.Controls.SettingsCard>.MarshalerArray CreateMarshalerArray(global::Mntone.AngelUmbrella.Controls.SettingsCard[] array) => MarshalInterfaceHelper<global::Mntone.AngelUmbrella.Controls.SettingsCard>.CreateMarshalerArray2(array, (o) => CreateMarshaler2(o)); public static (int length, IntPtr data) GetAbiArray(object box) => MarshalInterfaceHelper<global::Mntone.AngelUmbrella.Controls.SettingsCard>.GetAbiArray(box); public static unsafe global::Mntone.AngelUmbrella.Controls.SettingsCard[] FromAbiArray(object box) => MarshalInterfaceHelper<global::Mntone.AngelUmbrella.Controls.SettingsCard>.FromAbiArray(box, FromAbi); public static (int length, IntPtr data) FromManagedArray(global::Mntone.AngelUmbrella.Controls.SettingsCard[] array) => MarshalInterfaceHelper<global::Mntone.AngelUmbrella.Controls.SettingsCard>.FromManagedArray(array, (o) => FromManaged(o)); public static void DisposeMarshaler(IObjectReference value) => MarshalInspectable<object>.DisposeMarshaler(value); public static void DisposeMarshalerArray(MarshalInterfaceHelper<global::Mntone.AngelUmbrella.Controls.SettingsCard>.MarshalerArray array) => MarshalInterfaceHelper<global::Mntone.AngelUmbrella.Controls.SettingsCard>.DisposeMarshalerArray(array); public static void DisposeAbi(IntPtr abi) => MarshalInspectable<object>.DisposeAbi(abi); public static unsafe void DisposeAbiArray(object box) => MarshalInspectable<object>.DisposeAbiArray(box); } } #pragma warning restore CA1416
2. C++/WinRT や C#/WinRT の特徴
まず、言語例外を活用すること。これによって、COM の HRESULT でソースコードが煩雑になる問題に自然と対処することができます。
ABI を超えるときは、内部で try - catch ...
して、HRESULT に変換し、そして ABI を越えた後、再び各言語の例外機能に変換すること(プロジェクションのお仕事)によって実現されています(上の C# ソースコードを参照)。
C++/WinRT 限定のお話になりますが、すべての関数で C++ 例外が自動的にキャッチされ、HRESULT に変換されるため、それに対する最適化コードを吐くように、コンパイラー側に手が加わっているそうです。
最適化された例外処理 (EH) コード生成
(中略: try - catch ... コードなど)
C++/WinRT 自体では、実装されているすべての API に対してこのパターンが生成されます。 何千もの API 関数において、ここでの最適化が重要である可能性があります。 これまで、オプティマイザーではそれらの catch ブロックがすべて同じであることは検出されなかったため、各 ABI で多くのコードが重複していました (それは、システム コードで例外を使うと大きなバイナリが生成されるという確信の原因になっていました)。 しかし、Visual Studio 2019 以降の C++ コンパイラでは、それらのすべての catch funclet が折りたたまれていて、固有のものだけが格納されます。結果として、このパターンに大きく依存するバイナリのコード サイズは全体的としてさらに 18% 減少します。 EH コードがリターン コードを使うより効率的になっただけでなく、大きいバイナリ ファイルに関する心配は過去のものになりました。
C++/WinRT はあくまで、C++ の上で動く薄いラッパーなので、MSVC チームによって C++ 自体のコード生成が改善される余地を大きく残しており、長年に渡ってコンパイラー改善の恩恵を受けられるため、とても良いものになっています(C++/CLI や C++/CX はこの辺りが残念だった)。
もちろん、この方法はデメリットもあります。「C++/WinRT」「C#/WinRT」に共通する特徴なのですが、「今扱っているクラスが、実装型なのかプロジェクション型なのか」ということを意識するということです。
C#/WinRT では自家製のコードは実装型ですし、逆に外部のコードはプロジェクション型なので、さほど意識することはないでしょう。しかし、C++/WinRT では実装型とプロジェクション型で少々取り扱い方が異なり、例えば、実装型(=自家製のコード)のオブジェクトを作成する場合は低コストで生成できるといったメリットも存在します。プログラマーが気にする部分は大きくなりましたが、逆に言えばきちんと実装できるので、あればパフォーマンスを大きく得られる仕様になったと言えます。
3. Windows Runtime Component を C++/WinRT で作成する場合のハマりポイント
これが書きたかった本題w
Q. C++/WinRT で作成したコンポーネントを C# で unit test したい (CLASS_NOT_REGISTERED
)
A. デスクトップ互換を有効にすること。
i) VC++ のプロジェクト プロパティーから「デスクトップ互換」を有効化
ii) あるいは、PropertyGroup
に<DesktopCompatible>true</DesktopCompatible>
をぶち込む(公式) CsWinRT/embedded.md at 31a191d0e9b557e3732f93d86cf626defbb8d009 · microsoft/CsWinRT · GitHub
Q. WinUI 3 に関連した要素 (DispatcherQueue
が必要) を unit test したい
A. UITestMethodAttribute.DispatcherQueue
に DispatcherQueue
を確保することで実現可能。
- xaml 用のエントリーポイント `App` クラスとルートウィンドウ `MainWindow` の生成
- OnLaunched でウィンドウを作ってそこから `DispatcherQueue` を確保。
AngelUmbrella/App.xaml.cs at 27290c65070d30d0f7891febeaa499ca12ea5434 · mntone/AngelUmbrella · GitHubprotected override void OnLaunched(LaunchActivatedEventArgs args) { Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI(); _window = new MainWindow(); _window.Activate(); UITestMethodAttribute.DispatcherQueue = _window.DispatcherQueue; // Replace back with args.Arguments when https://github.com/microsoft/microsoft-ui-xaml/issues/3368 is fixed Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(Environment.CommandLine); }
Q. C++/WinRT で作成したコンポーネントを C# projection で C# に適した形にしたい
A. 一部を internal として定義し、それに対する関数を書く。
- プロジェクション設定に include/exclude を設定 (このとき、public と private を分けて定義しても出力ファイル名が同じなため、public が private によって上書きされるため、public と private を分けたい場合は public 要素をコピーするなどして対処)
<PropertyGroup> <LangVersion>10</LangVersion> <CsWinRTPrivateProjection>true</CsWinRTPrivateProjection> <CsWinRTIncludes>Mntone.AngelUmbrella</CsWinRTIncludes> <CsWinRTExcludes>Mntone.AngelUmbrella.Composition.SystemBackdrops</CsWinRTExcludes> <CsWinRTPrivateFilters> -include Mntone.AngelUmbrella.Composition.SystemBackdrops.IDesktopAcrylicHelperStatics -include Mntone.AngelUmbrella.Composition.SystemBackdrops.DesktopAcrylicHelper </CsWinRTPrivateFilters> <CsWinRTIIDOptimizerOptOut>true</CsWinRTIIDOptimizerOptOut> </PropertyGroup>- private にした要素を使って拡張メソッドを定義する (例: AngelUmbrella/DesktopAcrylicControllerExtensions.cs at feafa0495cadfd80f5e182ccea6633ed9927a89d · mntone/AngelUmbrella · GitHub )
ざっとこんなところかな? UnitTest などは Angel Umbrella 参考にしてもらえるといいかと。
4. 最後に
WinUI 3 は Windows App SDK Version 1.2 になってから初めて触っているのですが、めっちゃよくないですか? 自分は、WPF で今更新規アプリ作りたくないなーってぐらいに、WinUI がいいと思いました。
WinUI 3 自体に機能が存在しなくても、古の Win32 をたたけば何とかなるので、たいていのことは何でもできます。逆に UWP (PartialTrust) からできなくなったことは、サンドボックス下で現状動かせない (FullTrust) ということ。これは近代 Win32 としては改修が今後必要になる案件だと思っています。
AppContainer for Win32 apps という Issue も立っているので、この辺りは実装されるかもしれません。
github.com
ちなみに、ポジティブ☆デスクトップを AppContainer で動かしたことありますが、キーボード入力は取れなくなりましたね。この辺り、API レベルでもっと事細かに権限 on/off できるようになれば、いいのですがねー。
Rust/WinRT もやってみたさがありますが、そもそも自分は Rust に関しては全然詳しくはない (昔 SimpleLUTGenerator というのを書いたことはありますが) ため、調査に時間がかかりそうってのはありますね…
今日のところはこの辺で。C++/WinRT や C#/WinRT に関してわからないことがあれば、Twitter や Mastodon、Reply でも DM でもどちらでもよいんで、気軽に聞いていただければよいかと!
*1:Application Binary Interface