From b3c49ba16fba1d4e143603750b93450adb3f985e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 29 Nov 2023 12:53:10 +0100 Subject: [PATCH 1/5] Add MarshalString.FromAbiUnsafe method --- src/WinRT.Runtime/Marshalers.cs | 33 ++++++++++++++++++- .../MatchingRefApiCompatBaseline.txt | 3 +- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/WinRT.Runtime/Marshalers.cs b/src/WinRT.Runtime/Marshalers.cs index b309988dd..079a8202a 100644 --- a/src/WinRT.Runtime/Marshalers.cs +++ b/src/WinRT.Runtime/Marshalers.cs @@ -171,6 +171,37 @@ public static unsafe string FromAbi(IntPtr value) return new string(buffer, 0, (int)length); } + /// + /// Marshals an input HSTRING value to a value. + /// + /// The input HSTRING value to marshal. + /// The resulting value. + /// + /// + /// This method is equivalent to , but it does not create a new instance. + /// Doing so makes it zero-allocation, but extra care should be taken by callers to ensure that the returned value + /// does not escape the scope where the source HSTRING is valid. + /// + /// + /// For instance, if this method is invoked in the scope of a method that receives the HSTRING value as one of + /// its parameters, the resulting is always valid for the scope of such method. But, if + /// the HSTRING was created by reference in a given scope, the resulting value + /// will also only be valid within such scope, and should not be used outside of it. + /// + /// + public static unsafe ReadOnlySpan FromAbiUnsafe(IntPtr value) + { + if (value == IntPtr.Zero) + { + return "".AsSpan(); + } + + uint length; + char* buffer = Platform.WindowsGetStringRawBuffer(value, &length); + + return new(buffer, (int)length); + } + public static unsafe IntPtr FromManaged(string value) { if (value is null) @@ -1194,7 +1225,7 @@ private static Func BindCreateMarshaler() #if EMBED internal -#else +#else public #endif static class MarshalInspectable< diff --git a/src/WinRT.Runtime/MatchingRefApiCompatBaseline.txt b/src/WinRT.Runtime/MatchingRefApiCompatBaseline.txt index 755768cf6..8f52bb8c1 100644 --- a/src/WinRT.Runtime/MatchingRefApiCompatBaseline.txt +++ b/src/WinRT.Runtime/MatchingRefApiCompatBaseline.txt @@ -120,4 +120,5 @@ TypesMustExist : Type 'WinRT.StructTypeDetails' does not exist in the r MembersMustExist : Member 'public void WinRT.WindowsRuntimeTypeAttribute..ctor(System.String, System.String)' does not exist in the reference but it does exist in the implementation. MembersMustExist : Member 'public System.String WinRT.WindowsRuntimeTypeAttribute.GuidSignature.get()' does not exist in the reference but it does exist in the implementation. TypesMustExist : Type 'WinRT.WinRTExposedTypeAttribute' does not exist in the reference but it does exist in the implementation. -Total Issues: 121 +MembersMustExist : Member 'public System.ReadOnlySpan WinRT.MarshalString.FromAbiUnsafe(System.IntPtr)' does not exist in the reference but it does exist in the implementation. +Total Issues: 122 From 9dd0f6519374b02c7525d3bec203c8b8551b8ed9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 29 Nov 2023 12:54:04 +0100 Subject: [PATCH 2/5] Make exported activation factory zero-alloc --- src/Authoring/WinRT.SourceGenerator/Generator.cs | 2 +- src/cswinrt/code_writers.h | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Authoring/WinRT.SourceGenerator/Generator.cs b/src/Authoring/WinRT.SourceGenerator/Generator.cs index baf75519b..3d522360b 100644 --- a/src/Authoring/WinRT.SourceGenerator/Generator.cs +++ b/src/Authoring/WinRT.SourceGenerator/Generator.cs @@ -226,7 +226,7 @@ public static int DllGetActivationFactory(void* activatableClassId, void** facto try { - IntPtr obj = GetActivationFactory(MarshalString.FromAbi((IntPtr)activatableClassId)); + IntPtr obj = GetActivationFactory(MarshalString.FromAbiUnsafe((IntPtr)activatableClassId)); if ((void*)obj is null) { diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index 42b0f09c4..ec379af5c 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -9450,14 +9450,16 @@ namespace WinRT { % static partial class Module { -public static unsafe IntPtr GetActivationFactory(String runtimeClassId) +public static unsafe IntPtr GetActivationFactory(% runtimeClassId) {% return IntPtr.Zero; } +% } } )", internal_accessibility(), + settings.netstandard_compat ? "string" : "ReadOnlySpan", bind_each([](writer& w, TypeDef const& type) { w.write(R"( @@ -9473,7 +9475,13 @@ bind(type, typedef_name_type::CCW, true) ); }, types - )); + ), + settings.netstandard_compat ? "// No ReadOnlySpan overload available" : R"( +public static IntPtr GetActivationFactory(string runtimeClassId) +{ + return GetActivationFactory(runtimeClassId.AsSpan()); +}" +)"); } void write_event_source_generic_args(writer& w, cswinrt::type_semantics eventTypeSemantics) From 6a84928299d6387b6e209618cf382a3fded0c835 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 29 Nov 2023 13:02:37 +0100 Subject: [PATCH 3/5] Always lookup the string overload from native host --- src/Authoring/WinRT.Host.Shim/Module.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Authoring/WinRT.Host.Shim/Module.cs b/src/Authoring/WinRT.Host.Shim/Module.cs index 89ae253b8..bb90a0b3b 100644 --- a/src/Authoring/WinRT.Host.Shim/Module.cs +++ b/src/Authoring/WinRT.Host.Shim/Module.cs @@ -36,7 +36,7 @@ public static unsafe int GetActivationFactory(IntPtr hstrTargetAssembly, IntPtr { return REGDB_E_READREGDB; } - var GetActivationFactory = type.GetMethod("GetActivationFactory"); + var GetActivationFactory = type.GetMethod("GetActivationFactory", new Type[] { typeof(string) }); if (GetActivationFactory == null) { return REGDB_E_READREGDB; From 1914209cd0efa871df2a14740f9ff3397080e9f9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 29 Nov 2023 14:35:16 +0100 Subject: [PATCH 4/5] Change some SizeOf() to just sizeof(T) --- src/WinRT.Runtime/Marshalers.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/WinRT.Runtime/Marshalers.cs b/src/WinRT.Runtime/Marshalers.cs index 079a8202a..23699ba42 100644 --- a/src/WinRT.Runtime/Marshalers.cs +++ b/src/WinRT.Runtime/Marshalers.cs @@ -131,7 +131,7 @@ public unsafe IntPtr GetAbi() { IntPtr hstring; Debug.Assert(_header == IntPtr.Zero); - _header = Marshal.AllocHGlobal(Unsafe.SizeOf()); + _header = Marshal.AllocHGlobal(sizeof(HSTRING_HEADER)); Marshal.ThrowExceptionForHR(Platform.WindowsCreateStringReference( (ushort*)chars, value.Length, (IntPtr*)_header, &hstring)); return hstring; @@ -249,7 +249,7 @@ public static unsafe MarshalerArray CreateMarshalerArray(string[] array) try { var length = array.Length; - m._array = Marshal.AllocCoTaskMem(length * Marshal.SizeOf()); + m._array = Marshal.AllocCoTaskMem(length * sizeof(IntPtr)); m._marshalers = new MarshalString[length]; var elements = (IntPtr*)m._array.ToPointer(); for (int i = 0; i < length; i++) @@ -317,7 +317,7 @@ public static unsafe (int length, IntPtr data) FromManagedArray(string[] array) try { var length = array.Length; - data = Marshal.AllocCoTaskMem(length * Marshal.SizeOf()); + data = Marshal.AllocCoTaskMem(length * sizeof(IntPtr)); var elements = (IntPtr*)data; for (i = 0; i < length; i++) { From c50ccf2873a802119adc91672af27cb711617435 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 29 Nov 2023 14:39:20 +0100 Subject: [PATCH 5/5] Add unit test for MarshalString.FromAbiUnsafe --- src/Tests/UnitTest/TestComponentCSharp_Tests.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs index 0fd80d0c4..1c7b1136f 100644 --- a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs +++ b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs @@ -1281,6 +1281,22 @@ public unsafe void TestFactoryCast() Assert.Equal("ComImports", MarshalString.FromAbi(hstr)); } + [Fact] + public unsafe void TestFactoryCast_Unsafe() + { + IntPtr hstr; + + // Access nonstatic class factory + var instanceFactory = Class.As(); + instanceFactory.ToString(out hstr); + Assert.Equal("Class", MarshalString.FromAbiUnsafe(hstr).ToString()); + + // Access static class factory + var staticFactory = ComImports.As(); + staticFactory.ToString(out hstr); + Assert.Equal("ComImports", MarshalString.FromAbiUnsafe(hstr).ToString()); + } + [Fact] public void TestFundamentalGeneric() {