From 44096181fa6c6bb462977045a459250e50a89e5e Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 4 Apr 2022 23:14:18 -0700 Subject: [PATCH 01/10] Add AnsiStringMarshaller, Utf16StringMarshaller, Utf8StringMarshaller --- .../System.Private.CoreLib.Shared.projitems | 3 + .../InteropServices/AnsiStringMarshaller.cs | 52 +++++++++ .../InteropServices/Utf16StringMarshaller.cs | 82 ++++++++++++++ .../InteropServices/Utf8StringMarshaller.cs | 66 +++++++++++ .../ref/System.Runtime.InteropServices.cs | 43 +++++++ .../StringTests.cs | 24 ++-- .../TestAssets/SharedTypes/NonBlittable.cs | 107 ------------------ 7 files changed, 258 insertions(+), 119 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 0b77ecb3fb0878..4135d29bf50d94 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -782,6 +782,7 @@ + @@ -887,6 +888,8 @@ + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs new file mode 100644 index 00000000000000..07da89fde857b3 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// + /// Marshaller for ANSI strings + /// + [CLSCompliant(false)] + [CustomTypeMarshaller(typeof(string), BufferSize = 0x100, + Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer)] + public unsafe ref struct AnsiStringMarshaller + { + private byte* allocated; + private Span span; + + public AnsiStringMarshaller(string str) + : this(str, default(Span)) + { } + + public AnsiStringMarshaller(string str, Span buffer) + { + allocated = null; + span = default; + if (str is null) + return; + + // +1 for null terminator + // + 1 for the null character from the user. + 1 for the null character we put in. + int maxLength = (str.Length + 1) * Marshal.SystemMaxDBCSCharSize + 1; + if (buffer.Length >= maxLength) + { + int length = Marshal.StringToAnsiString(str, (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(buffer)), buffer.Length); + span = buffer.Slice(0, length); + } + else + { + allocated = (byte*)Marshal.StringToCoTaskMemAnsi(str); + } + } + + public byte* ToNativeValue() => allocated != null ? allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + + public void FromNativeValue(byte* value) => allocated = value; + + public string? ToManaged() => allocated == null ? null : new string((sbyte*)allocated); + + public void FreeNative() => Marshal.FreeCoTaskMem((IntPtr)allocated); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs new file mode 100644 index 00000000000000..35c8eb69ae1810 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// + /// Marshaller for UTF-16 strings + /// + [CLSCompliant(false)] + [CustomTypeMarshaller(typeof(string), BufferSize = 0x100, + Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer)] + public unsafe ref struct Utf16StringMarshaller + { + private ushort* allocated; + private Span span; + private bool isNullString; + + public Utf16StringMarshaller(string str) + : this(str, default(Span)) + { + } + + public Utf16StringMarshaller(string str, Span buffer) + { + isNullString = false; + span = default; + if (str is null) + { + allocated = null; + isNullString = true; + } + else if ((str.Length + 1) < buffer.Length) + { + span = buffer; + str.AsSpan().CopyTo(MemoryMarshal.Cast(buffer)); + // Supplied memory is in an undefined state so ensure + // there is a trailing null in the buffer. + span[str.Length] = '\0'; + allocated = null; + } + else + { + allocated = (ushort*)Marshal.StringToCoTaskMemUni(str); + } + } + + public ref ushort GetPinnableReference() + { + if (allocated != null) + return ref Unsafe.AsRef(allocated); + + return ref span.GetPinnableReference(); + } + + public ushort* ToNativeValue() => allocated != null ? allocated : (ushort*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + + public void FromNativeValue(ushort* value) + { + allocated = value; + isNullString = value == null; + } + + public string? ToManaged() + { + if (isNullString) + return null; + + if (allocated != null) + return new string((char*)allocated); + + return MemoryMarshal.Cast(span).ToString(); + } + + public void FreeNative() + { + if (allocated != null) + Marshal.FreeCoTaskMem((IntPtr)allocated); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs new file mode 100644 index 00000000000000..d63386761a8e86 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Text; + +namespace System.Runtime.InteropServices +{ + /// + /// Marshaller for UTF-8 strings + /// + [CLSCompliant(false)] + [CustomTypeMarshaller(typeof(string), BufferSize = 0x100, + Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer)] + public unsafe ref struct Utf8StringMarshaller + { + private byte* allocated; + private Span span; + + // Conversion from a 2-byte 'char' in UTF-16 to bytes in UTF-8 has a maximum of 3 bytes per 'char' + // Two bytes ('char') in UTF-16 can be either: + // - Code point in the Basic Multilingual Plane: all 16 bits are that of the code point + // - Part of a pair for a code point in the Supplementary Planes: 10 bits are that of the code point + // In UTF-8, 3 bytes are need to represent the code point in first and 4 bytes in the second. Thus, the + // maximum number of bytes per 'char' is 3. + private const int MaxByteCountPerChar = 3; + + public Utf8StringMarshaller(string str) + : this(str, default(Span)) + { } + + public Utf8StringMarshaller(string str, Span buffer) + { + allocated = null; + span = default; + if (str is null) + return; + + // + 1 for number of characters in case left over high surrogate is ? + // * (3 for UTF-8) + // +1 for null terminator + if (buffer.Length >= (str.Length + 1) * MaxByteCountPerChar + 1) + { + int byteCount = Encoding.UTF8.GetBytes(str, buffer); + buffer[byteCount] = 0; // null-terminate + span = buffer; + } + else + { + allocated = (byte*)Marshal.StringToCoTaskMemUTF8(str); + } + } + + public byte* ToNativeValue() => allocated != null ? allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + + public void FromNativeValue(byte* value) => allocated = value; + + public string? ToManaged() => allocated == null ? null : Marshal.PtrToStringUTF8((IntPtr)allocated); + + public void FreeNative() + { + if (allocated != null) + Marshal.FreeCoTaskMem((IntPtr)allocated); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index 72adc4842f9676..61af7ad1d8bce6 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -98,6 +98,20 @@ public sealed partial class AllowReversePInvokeCallsAttribute : System.Attribute { public AllowReversePInvokeCallsAttribute() { } } + [System.CLSCompliant(false)] + [System.Runtime.InteropServices.CustomTypeMarshaller(typeof(string), BufferSize = 0x100, + Features = System.Runtime.InteropServices.CustomTypeMarshallerFeatures.UnmanagedResources + | System.Runtime.InteropServices.CustomTypeMarshallerFeatures.CallerAllocatedBuffer + | System.Runtime.InteropServices.CustomTypeMarshallerFeatures.TwoStageMarshalling )] + public unsafe ref struct AnsiStringMarshaller + { + public AnsiStringMarshaller(string? str) { } + public AnsiStringMarshaller(string? str, System.Span buffer) { } + public byte* ToNativeValue() { throw null; } + public void FromNativeValue(byte* value) { } + public string? ToManaged() { throw null; } + public void FreeNative() { } + } public readonly partial struct ArrayWithOffset : System.IEquatable { private readonly object _dummy; @@ -1285,6 +1299,35 @@ public UnmanagedCallersOnlyAttribute() { } public System.Type[]? CallConvs; public string? EntryPoint; } + [System.CLSCompliant(false)] + [System.Runtime.InteropServices.CustomTypeMarshaller(typeof(string), BufferSize = 0x100, + Features = System.Runtime.InteropServices.CustomTypeMarshallerFeatures.UnmanagedResources + | System.Runtime.InteropServices.CustomTypeMarshallerFeatures.CallerAllocatedBuffer + | System.Runtime.InteropServices.CustomTypeMarshallerFeatures.TwoStageMarshalling )] + public unsafe ref struct Utf8StringMarshaller + { + public Utf8StringMarshaller(string? str) { } + public Utf8StringMarshaller(string? str, System.Span buffer) { } + public byte* ToNativeValue() { throw null; } + public void FromNativeValue(byte* value) { } + public string? ToManaged() { throw null; } + public void FreeNative() { } + } + [System.CLSCompliant(false)] + [System.Runtime.InteropServices.CustomTypeMarshaller(typeof(string), BufferSize = 0x100, + Features = System.Runtime.InteropServices.CustomTypeMarshallerFeatures.UnmanagedResources + | System.Runtime.InteropServices.CustomTypeMarshallerFeatures.CallerAllocatedBuffer + | System.Runtime.InteropServices.CustomTypeMarshallerFeatures.TwoStageMarshalling )] + public unsafe ref struct Utf16StringMarshaller + { + public Utf16StringMarshaller(string? str) { } + public Utf16StringMarshaller(string? str, System.Span buffer) { } + public ref ushort GetPinnableReference() { throw null; } + public ushort* ToNativeValue() { throw null; } + public void FromNativeValue(ushort* value) { } + public string? ToManaged() { throw null; } + public void FreeNative() { } + } } namespace System.Runtime.InteropServices.ComTypes { diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs index 73ac84acdfa1cf..81560998fde92b 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs @@ -188,43 +188,43 @@ public partial class StringMarshallingCustomType { public partial class Utf16 { - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReturnLength, StringMarshallingCustomType = typeof(SharedTypes.Utf16StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReturnLength, StringMarshallingCustomType = typeof(Utf16StringMarshaller))] public static partial int ReturnLength(string s); - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReverseReturn, StringMarshallingCustomType = typeof(SharedTypes.Utf16StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReverseReturn, StringMarshallingCustomType = typeof(Utf16StringMarshaller))] public static partial string Reverse_Return(string s); - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReverseOut, StringMarshallingCustomType = typeof(SharedTypes.Utf16StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReverseOut, StringMarshallingCustomType = typeof(Utf16StringMarshaller))] public static partial void Reverse_Out(string s, out string ret); - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReverseInplace, StringMarshallingCustomType = typeof(SharedTypes.Utf16StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReverseInplace, StringMarshallingCustomType = typeof(Utf16StringMarshaller))] public static partial void Reverse_Ref(ref string s); - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReverseInplace, StringMarshallingCustomType = typeof(SharedTypes.Utf16StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReverseInplace, StringMarshallingCustomType = typeof(Utf16StringMarshaller))] public static partial void Reverse_In(in string s); - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReverseReplace, StringMarshallingCustomType = typeof(SharedTypes.Utf16StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.UShort.ReverseReplace, StringMarshallingCustomType = typeof(Utf16StringMarshaller))] public static partial void Reverse_Replace_Ref(ref string s); } public partial class Utf8 { - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReturnLength, StringMarshallingCustomType = typeof(SharedTypes.Utf8StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReturnLength, StringMarshallingCustomType = typeof(Utf8StringMarshaller))] public static partial int ReturnLength(string s); - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReverseReturn, StringMarshallingCustomType = typeof(SharedTypes.Utf8StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReverseReturn, StringMarshallingCustomType = typeof(Utf8StringMarshaller))] public static partial string Reverse_Return(string s); - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReverseOut, StringMarshallingCustomType = typeof(SharedTypes.Utf8StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReverseOut, StringMarshallingCustomType = typeof(Utf8StringMarshaller))] public static partial void Reverse_Out(string s, out string ret); - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReverseInplace, StringMarshallingCustomType = typeof(SharedTypes.Utf8StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReverseInplace, StringMarshallingCustomType = typeof(Utf8StringMarshaller))] public static partial void Reverse_Ref(ref string s); - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReverseInplace, StringMarshallingCustomType = typeof(SharedTypes.Utf8StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReverseInplace, StringMarshallingCustomType = typeof(Utf8StringMarshaller))] public static partial void Reverse_In(in string s); - [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReverseReplace, StringMarshallingCustomType = typeof(SharedTypes.Utf8StringMarshaller))] + [LibraryImport(NativeExportsNE_Binary, EntryPoint = EntryPoints.Byte.ReverseReplace, StringMarshallingCustomType = typeof(Utf8StringMarshaller))] public static partial void Reverse_Replace_Ref(ref string s); } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs index 5697e7ad9215a4..772c983992dd20 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs @@ -124,113 +124,6 @@ public void FreeNative() } } - [CustomTypeMarshaller(typeof(string), Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer, BufferSize = 0x100)] - public unsafe ref struct Utf16StringMarshaller - { - private ushort* allocated; - private Span span; - private bool isNullString; - - public Utf16StringMarshaller(string str) - : this(str, default(Span)) - { - } - - public Utf16StringMarshaller(string str, Span buffer) - { - isNullString = false; - if (str is null) - { - allocated = null; - span = default; - isNullString = true; - } - else if ((str.Length + 1) < buffer.Length) - { - span = buffer; - str.AsSpan().CopyTo(MemoryMarshal.Cast(buffer)); - // Supplied memory is in an undefined state so ensure - // there is a trailing null in the buffer. - span[str.Length] = '\0'; - allocated = null; - } - else - { - allocated = (ushort*)Marshal.StringToCoTaskMemUni(str); - span = new Span(allocated, str.Length + 1); - } - } - - public ref ushort GetPinnableReference() - { - return ref span.GetPinnableReference(); - } - - public ushort* ToNativeValue() => (ushort*)Unsafe.AsPointer(ref GetPinnableReference()); - - public void FromNativeValue(ushort* value) - { - allocated = value; - span = new Span(value, value == null ? 0 : FindStringLength(value)); - isNullString = value == null; - - static int FindStringLength(ushort* ptr) - { - // Implemented similarly to string.wcslen as we can't access that outside of CoreLib - var searchSpace = new Span(ptr, int.MaxValue); - return searchSpace.IndexOf((ushort)0); - } - } - - public string ToManaged() - { - return isNullString ? null : MemoryMarshal.Cast(span).ToString(); - } - - public void FreeNative() - { - Marshal.FreeCoTaskMem((IntPtr)allocated); - } - } - - [CustomTypeMarshaller(typeof(string), Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer, BufferSize = 0x100)] - public unsafe ref struct Utf8StringMarshaller - { - private byte* allocated; - private Span span; - - public Utf8StringMarshaller(string str) - : this(str, default(Span)) - { } - - public Utf8StringMarshaller(string str, Span buffer) - { - allocated = null; - span = default; - if (str is null) - return; - - if (buffer.Length >= Encoding.UTF8.GetMaxByteCount(str.Length) + 1) // +1 for null terminator - { - int byteCount = Encoding.UTF8.GetBytes(str, buffer); - buffer[byteCount] = 0; // null-terminate - span = buffer; - } - else - { - allocated = (byte*)Marshal.StringToCoTaskMemUTF8(str); - } - } - - public byte* ToNativeValue() => allocated != null ? allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); - - public void FromNativeValue(byte* value) => allocated = value; - - public string? ToManaged() => Marshal.PtrToStringUTF8((IntPtr)allocated); - - public void FreeNative() => Marshal.FreeCoTaskMem((IntPtr)allocated); - } - [NativeMarshalling(typeof(IntStructWrapperNative))] public struct IntStructWrapper { From 5695075d8da2fa0582cc8e3a02e963e51907fd12 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 4 Apr 2022 23:14:57 -0700 Subject: [PATCH 02/10] Use StringMarshallers in generated code --- .../LibraryImportGenerator/Compatibility.md | 1 + .../Interop.MountPoints.FormatInfo.cs | 4 +- .../src/System/Drawing/GdiplusNative.Unix.cs | 2 +- .../InteropServices/AnsiStringMarshaller.cs | 37 ++++++++ .../InteropServices/Utf16StringMarshaller.cs | 40 +++++++++ .../InteropServices/Utf8StringMarshaller.cs | 37 ++++++++ .../MarshallingAttributeInfo.cs | 86 ++++++++++++++++--- .../TypeNames.cs | 4 + .../StringTests.cs | 11 +-- 9 files changed, 204 insertions(+), 18 deletions(-) diff --git a/docs/design/libraries/LibraryImportGenerator/Compatibility.md b/docs/design/libraries/LibraryImportGenerator/Compatibility.md index 8e4f8506941592..c4a1c9060eb64d 100644 --- a/docs/design/libraries/LibraryImportGenerator/Compatibility.md +++ b/docs/design/libraries/LibraryImportGenerator/Compatibility.md @@ -57,6 +57,7 @@ When marshalling as [ANSI](https://docs.microsoft.com/windows/win32/intl/code-pa - No optimization for stack allocation will be performed. Marshalling will always allocate through `AllocCoTaskMem`. The p/invoke source generator does not provide an equivalent to using `CharSet.Auto` in the built-in system. If platform-dependent behaviour is desired, it is left to the user to define different p/invokes with different marshalling configurations. +Similarly, `UnmanagedType.LPStr` will only mean ANSI rather than ANSI on Windows and UTF-8 on non-Windows. `UnmanagedType.LPUTF8Str` or `StringMarshalling.Utf8` can be used for UTF-8 and it is left to the user to define different p/invokes if platform-dependent behaviour is desired. ### `bool` marshalling diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs index 8229e642dcb11c..0abc6265c087ff 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs @@ -31,11 +31,11 @@ internal struct MountPointInformation } [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetSpaceInfoForMountPoint", SetLastError = true)] - internal static partial int GetSpaceInfoForMountPoint([MarshalAs(UnmanagedType.LPStr)]string name, out MountPointInformation mpi); + internal static partial int GetSpaceInfoForMountPoint([MarshalAs(UnmanagedType.LPUTF8Str)]string name, out MountPointInformation mpi); [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetFormatInfoForMountPoint", SetLastError = true)] private static unsafe partial int GetFormatInfoForMountPoint( - [MarshalAs(UnmanagedType.LPStr)]string name, + [MarshalAs(UnmanagedType.LPUTF8Str)]string name, byte* formatNameBuffer, int bufferLength, long* formatType); diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs index 440fdba1a6518d..7408d70ab74bcc 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs @@ -412,7 +412,7 @@ internal static partial int GdipRecordMetafileFromDelegateI_linux(StreamGetHeade [LibraryImport(LibraryName)] internal static partial int GdipGetPostScriptGraphicsContext( - [MarshalAs(UnmanagedType.LPStr)] string filename, + [MarshalAs(UnmanagedType.LPUTF8Str)] string filename, int width, int height, double dpix, double dpiy, ref IntPtr graphics); [LibraryImport(LibraryName)] diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs index 07da89fde857b3..a148e943fb2312 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs @@ -16,10 +16,22 @@ public unsafe ref struct AnsiStringMarshaller private byte* allocated; private Span span; + /// + /// Initializes a new instance of the . + /// + /// The string to marshal. public AnsiStringMarshaller(string str) : this(str, default(Span)) { } + /// + /// Initializes a new instance of the . + /// + /// The string to marshal. + /// Buffer that may be used for marshalling. + /// + /// + /// public AnsiStringMarshaller(string str, Span buffer) { allocated = null; @@ -41,12 +53,37 @@ public AnsiStringMarshaller(string str, Span buffer) } } + /// + /// Returns the native value representing the string. + /// + /// + /// + /// public byte* ToNativeValue() => allocated != null ? allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + /// + /// Sets the native value representing the string. + /// + /// The native value. + /// + /// + /// public void FromNativeValue(byte* value) => allocated = value; + /// + /// Returns the managed string. + /// + /// + /// + /// public string? ToManaged() => allocated == null ? null : new string((sbyte*)allocated); + /// + /// Frees native resources. + /// + /// + /// + /// public void FreeNative() => Marshal.FreeCoTaskMem((IntPtr)allocated); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs index 35c8eb69ae1810..df0319fbf0e3b9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs @@ -17,11 +17,23 @@ public unsafe ref struct Utf16StringMarshaller private Span span; private bool isNullString; + /// + /// Initializes a new instance of the . + /// + /// The string to marshal. public Utf16StringMarshaller(string str) : this(str, default(Span)) { } + /// + /// Initializes a new instance of the . + /// + /// The string to marshal. + /// Buffer that may be used for marshalling. + /// + /// + /// public Utf16StringMarshaller(string str, Span buffer) { isNullString = false; @@ -46,6 +58,9 @@ public Utf16StringMarshaller(string str, Span buffer) } } + /// + /// Returns a reference to the marshalled string. + /// public ref ushort GetPinnableReference() { if (allocated != null) @@ -54,14 +69,33 @@ public ref ushort GetPinnableReference() return ref span.GetPinnableReference(); } + /// + /// Returns the native value representing the string. + /// + /// + /// + /// public ushort* ToNativeValue() => allocated != null ? allocated : (ushort*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + /// + /// Sets the native value representing the string. + /// + /// The native value. + /// + /// + /// public void FromNativeValue(ushort* value) { allocated = value; isNullString = value == null; } + /// + /// Returns the managed string. + /// + /// + /// + /// public string? ToManaged() { if (isNullString) @@ -73,6 +107,12 @@ public void FromNativeValue(ushort* value) return MemoryMarshal.Cast(span).ToString(); } + /// + /// Frees native resources. + /// + /// + /// + /// public void FreeNative() { if (allocated != null) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs index d63386761a8e86..1585b1d1950a59 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs @@ -25,10 +25,22 @@ public unsafe ref struct Utf8StringMarshaller // maximum number of bytes per 'char' is 3. private const int MaxByteCountPerChar = 3; + /// + /// Initializes a new instance of the . + /// + /// The string to marshal. public Utf8StringMarshaller(string str) : this(str, default(Span)) { } + /// + /// Initializes a new instance of the . + /// + /// The string to marshal. + /// Buffer that may be used for marshalling. + /// + /// + /// public Utf8StringMarshaller(string str, Span buffer) { allocated = null; @@ -51,12 +63,37 @@ public Utf8StringMarshaller(string str, Span buffer) } } + /// + /// Returns the native value representing the string. + /// + /// + /// + /// public byte* ToNativeValue() => allocated != null ? allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + /// + /// Sets the native value representing the string. + /// + /// The native value. + /// + /// + /// public void FromNativeValue(byte* value) => allocated = value; + /// + /// Returns the managed string. + /// + /// + /// + /// public string? ToManaged() => allocated == null ? null : Marshal.PtrToStringUTF8((IntPtr)allocated); + /// + /// Frees native resources. + /// + /// + /// + /// public void FreeNative() { if (allocated != null) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs index a26bb93e3e3eb6..69acaadd8a2e48 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs @@ -90,6 +90,7 @@ public sealed record MarshalAsInfo( UnmanagedType UnmanagedType, CharEncoding CharEncoding) : MarshallingInfoStringSupport(CharEncoding) { + internal const UnmanagedType UnmanagedType_LPUTF8Str = (UnmanagedType)0x30; } /// @@ -486,6 +487,7 @@ private MarshallingInfo CreateInfoFromMarshalAs( { _diagnostics.ReportConfigurationNotSupported(attrData, nameof(UnmanagedType), unmanagedType.ToString()); } + bool isArrayType = unmanagedType == UnmanagedType.LPArray || unmanagedType == UnmanagedType.ByValArray; UnmanagedType elementUnmanagedType = (UnmanagedType)SizeAndParamIndexInfo.UnspecifiedConstSize; SizeAndParamIndexInfo arraySizeInfo = SizeAndParamIndexInfo.Unspecified; @@ -547,7 +549,9 @@ private MarshallingInfo CreateInfoFromMarshalAs( MarshallingInfo elementMarshallingInfo = NoMarshallingInfo.Instance; if (elementUnmanagedType != (UnmanagedType)SizeAndParamIndexInfo.UnspecifiedConstSize) { - elementMarshallingInfo = new MarshalAsInfo(elementUnmanagedType, _defaultInfo.CharEncoding); + elementMarshallingInfo = elementType.SpecialType == SpecialType.System_String + ? CreateStringMarshallingInfo(elementType, elementUnmanagedType, attrData) + : new MarshalAsInfo(elementUnmanagedType, _defaultInfo.CharEncoding); } else { @@ -558,6 +562,11 @@ private MarshallingInfo CreateInfoFromMarshalAs( return CreateArrayMarshallingInfo(elementType, arraySizeInfo, elementMarshallingInfo); } + if (type.SpecialType == SpecialType.System_String) + { + return CreateStringMarshallingInfo(type, unmanagedType, attrData); + } + return new MarshalAsInfo(unmanagedType, _defaultInfo.CharEncoding); } @@ -729,17 +738,28 @@ private bool TryCreateTypeBasedMarshallingInfo( // If the type is a character or string then pass on these details. if (type.SpecialType == SpecialType.System_Char || type.SpecialType == SpecialType.System_String) { - if (_defaultInfo.CharEncoding == CharEncoding.Custom && _defaultInfo.StringMarshallingCustomType is not null) - { - AttributeData attrData = _contextSymbol is IMethodSymbol - ? _contextSymbol.GetAttributes().First(a => a.AttributeClass.ToDisplayString() == TypeNames.LibraryImportAttribute) - : default; - marshallingInfo = CreateNativeMarshallingInfo(type, _defaultInfo.StringMarshallingCustomType, attrData, true, indirectionLevel, parsedCountInfo, useSiteAttributes, inspectedElements, ref maxIndirectionDepthUsed); - return true; - } - if (_defaultInfo.CharEncoding != CharEncoding.Undefined) { + if (_defaultInfo.CharEncoding == CharEncoding.Custom) + { + if (_defaultInfo.StringMarshallingCustomType is not null) + { + AttributeData attrData = _contextSymbol is IMethodSymbol + ? _contextSymbol.GetAttributes().First(a => a.AttributeClass.ToDisplayString() == TypeNames.LibraryImportAttribute) + : default; + marshallingInfo = CreateNativeMarshallingInfo(type, _defaultInfo.StringMarshallingCustomType, attrData, true, indirectionLevel, parsedCountInfo, useSiteAttributes, inspectedElements, ref maxIndirectionDepthUsed); + return true; + } + } + else if (type.SpecialType == SpecialType.System_String) + { + AttributeData attrData = _contextSymbol is IMethodSymbol + ? _contextSymbol.GetAttributes().First(a => a.AttributeClass.ToDisplayString() == TypeNames.LibraryImportAttribute) + : default; + marshallingInfo = CreateStringMarshallingInfo(type, _defaultInfo.CharEncoding, default); + return true; + } + marshallingInfo = new MarshallingInfoStringSupport(_defaultInfo.CharEncoding); return true; } @@ -806,6 +826,52 @@ private MarshallingInfo CreateArrayMarshallingInfo( ElementMarshallingInfo: elementMarshallingInfo); } + private MarshallingInfo CreateStringMarshallingInfo( + ITypeSymbol type, + UnmanagedType unmanagedType, + AttributeData attrData) + { + CharEncoding charEncoding = unmanagedType switch + { + UnmanagedType.LPStr => CharEncoding.Ansi, + UnmanagedType.LPTStr or UnmanagedType.LPWStr => CharEncoding.Utf16, + MarshalAsInfo.UnmanagedType_LPUTF8Str => CharEncoding.Utf8, + _ => CharEncoding.Undefined + }; + if (charEncoding == CharEncoding.Undefined) + return new MarshalAsInfo(unmanagedType, _defaultInfo.CharEncoding); + + return CreateStringMarshallingInfo(type, charEncoding, attrData); + } + + private MarshallingInfo CreateStringMarshallingInfo( + ITypeSymbol type, + CharEncoding charEncoding, + AttributeData attrData) + { + string? marshallerName = charEncoding switch + { + CharEncoding.Ansi => TypeNames.AnsiStringMarshaller, + CharEncoding.Utf16 => TypeNames.Utf16StringMarshaller, + CharEncoding.Utf8 => TypeNames.Utf8StringMarshaller, + _ => throw new InvalidOperationException() + }; + INamedTypeSymbol? stringMarshaller = _compilation.GetTypeByMetadataName(marshallerName); + if (stringMarshaller is null) + return new MissingSupportMarshallingInfo(); + + var (_, _, customTypeMarshallerData) = ManualTypeMarshallingHelper.GetMarshallerShapeInfo(stringMarshaller); + Debug.Assert(customTypeMarshallerData is not null); + + return CreateNativeMarshallingInfoForValue( + type, + stringMarshaller, + attrData, + customTypeMarshallerData.Value, + allowPinningManagedType: charEncoding == CharEncoding.Utf16, + useDefaultMarshalling: false); + } + private MarshallingInfo GetBlittableMarshallingInfo(ITypeSymbol type) { if (type.TypeKind is TypeKind.Enum or TypeKind.Pointer or TypeKind.FunctionPointer diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs index ba09ebee8677e8..310299ed6d9f40 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs @@ -19,6 +19,10 @@ public static class TypeNames public const string CustomTypeMarshallerAttributeGenericPlaceholder = "System.Runtime.InteropServices.CustomTypeMarshallerAttribute.GenericPlaceholder"; + public const string AnsiStringMarshaller = "System.Runtime.InteropServices.AnsiStringMarshaller"; + public const string Utf16StringMarshaller = "System.Runtime.InteropServices.Utf16StringMarshaller"; + public const string Utf8StringMarshaller = "System.Runtime.InteropServices.Utf8StringMarshaller"; + public const string LCIDConversionAttribute = "System.Runtime.InteropServices.LCIDConversionAttribute"; public const string SuppressGCTransitionAttribute = "System.Runtime.InteropServices.SuppressGCTransitionAttribute"; diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs index 81560998fde92b..7c090b302515f5 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs @@ -375,11 +375,10 @@ public void UTF8StringByRef(string value) [Theory] [MemberData(nameof(UnicodeStrings))] + [PlatformSpecific(TestPlatforms.Windows)] public void AnsiStringMarshalledAsExpected(string value) { - int expectedLen = value != null - ? OperatingSystem.IsWindows() ? GetLengthAnsi(value) : Encoding.UTF8.GetByteCount(value) - : -1; + int expectedLen = value != null ? GetLengthAnsi(value) : -1; Assert.Equal(expectedLen, NativeExportsNE.LPStr.ReturnLength(value)); Assert.Equal(expectedLen, NativeExportsNE.LPStr.ReturnLength_IgnoreStringMarshalling(value)); @@ -387,9 +386,10 @@ public void AnsiStringMarshalledAsExpected(string value) [Theory] [MemberData(nameof(UnicodeStrings))] + [PlatformSpecific(TestPlatforms.Windows)] public void AnsiStringReturn(string value) { - string expected = OperatingSystem.IsWindows() ? ReverseAnsi(value) : ReverseBytes(value, Encoding.UTF8); + string expected = ReverseAnsi(value); Assert.Equal(expected, NativeExportsNE.LPStr.Reverse_Return(value)); @@ -400,10 +400,11 @@ public void AnsiStringReturn(string value) [Theory] [MemberData(nameof(UnicodeStrings))] + [PlatformSpecific(TestPlatforms.Windows)] public void AnsiStringByRef(string value) { string refValue = value; - string expected = OperatingSystem.IsWindows() ? ReverseAnsi(value) : ReverseBytes(value, Encoding.UTF8); + string expected = ReverseAnsi(value); NativeExportsNE.LPStr.Reverse_In(in refValue); Assert.Equal(value, refValue); // Should not be updated when using 'in' From cff2b5dec3d79440dc390065dd83e7ff25e9ca0d Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 4 Apr 2022 23:33:11 -0700 Subject: [PATCH 03/10] Remove direct generation --- .../LibraryImportGenerator/Compatibility.md | 1 - .../src/System/Drawing/macFunctions.cs | 8 +- .../MarshalAsMarshallingGeneratorFactory.cs | 35 +--- .../Marshalling/StringMarshaller.Ansi.cs | 149 -------------- .../Marshalling/StringMarshaller.Utf16.cs | 193 ------------------ .../Marshalling/StringMarshaller.Utf8.cs | 179 ---------------- 6 files changed, 7 insertions(+), 558 deletions(-) delete mode 100644 src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Ansi.cs delete mode 100644 src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Utf16.cs delete mode 100644 src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Utf8.cs diff --git a/docs/design/libraries/LibraryImportGenerator/Compatibility.md b/docs/design/libraries/LibraryImportGenerator/Compatibility.md index c4a1c9060eb64d..e147792dc9e9a1 100644 --- a/docs/design/libraries/LibraryImportGenerator/Compatibility.md +++ b/docs/design/libraries/LibraryImportGenerator/Compatibility.md @@ -54,7 +54,6 @@ In the built-in system, marshalling a `string` contains an optimization for para When marshalling as [ANSI](https://docs.microsoft.com/windows/win32/intl/code-pages) on Windows (using `UnmanagedType.LPStr`): - Best-fit mapping will be disabled and no exception will be thrown for unmappable characters. In the built-in system, this behaviour was configured through [`DllImportAttribute.BestFitMapping`] and [`DllImportAttribute.ThrowOnUnmappableChar`]. The generated marshalling code will have the equivalent behaviour of `BestFitMapping=false` and `ThrowOnUnmappableChar=false`. - - No optimization for stack allocation will be performed. Marshalling will always allocate through `AllocCoTaskMem`. The p/invoke source generator does not provide an equivalent to using `CharSet.Auto` in the built-in system. If platform-dependent behaviour is desired, it is left to the user to define different p/invokes with different marshalling configurations. Similarly, `UnmanagedType.LPStr` will only mean ANSI rather than ANSI on Windows and UTF-8 on non-Windows. `UnmanagedType.LPUTF8Str` or `StringMarshalling.Utf8` can be used for UTF-8 and it is left to the user to define different p/invokes if platform-dependent behaviour is desired. diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/macFunctions.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/macFunctions.cs index df6a19216d049d..4d810dc336eca3 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/macFunctions.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/macFunctions.cs @@ -151,8 +151,8 @@ internal static void ReleaseContext(IntPtr port, IntPtr context) } #region Cocoa Methods - [LibraryImport("libobjc.dylib", StringMarshalling = StringMarshalling.Utf8)] - public static partial IntPtr objc_getClass(string className); + [LibraryImport("libobjc.dylib")] + public static partial IntPtr objc_getClass([MarshalAs(UnmanagedType.LPUTF8Str)] string className); [LibraryImport("libobjc.dylib", EntryPoint = "objc_msgSend")] public static partial IntPtr intptr_objc_msgSend(IntPtr basePtr, IntPtr selector); [LibraryImport("libobjc.dylib", EntryPoint = "objc_msgSend_stret")] @@ -160,8 +160,8 @@ internal static void ReleaseContext(IntPtr port, IntPtr context) [LibraryImport("libobjc.dylib", EntryPoint = "objc_msgSend")] [return:MarshalAs(UnmanagedType.U1)] public static partial bool bool_objc_msgSend(IntPtr handle, IntPtr selector); - [LibraryImport("libobjc.dylib", StringMarshalling = StringMarshalling.Utf8)] - public static partial IntPtr sel_registerName(string selectorName); + [LibraryImport("libobjc.dylib")] + public static partial IntPtr sel_registerName([MarshalAs(UnmanagedType.LPUTF8Str)] string selectorName); #endregion [LibraryImport("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")] diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshalAsMarshallingGeneratorFactory.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshalAsMarshallingGeneratorFactory.cs index 1610495df4d7d7..bec9d388e52dd0 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshalAsMarshallingGeneratorFactory.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshalAsMarshallingGeneratorFactory.cs @@ -14,9 +14,6 @@ public sealed class MarshalAsMarshallingGeneratorFactory : IMarshallingGenerator private static readonly VariantBoolMarshaller s_variantBool = new(); private static readonly Utf16CharMarshaller s_utf16Char = new(); - private static readonly IMarshallingGenerator s_utf16String = new PinnableManagedValueMarshaller(new Utf16StringMarshaller()); - private static readonly Utf8StringMarshaller s_utf8String = new(); - private static readonly AnsiStringMarshaller s_ansiString = new AnsiStringMarshaller(s_utf8String); private static readonly Forwarder s_forwarder = new(); private static readonly BlittableMarshaller s_blittable = new(); @@ -109,7 +106,7 @@ public IMarshallingGenerator Create( return CreateCharMarshaller(info, context); case { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_String } }: - return CreateStringMarshaller(info, context); + return ReportStringMarshallingNotSupported(info, context); case { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Void } }: return s_forwarder; @@ -158,7 +155,7 @@ private static IMarshallingGenerator CreateCharMarshaller(TypePositionInfo info, throw new MarshallingNotSupportedException(info, context); } - private static IMarshallingGenerator CreateStringMarshaller(TypePositionInfo info, StubCodeContext context) + private static IMarshallingGenerator ReportStringMarshallingNotSupported(TypePositionInfo info, StubCodeContext context) { MarshallingInfo marshalInfo = info.MarshallingAttributeInfo; if (marshalInfo is NoMarshallingInfo) @@ -170,33 +167,7 @@ private static IMarshallingGenerator CreateStringMarshaller(TypePositionInfo inf }; } - // Explicit MarshalAs takes precedence over string encoding info - if (marshalInfo is MarshalAsInfo marshalAsInfo) - { - switch (marshalAsInfo.UnmanagedType) - { - case UnmanagedType.LPStr: - return s_ansiString; - case UnmanagedType.LPTStr: - case UnmanagedType.LPWStr: - return s_utf16String; - case (UnmanagedType)0x30:// UnmanagedType.LPUTF8Str - return s_utf8String; - } - } - else if (marshalInfo is MarshallingInfoStringSupport marshalStringInfo) - { - switch (marshalStringInfo.CharEncoding) - { - case CharEncoding.Ansi: - return s_ansiString; - case CharEncoding.Utf16: - return s_utf16String; - case CharEncoding.Utf8: - return s_utf8String; - } - } - + // Supported string marshalling should have gone through custom type marshallers. throw new MarshallingNotSupportedException(info, context); } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Ansi.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Ansi.cs deleted file mode 100644 index d279cb5d486e96..00000000000000 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Ansi.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; - -using static Microsoft.Interop.MarshallerHelpers; - -namespace Microsoft.Interop -{ - public sealed class AnsiStringMarshaller : ConditionalStackallocMarshallingGenerator - { - private static readonly TypeSyntax s_nativeType = PointerType(PredefinedType(Token(SyntaxKind.ByteKeyword))); - - private readonly Utf8StringMarshaller _utf8StringMarshaller; - - public AnsiStringMarshaller(Utf8StringMarshaller utf8StringMarshaller) - { - _utf8StringMarshaller = utf8StringMarshaller; - } - - public override SignatureBehavior GetNativeSignatureBehavior(TypePositionInfo info) - { - return info.IsByRef ? SignatureBehavior.PointerToNativeType : SignatureBehavior.NativeType; - } - - public override ValueBoundaryBehavior GetValueBoundaryBehavior(TypePositionInfo info, StubCodeContext context) - { - return info.IsByRef ? ValueBoundaryBehavior.AddressOfNativeIdentifier : ValueBoundaryBehavior.NativeIdentifier; - } - - public override TypeSyntax AsNativeType(TypePositionInfo info) - { - // byte* - return s_nativeType; - } - - public override IEnumerable Generate(TypePositionInfo info, StubCodeContext context) - { - (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info); - switch (context.CurrentStage) - { - case StubCodeContext.Stage.Setup: - if (TryGenerateSetupSyntax(info, context, out StatementSyntax conditionalAllocSetup)) - yield return conditionalAllocSetup; - - break; - case StubCodeContext.Stage.Marshal: - if (info.RefKind != RefKind.Out) - { - // = (byte*)Marshal.StringToCoTaskMemAnsi(); - BlockSyntax windowsBlock = Block( - ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - IdentifierName(nativeIdentifier), - CastExpression( - AsNativeType(info), - StringMarshaller.AllocationExpression(CharEncoding.Ansi, managedIdentifier))))); - - // Set the allocation marker to true if it is being used - if (UsesConditionalStackAlloc(info, context)) - { - // = true - windowsBlock = windowsBlock.AddStatements( - ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - IdentifierName(GetAllocationMarkerIdentifier(info, context)), - LiteralExpression(SyntaxKind.TrueLiteralExpression)))); - } - - // [Compat] The generated source for ANSI string marshalling does not optimize for - // allocating on the stack based on the string length. It always uses AllocCoTaskMem. - // if (OperatingSystem.IsWindows()) - // { - // = (byte*)Marshal.StringToCoTaskMemAnsi(); - // } - // else - // { - // << marshal as UTF-8 >> - // } - yield return IfStatement(IsWindows, - windowsBlock, - ElseClause( - Block(_utf8StringMarshaller.Generate(info, context)))); - } - break; - case StubCodeContext.Stage.Unmarshal: - if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) - { - // if (OperatingSystem.IsWindows()) - // { - // = == null ? null : new string((sbyte*)); - // } - // else - // { - // << unmarshal as UTF-8 >> - // } - yield return IfStatement(IsWindows, - Block( - ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - IdentifierName(managedIdentifier), - ConditionalExpression( - BinaryExpression( - SyntaxKind.EqualsExpression, - IdentifierName(nativeIdentifier), - LiteralExpression(SyntaxKind.DefaultLiteralExpression)), - LiteralExpression(SyntaxKind.NullLiteralExpression), - ObjectCreationExpression( - PredefinedType(Token(SyntaxKind.StringKeyword)), - ArgumentList(SingletonSeparatedList( - Argument( - CastExpression( - PointerType(PredefinedType(Token(SyntaxKind.SByteKeyword))), - IdentifierName(nativeIdentifier))))), - initializer: null))))), - ElseClause( - Block(_utf8StringMarshaller.Generate(info, context)))); - } - break; - case StubCodeContext.Stage.Cleanup: - yield return GenerateConditionalAllocationFreeSyntax(info, context); - break; - } - } - - public override bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true; - - public override bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; - - // This marshaller only uses the conditional allocaction base for setup and cleanup. - // It always allocates for ANSI (Windows) and relies on the UTF-8 (non-Windows) string marshaller for allocation/marshalling. - protected override ExpressionSyntax GenerateAllocationExpression(TypePositionInfo info, StubCodeContext context, SyntaxToken byteLengthIdentifier, out bool allocationRequiresByteLength) => throw new NotImplementedException(); - protected override ExpressionSyntax GenerateByteLengthCalculationExpression(TypePositionInfo info, StubCodeContext context) => throw new NotImplementedException(); - protected override StatementSyntax GenerateStackallocOnlyValueMarshalling(TypePositionInfo info, StubCodeContext context, SyntaxToken byteLengthIdentifier, SyntaxToken stackAllocPtrIdentifier) => throw new NotImplementedException(); - - protected override ExpressionSyntax GenerateFreeExpression(TypePositionInfo info, StubCodeContext context) - { - return StringMarshaller.FreeExpression(context.GetIdentifiers(info).native); - } - } -} diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Utf16.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Utf16.cs deleted file mode 100644 index 956098935e6bd2..00000000000000 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Utf16.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; - -using static Microsoft.Interop.MarshallerHelpers; - -namespace Microsoft.Interop -{ - public sealed class Utf16StringMarshaller : ConditionalStackallocMarshallingGenerator - { - // [Compat] Equivalent of MAX_PATH on Windows to match built-in system - // The assumption is file paths are the most common case for marshalling strings, - // so the threshold for optimized allocation is based on that length. - private const int StackAllocBytesThreshold = 260 * sizeof(ushort); - - private static readonly TypeSyntax s_nativeType = PointerType(PredefinedType(Token(SyntaxKind.UShortKeyword))); - - public override SignatureBehavior GetNativeSignatureBehavior(TypePositionInfo info) - { - return info.IsByRef ? SignatureBehavior.PointerToNativeType : SignatureBehavior.NativeType; - } - - public override ValueBoundaryBehavior GetValueBoundaryBehavior(TypePositionInfo info, StubCodeContext context) - { - return info.IsByRef ? ValueBoundaryBehavior.AddressOfNativeIdentifier : ValueBoundaryBehavior.NativeIdentifier; - } - - public override TypeSyntax AsNativeType(TypePositionInfo info) - { - // ushort* - return s_nativeType; - } - - public override IEnumerable Generate(TypePositionInfo info, StubCodeContext context) - { - (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info); - - switch (context.CurrentStage) - { - case StubCodeContext.Stage.Setup: - if (TryGenerateSetupSyntax(info, context, out StatementSyntax conditionalAllocSetup)) - yield return conditionalAllocSetup; - - break; - case StubCodeContext.Stage.Marshal: - if (info.RefKind != RefKind.Out) - { - foreach (StatementSyntax statement in GenerateConditionalAllocationSyntax( - info, - context, - StackAllocBytesThreshold)) - { - yield return statement; - } - } - break; - case StubCodeContext.Stage.Unmarshal: - if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) - { - // = == null ? null : new string((char*)); - yield return ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - IdentifierName(managedIdentifier), - ConditionalExpression( - BinaryExpression( - SyntaxKind.EqualsExpression, - IdentifierName(nativeIdentifier), - LiteralExpression(SyntaxKind.DefaultLiteralExpression)), - LiteralExpression(SyntaxKind.NullLiteralExpression), - ObjectCreationExpression( - PredefinedType(Token(SyntaxKind.StringKeyword)), - ArgumentList(SingletonSeparatedList( - Argument( - CastExpression( - PointerType(PredefinedType(Token(SyntaxKind.CharKeyword))), - IdentifierName(nativeIdentifier))))), - initializer: null)))); - } - break; - case StubCodeContext.Stage.Cleanup: - yield return GenerateConditionalAllocationFreeSyntax(info, context); - - break; - } - } - - public override bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) - => true; - - public override bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; - - protected override ExpressionSyntax GenerateAllocationExpression( - TypePositionInfo info, - StubCodeContext context, - SyntaxToken byteLengthIdentifier, - out bool allocationRequiresByteLength) - { - allocationRequiresByteLength = false; - return CastExpression( - AsNativeType(info), - StringMarshaller.AllocationExpression(CharEncoding.Utf16, context.GetIdentifiers(info).managed)); - } - - protected override ExpressionSyntax GenerateByteLengthCalculationExpression(TypePositionInfo info, StubCodeContext context) - { - // +1 for null terminator - // *2 for number of bytes per char - // int = (.Length + 1) * 2; - return - BinaryExpression( - SyntaxKind.MultiplyExpression, - ParenthesizedExpression( - BinaryExpression( - SyntaxKind.AddExpression, - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(context.GetIdentifiers(info).managed), - IdentifierName("Length")), - LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(1)))), - LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(2))); - } - - protected override StatementSyntax GenerateStackallocOnlyValueMarshalling( - TypePositionInfo info, - StubCodeContext context, - SyntaxToken byteLengthIdentifier, - SyntaxToken stackAllocPtrIdentifier) - { - string managedIdentifier = context.GetIdentifiers(info).managed; - return Block( - // ((ReadOnlySpan)).CopyTo(new Span(, .Length)); - ExpressionStatement( - InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - ParenthesizedExpression( - CastExpression( - GenericName(Identifier("System.ReadOnlySpan"), - TypeArgumentList(SingletonSeparatedList( - PredefinedType(Token(SyntaxKind.CharKeyword))))), - IdentifierName(managedIdentifier))), - IdentifierName("CopyTo")), - ArgumentList( - SeparatedList(new[] { - Argument( - ObjectCreationExpression( - GenericName(Identifier(TypeNames.System_Span), - TypeArgumentList(SingletonSeparatedList( - PredefinedType(Token(SyntaxKind.CharKeyword))))), - ArgumentList( - SeparatedList(new[]{ - Argument(IdentifierName(stackAllocPtrIdentifier)), - Argument( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(managedIdentifier), - IdentifierName("Length")))})), - initializer: null))})))), - // ((char*))[.Length] = '\0'; - ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - ElementAccessExpression( - ParenthesizedExpression( - CastExpression( - PointerType(PredefinedType(Token(SyntaxKind.CharKeyword))), - IdentifierName(stackAllocPtrIdentifier))), - BracketedArgumentList( - SingletonSeparatedList( - Argument( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(managedIdentifier), - IdentifierName("Length")))))), - LiteralExpression( - SyntaxKind.CharacterLiteralExpression, - Literal('\0'))))); - } - - protected override ExpressionSyntax GenerateFreeExpression( - TypePositionInfo info, - StubCodeContext context) - { - return StringMarshaller.FreeExpression(context.GetIdentifiers(info).native); - } - } -} diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Utf8.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Utf8.cs deleted file mode 100644 index 1774811838fb64..00000000000000 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/StringMarshaller.Utf8.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; - -using static Microsoft.Interop.MarshallerHelpers; - -namespace Microsoft.Interop -{ - public sealed class Utf8StringMarshaller : ConditionalStackallocMarshallingGenerator - { - // [Compat] Equivalent of MAX_PATH on Windows to match built-in system - // The assumption is file paths are the most common case for marshalling strings, - // so the threshold for optimized allocation is based on that length. - private const int StackAllocBytesThreshold = 260; - - // Conversion from a 2-byte 'char' in UTF-16 to bytes in UTF-8 has a maximum of 3 bytes per 'char' - // Two bytes ('char') in UTF-16 can be either: - // - Code point in the Basic Multilingual Plane: all 16 bits are that of the code point - // - Part of a pair for a code point in the Supplementary Planes: 10 bits are that of the code point - // In UTF-8, 3 bytes are need to represent the code point in first and 4 bytes in the second. Thus, the - // maximum number of bytes per 'char' is 3. - private const int MaxByteCountPerChar = 3; - - private static readonly TypeSyntax s_nativeType = PointerType(PredefinedType(Token(SyntaxKind.ByteKeyword))); - private static readonly TypeSyntax s_utf8EncodingType = ParseTypeName("System.Text.Encoding.UTF8"); - - public override SignatureBehavior GetNativeSignatureBehavior(TypePositionInfo info) - { - return info.IsByRef ? SignatureBehavior.PointerToNativeType : SignatureBehavior.NativeType; - } - - public override ValueBoundaryBehavior GetValueBoundaryBehavior(TypePositionInfo info, StubCodeContext context) - { - return info.IsByRef ? ValueBoundaryBehavior.AddressOfNativeIdentifier : ValueBoundaryBehavior.NativeIdentifier; - } - - public override TypeSyntax AsNativeType(TypePositionInfo info) => s_nativeType; - - public override IEnumerable Generate(TypePositionInfo info, StubCodeContext context) - { - (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info); - switch (context.CurrentStage) - { - case StubCodeContext.Stage.Setup: - if (TryGenerateSetupSyntax(info, context, out StatementSyntax conditionalAllocSetup)) - yield return conditionalAllocSetup; - - break; - case StubCodeContext.Stage.Marshal: - if (info.RefKind != RefKind.Out) - { - foreach (StatementSyntax statement in GenerateConditionalAllocationSyntax( - info, - context, - StackAllocBytesThreshold)) - { - yield return statement; - } - } - break; - case StubCodeContext.Stage.Unmarshal: - if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) - { - // = Marshal.PtrToStringUTF8((IntPtr)); - yield return ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - IdentifierName(managedIdentifier), - InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - InteropServicesMarshalType, - IdentifierName("PtrToStringUTF8")), - ArgumentList(SingletonSeparatedList( - Argument( - CastExpression( - SystemIntPtrType, - IdentifierName(nativeIdentifier)))))))); - } - break; - case StubCodeContext.Stage.Cleanup: - yield return GenerateConditionalAllocationFreeSyntax(info, context); - break; - } - } - - public override bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true; - - public override bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; - - protected override ExpressionSyntax GenerateAllocationExpression( - TypePositionInfo info, - StubCodeContext context, - SyntaxToken byteLengthIdentifier, - out bool allocationRequiresByteLength) - { - allocationRequiresByteLength = false; - return CastExpression( - AsNativeType(info), - StringMarshaller.AllocationExpression(CharEncoding.Utf8, context.GetIdentifiers(info).managed)); - } - - protected override ExpressionSyntax GenerateByteLengthCalculationExpression(TypePositionInfo info, StubCodeContext context) - { - // + 1 for number of characters in case left over high surrogate is ? - // * (3 for UTF-8) - // +1 for null terminator - // int = (.Length + 1) * 3 + 1; - return BinaryExpression( - SyntaxKind.AddExpression, - BinaryExpression( - SyntaxKind.MultiplyExpression, - ParenthesizedExpression( - BinaryExpression( - SyntaxKind.AddExpression, - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(context.GetIdentifiers(info).managed), - IdentifierName("Length")), - LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(1)))), - LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(MaxByteCountPerChar))), - LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(1))); - } - - protected override StatementSyntax GenerateStackallocOnlyValueMarshalling( - TypePositionInfo info, - StubCodeContext context, - SyntaxToken byteLengthIdentifier, - SyntaxToken stackAllocPtrIdentifier) - { - return Block( - // = Encoding.UTF8.GetBytes(, new Span(, )); - ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - IdentifierName(byteLengthIdentifier), - InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - s_utf8EncodingType, - IdentifierName("GetBytes")), - ArgumentList( - SeparatedList(new ArgumentSyntax[] { - Argument(IdentifierName(context.GetIdentifiers(info).managed)), - Argument( - ObjectCreationExpression( - GenericName(Identifier(TypeNames.System_Span), - TypeArgumentList(SingletonSeparatedList( - PredefinedType(Token(SyntaxKind.ByteKeyword))))), - ArgumentList( - SeparatedList(new ArgumentSyntax[]{ - Argument(IdentifierName(stackAllocPtrIdentifier)), - Argument(IdentifierName(byteLengthIdentifier))})), - initializer: null))}))))), - // [] = 0; - ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - ElementAccessExpression( - IdentifierName(stackAllocPtrIdentifier), - BracketedArgumentList( - SingletonSeparatedList( - Argument(IdentifierName(byteLengthIdentifier))))), - LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0))))); - } - - protected override ExpressionSyntax GenerateFreeExpression( - TypePositionInfo info, - StubCodeContext context) - { - return StringMarshaller.FreeExpression(context.GetIdentifiers(info).native); - } - } -} From c5ae13ddd02b2136caf64f06210c807ab9edd2af Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 7 Apr 2022 11:43:54 -0700 Subject: [PATCH 04/10] PR feedback --- .../LibraryImportGenerator/Compatibility.md | 1 - .../InteropServices/AnsiStringMarshaller.cs | 40 ++++++++------ .../InteropServices/Utf16StringMarshaller.cs | 52 ++++++++++--------- .../InteropServices/Utf8StringMarshaller.cs | 50 +++++++++--------- .../MarshallingAttributeInfo.cs | 27 +++++----- .../StringTests.cs | 11 ++-- 6 files changed, 95 insertions(+), 86 deletions(-) diff --git a/docs/design/libraries/LibraryImportGenerator/Compatibility.md b/docs/design/libraries/LibraryImportGenerator/Compatibility.md index e147792dc9e9a1..0e3cd08727fb9c 100644 --- a/docs/design/libraries/LibraryImportGenerator/Compatibility.md +++ b/docs/design/libraries/LibraryImportGenerator/Compatibility.md @@ -56,7 +56,6 @@ When marshalling as [ANSI](https://docs.microsoft.com/windows/win32/intl/code-pa - Best-fit mapping will be disabled and no exception will be thrown for unmappable characters. In the built-in system, this behaviour was configured through [`DllImportAttribute.BestFitMapping`] and [`DllImportAttribute.ThrowOnUnmappableChar`]. The generated marshalling code will have the equivalent behaviour of `BestFitMapping=false` and `ThrowOnUnmappableChar=false`. The p/invoke source generator does not provide an equivalent to using `CharSet.Auto` in the built-in system. If platform-dependent behaviour is desired, it is left to the user to define different p/invokes with different marshalling configurations. -Similarly, `UnmanagedType.LPStr` will only mean ANSI rather than ANSI on Windows and UTF-8 on non-Windows. `UnmanagedType.LPUTF8Str` or `StringMarshalling.Utf8` can be used for UTF-8 and it is left to the user to define different p/invokes if platform-dependent behaviour is desired. ### `bool` marshalling diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs index a148e943fb2312..1468df8072407e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs @@ -13,15 +13,15 @@ namespace System.Runtime.InteropServices Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer)] public unsafe ref struct AnsiStringMarshaller { - private byte* allocated; - private Span span; + private byte* _allocated; + private readonly Span _span; /// /// Initializes a new instance of the . /// /// The string to marshal. public AnsiStringMarshaller(string str) - : this(str, default(Span)) + : this(str, default) { } /// @@ -30,26 +30,30 @@ public AnsiStringMarshaller(string str) /// The string to marshal. /// Buffer that may be used for marshalling. /// + /// The must not be movable - that is, it should not be + /// on the managed heap or it should be pinned. /// /// public AnsiStringMarshaller(string str, Span buffer) { - allocated = null; - span = default; + _allocated = null; if (str is null) + { + _span = default; return; + } - // +1 for null terminator - // + 1 for the null character from the user. + 1 for the null character we put in. - int maxLength = (str.Length + 1) * Marshal.SystemMaxDBCSCharSize + 1; - if (buffer.Length >= maxLength) + // + 1 for null terminator + int maxByteCount = (str.Length + 1) * Marshal.SystemMaxDBCSCharSize + 1; + if (buffer.Length >= maxByteCount) { - int length = Marshal.StringToAnsiString(str, (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(buffer)), buffer.Length); - span = buffer.Slice(0, length); + Marshal.StringToAnsiString(str, (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(buffer)), buffer.Length); + _span = buffer; } else { - allocated = (byte*)Marshal.StringToCoTaskMemAnsi(str); + _allocated = (byte*)Marshal.StringToCoTaskMemAnsi(str); + _span = default; } } @@ -59,7 +63,7 @@ public AnsiStringMarshaller(string str, Span buffer) /// /// /// - public byte* ToNativeValue() => allocated != null ? allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + public byte* ToNativeValue() => _allocated != null ? _allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(_span)); /// /// Sets the native value representing the string. @@ -68,7 +72,7 @@ public AnsiStringMarshaller(string str, Span buffer) /// /// /// - public void FromNativeValue(byte* value) => allocated = value; + public void FromNativeValue(byte* value) => _allocated = value; /// /// Returns the managed string. @@ -76,7 +80,7 @@ public AnsiStringMarshaller(string str, Span buffer) /// /// /// - public string? ToManaged() => allocated == null ? null : new string((sbyte*)allocated); + public string? ToManaged() => _allocated == null ? null : new string((sbyte*)_allocated); /// /// Frees native resources. @@ -84,6 +88,10 @@ public AnsiStringMarshaller(string str, Span buffer) /// /// /// - public void FreeNative() => Marshal.FreeCoTaskMem((IntPtr)allocated); + public void FreeNative() + { + if (_allocated != null) + Marshal.FreeCoTaskMem((IntPtr)_allocated); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs index df0319fbf0e3b9..abeb25a90b0645 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs @@ -13,16 +13,16 @@ namespace System.Runtime.InteropServices Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer)] public unsafe ref struct Utf16StringMarshaller { - private ushort* allocated; - private Span span; - private bool isNullString; + private ushort* _allocated; + private readonly Span _span; + private bool _isNullString; /// /// Initializes a new instance of the . /// /// The string to marshal. public Utf16StringMarshaller(string str) - : this(str, default(Span)) + : this(str, default) { } @@ -32,29 +32,31 @@ public Utf16StringMarshaller(string str) /// The string to marshal. /// Buffer that may be used for marshalling. /// + /// The must not be movable - that is, it should not be + /// on the managed heap or it should be pinned. /// /// public Utf16StringMarshaller(string str, Span buffer) { - isNullString = false; - span = default; + _isNullString = false; + _allocated = null; if (str is null) { - allocated = null; - isNullString = true; + _isNullString = true; + _span = default; } else if ((str.Length + 1) < buffer.Length) { - span = buffer; - str.AsSpan().CopyTo(MemoryMarshal.Cast(buffer)); + _span = buffer; + str.CopyTo(MemoryMarshal.Cast(buffer)); // Supplied memory is in an undefined state so ensure // there is a trailing null in the buffer. - span[str.Length] = '\0'; - allocated = null; + _span[str.Length] = '\0'; } else { - allocated = (ushort*)Marshal.StringToCoTaskMemUni(str); + _allocated = (ushort*)Marshal.StringToCoTaskMemUni(str); + _span = default; } } @@ -63,10 +65,10 @@ public Utf16StringMarshaller(string str, Span buffer) /// public ref ushort GetPinnableReference() { - if (allocated != null) - return ref Unsafe.AsRef(allocated); + if (_allocated != null) + return ref Unsafe.AsRef(_allocated); - return ref span.GetPinnableReference(); + return ref _span.GetPinnableReference(); } /// @@ -75,7 +77,7 @@ public ref ushort GetPinnableReference() /// /// /// - public ushort* ToNativeValue() => allocated != null ? allocated : (ushort*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + public ushort* ToNativeValue() => _allocated != null ? _allocated : (ushort*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(_span)); /// /// Sets the native value representing the string. @@ -86,8 +88,8 @@ public ref ushort GetPinnableReference() /// public void FromNativeValue(ushort* value) { - allocated = value; - isNullString = value == null; + _allocated = value; + _isNullString = value == null; } /// @@ -98,13 +100,13 @@ public void FromNativeValue(ushort* value) /// public string? ToManaged() { - if (isNullString) + if (_isNullString) return null; - if (allocated != null) - return new string((char*)allocated); + if (_allocated != null) + return new string((char*)_allocated); - return MemoryMarshal.Cast(span).ToString(); + return MemoryMarshal.Cast(_span).ToString(); } /// @@ -115,8 +117,8 @@ public void FromNativeValue(ushort* value) /// public void FreeNative() { - if (allocated != null) - Marshal.FreeCoTaskMem((IntPtr)allocated); + if (_allocated != null) + Marshal.FreeCoTaskMem((IntPtr)_allocated); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs index 1585b1d1950a59..edd54cc538c8b6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs @@ -14,23 +14,15 @@ namespace System.Runtime.InteropServices Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer)] public unsafe ref struct Utf8StringMarshaller { - private byte* allocated; - private Span span; - - // Conversion from a 2-byte 'char' in UTF-16 to bytes in UTF-8 has a maximum of 3 bytes per 'char' - // Two bytes ('char') in UTF-16 can be either: - // - Code point in the Basic Multilingual Plane: all 16 bits are that of the code point - // - Part of a pair for a code point in the Supplementary Planes: 10 bits are that of the code point - // In UTF-8, 3 bytes are need to represent the code point in first and 4 bytes in the second. Thus, the - // maximum number of bytes per 'char' is 3. - private const int MaxByteCountPerChar = 3; + private byte* _allocated; + private readonly Span _span; /// /// Initializes a new instance of the . /// /// The string to marshal. public Utf8StringMarshaller(string str) - : this(str, default(Span)) + : this(str, default) { } /// @@ -39,27 +31,37 @@ public Utf8StringMarshaller(string str) /// The string to marshal. /// Buffer that may be used for marshalling. /// + /// The must not be movable - that is, it should not be + /// on the managed heap or it should be pinned. /// /// public Utf8StringMarshaller(string str, Span buffer) { - allocated = null; - span = default; + _allocated = null; if (str is null) + { + _span = default; return; + } - // + 1 for number of characters in case left over high surrogate is ? - // * (3 for UTF-8) - // +1 for null terminator - if (buffer.Length >= (str.Length + 1) * MaxByteCountPerChar + 1) + // + 1 for null terminator + int maxByteCount = Encoding.UTF8.GetMaxByteCount(str.Length) + 1; + if (buffer.Length >= maxByteCount) { int byteCount = Encoding.UTF8.GetBytes(str, buffer); buffer[byteCount] = 0; // null-terminate - span = buffer; + _span = buffer; } else { - allocated = (byte*)Marshal.StringToCoTaskMemUTF8(str); + _allocated = (byte*)Marshal.AllocCoTaskMem(maxByteCount); + int byteCount; + fixed (char* ptr = str) + { + byteCount = Encoding.UTF8.GetBytes(ptr, str.Length, _allocated, maxByteCount); + } + _allocated[byteCount] = 0; // null-terminate + _span = default; } } @@ -69,7 +71,7 @@ public Utf8StringMarshaller(string str, Span buffer) /// /// /// - public byte* ToNativeValue() => allocated != null ? allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + public byte* ToNativeValue() => _allocated != null ? _allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(_span)); /// /// Sets the native value representing the string. @@ -78,7 +80,7 @@ public Utf8StringMarshaller(string str, Span buffer) /// /// /// - public void FromNativeValue(byte* value) => allocated = value; + public void FromNativeValue(byte* value) => _allocated = value; /// /// Returns the managed string. @@ -86,7 +88,7 @@ public Utf8StringMarshaller(string str, Span buffer) /// /// /// - public string? ToManaged() => allocated == null ? null : Marshal.PtrToStringUTF8((IntPtr)allocated); + public string? ToManaged() => _allocated == null ? null : Marshal.PtrToStringUTF8((IntPtr)_allocated); /// /// Frees native resources. @@ -96,8 +98,8 @@ public Utf8StringMarshaller(string str, Span buffer) /// public void FreeNative() { - if (allocated != null) - Marshal.FreeCoTaskMem((IntPtr)allocated); + if (_allocated != null) + Marshal.FreeCoTaskMem((IntPtr)_allocated); } } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs index 69acaadd8a2e48..9e8f1203032838 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs @@ -550,7 +550,7 @@ private MarshallingInfo CreateInfoFromMarshalAs( if (elementUnmanagedType != (UnmanagedType)SizeAndParamIndexInfo.UnspecifiedConstSize) { elementMarshallingInfo = elementType.SpecialType == SpecialType.System_String - ? CreateStringMarshallingInfo(elementType, elementUnmanagedType, attrData) + ? CreateStringMarshallingInfo(elementType, elementUnmanagedType) : new MarshalAsInfo(elementUnmanagedType, _defaultInfo.CharEncoding); } else @@ -564,7 +564,7 @@ private MarshallingInfo CreateInfoFromMarshalAs( if (type.SpecialType == SpecialType.System_String) { - return CreateStringMarshallingInfo(type, unmanagedType, attrData); + return CreateStringMarshallingInfo(type, unmanagedType); } return new MarshalAsInfo(unmanagedType, _defaultInfo.CharEncoding); @@ -666,7 +666,11 @@ private MarshallingInfo CreateNativeMarshallingInfoForValue( break; } - if (bufferElementType is null) + // Attribute data may be null when using runtime-provided marshallers by default for certain types (strings, for example) + // without the user explicitly putting an attribute on the type or parameter. The marshallers should have the correct shape + // already in thoses cases, so the diagnostic here is not so interesting. + Debug.Assert(bufferElementType is not null || attrData is not null); + if (bufferElementType is null && attrData is not null) { _diagnostics.ReportInvalidMarshallingAttributeInfo(attrData, nameof(SR.ValueInCallerAllocatedBufferRequiresSpanConstructorMessage), nativeType.ToDisplayString(), type.ToDisplayString()); return NoMarshallingInfo.Instance; @@ -745,7 +749,7 @@ private bool TryCreateTypeBasedMarshallingInfo( if (_defaultInfo.StringMarshallingCustomType is not null) { AttributeData attrData = _contextSymbol is IMethodSymbol - ? _contextSymbol.GetAttributes().First(a => a.AttributeClass.ToDisplayString() == TypeNames.LibraryImportAttribute) + ? _contextSymbol.GetAttributes().FirstOrDefault(a => a.AttributeClass.ToDisplayString() == TypeNames.LibraryImportAttribute) : default; marshallingInfo = CreateNativeMarshallingInfo(type, _defaultInfo.StringMarshallingCustomType, attrData, true, indirectionLevel, parsedCountInfo, useSiteAttributes, inspectedElements, ref maxIndirectionDepthUsed); return true; @@ -753,10 +757,7 @@ private bool TryCreateTypeBasedMarshallingInfo( } else if (type.SpecialType == SpecialType.System_String) { - AttributeData attrData = _contextSymbol is IMethodSymbol - ? _contextSymbol.GetAttributes().First(a => a.AttributeClass.ToDisplayString() == TypeNames.LibraryImportAttribute) - : default; - marshallingInfo = CreateStringMarshallingInfo(type, _defaultInfo.CharEncoding, default); + marshallingInfo = CreateStringMarshallingInfo(type, _defaultInfo.CharEncoding); return true; } @@ -828,8 +829,7 @@ private MarshallingInfo CreateArrayMarshallingInfo( private MarshallingInfo CreateStringMarshallingInfo( ITypeSymbol type, - UnmanagedType unmanagedType, - AttributeData attrData) + UnmanagedType unmanagedType) { CharEncoding charEncoding = unmanagedType switch { @@ -841,13 +841,12 @@ private MarshallingInfo CreateStringMarshallingInfo( if (charEncoding == CharEncoding.Undefined) return new MarshalAsInfo(unmanagedType, _defaultInfo.CharEncoding); - return CreateStringMarshallingInfo(type, charEncoding, attrData); + return CreateStringMarshallingInfo(type, charEncoding); } private MarshallingInfo CreateStringMarshallingInfo( ITypeSymbol type, - CharEncoding charEncoding, - AttributeData attrData) + CharEncoding charEncoding) { string? marshallerName = charEncoding switch { @@ -866,7 +865,7 @@ private MarshallingInfo CreateStringMarshallingInfo( return CreateNativeMarshallingInfoForValue( type, stringMarshaller, - attrData, + default, customTypeMarshallerData.Value, allowPinningManagedType: charEncoding == CharEncoding.Utf16, useDefaultMarshalling: false); diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs index 7c090b302515f5..81560998fde92b 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/StringTests.cs @@ -375,10 +375,11 @@ public void UTF8StringByRef(string value) [Theory] [MemberData(nameof(UnicodeStrings))] - [PlatformSpecific(TestPlatforms.Windows)] public void AnsiStringMarshalledAsExpected(string value) { - int expectedLen = value != null ? GetLengthAnsi(value) : -1; + int expectedLen = value != null + ? OperatingSystem.IsWindows() ? GetLengthAnsi(value) : Encoding.UTF8.GetByteCount(value) + : -1; Assert.Equal(expectedLen, NativeExportsNE.LPStr.ReturnLength(value)); Assert.Equal(expectedLen, NativeExportsNE.LPStr.ReturnLength_IgnoreStringMarshalling(value)); @@ -386,10 +387,9 @@ public void AnsiStringMarshalledAsExpected(string value) [Theory] [MemberData(nameof(UnicodeStrings))] - [PlatformSpecific(TestPlatforms.Windows)] public void AnsiStringReturn(string value) { - string expected = ReverseAnsi(value); + string expected = OperatingSystem.IsWindows() ? ReverseAnsi(value) : ReverseBytes(value, Encoding.UTF8); Assert.Equal(expected, NativeExportsNE.LPStr.Reverse_Return(value)); @@ -400,11 +400,10 @@ public void AnsiStringReturn(string value) [Theory] [MemberData(nameof(UnicodeStrings))] - [PlatformSpecific(TestPlatforms.Windows)] public void AnsiStringByRef(string value) { string refValue = value; - string expected = ReverseAnsi(value); + string expected = OperatingSystem.IsWindows() ? ReverseAnsi(value) : ReverseBytes(value, Encoding.UTF8); NativeExportsNE.LPStr.Reverse_In(in refValue); Assert.Equal(value, refValue); // Should not be updated when using 'in' From d33535b54ef471352c2c250717bd580122087a25 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 11 Apr 2022 20:27:38 -0700 Subject: [PATCH 05/10] Update Utf16StringMarshaller --- .../InteropServices/Utf16StringMarshaller.cs | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs index abeb25a90b0645..b952d16c0fcb38 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs @@ -15,7 +15,6 @@ public unsafe ref struct Utf16StringMarshaller { private ushort* _allocated; private readonly Span _span; - private bool _isNullString; /// /// Initializes a new instance of the . @@ -38,20 +37,19 @@ public Utf16StringMarshaller(string str) /// public Utf16StringMarshaller(string str, Span buffer) { - _isNullString = false; _allocated = null; if (str is null) { - _isNullString = true; _span = default; + return; } - else if ((str.Length + 1) < buffer.Length) + + // + 1 for null terminator + if (buffer.Length >= str.Length + 1) { _span = buffer; str.CopyTo(MemoryMarshal.Cast(buffer)); - // Supplied memory is in an undefined state so ensure - // there is a trailing null in the buffer. - _span[str.Length] = '\0'; + _span[str.Length] = '\0'; // null-terminate } else { @@ -86,11 +84,7 @@ public ref ushort GetPinnableReference() /// /// /// - public void FromNativeValue(ushort* value) - { - _allocated = value; - _isNullString = value == null; - } + public void FromNativeValue(ushort* value) => _allocated = value; /// /// Returns the managed string. @@ -98,16 +92,7 @@ public void FromNativeValue(ushort* value) /// /// /// - public string? ToManaged() - { - if (_isNullString) - return null; - - if (_allocated != null) - return new string((char*)_allocated); - - return MemoryMarshal.Cast(_span).ToString(); - } + public string? ToManaged() => _allocated == null ? null : new string((char*)_allocated); /// /// Frees native resources. From 0b0989f4ccb924d611d5067a0c03ec0329788a44 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 11 Apr 2022 21:18:07 -0700 Subject: [PATCH 06/10] Use forwarder stub if any parameter is missing marshalling support --- .../PInvokeStubCodeGenerator.cs | 5 ++- .../AttributeForwarding.cs | 43 ++----------------- .../CodeSnippets.cs | 9 ++++ .../Compiles.cs | 13 +++++- 4 files changed, 26 insertions(+), 44 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/PInvokeStubCodeGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/PInvokeStubCodeGenerator.cs index ec7a770472ac2b..7089b635941a80 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/PInvokeStubCodeGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/PInvokeStubCodeGenerator.cs @@ -95,8 +95,9 @@ public PInvokeStubCodeGenerator( { BoundGenerator generator = CreateGenerator(argType); - // Check each marshaler if the current target framework is supported or not. - SupportsTargetFramework &= generator.Generator.IsSupported(environment.TargetFramework, environment.TargetFrameworkVersion); + // Check if marshalling info and generator support the current target framework. + SupportsTargetFramework &= argType.MarshallingAttributeInfo is not MissingSupportMarshallingInfo + && generator.Generator.IsSupported(environment.TargetFramework, environment.TargetFrameworkVersion); // Check if generator is either blittable or just a forwarder. noMarshallingNeeded &= generator is { Generator: BlittableMarshaller, TypeInfo: { IsByRef: false } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs index 7edda25746c6ca..29583c57fbbe77 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs @@ -299,6 +299,8 @@ public Native(S s) { } [Fact] public async Task InOutAttributes_Forwarded_To_ForwardedParameter() { + // This code is invalid configuration from the source generator's perspective. + // We just use it as validation for forwarding the In and Out attributes. string source = @" using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -306,7 +308,7 @@ partial class C { [LibraryImportAttribute(""DoesNotExist"")] [return: MarshalAs(UnmanagedType.Bool)] - public static partial bool Method1([In, Out] int[] a); + public static partial bool Method1([In, Out] int a); } " + CodeSnippets.LibraryImportAttributeDeclaration; Compilation origComp = await TestUtils.CreateCompilation(source, TestTargetFramework.Standard); @@ -319,12 +321,6 @@ partial class C INamedTypeSymbol outAttribute = newComp.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_OutAttribute)!; Assert.Collection(targetMethod.Parameters, param => Assert.Collection(param.GetAttributes(), - attr => - { - Assert.Equal(marshalAsAttribute, attr.AttributeClass, SymbolEqualityComparer.Default); - Assert.Equal(UnmanagedType.LPArray, (UnmanagedType)attr.ConstructorArguments[0].Value!); - Assert.Empty(attr.NamedArguments); - }, attr => { Assert.Equal(inAttribute, attr.AttributeClass, SymbolEqualityComparer.Default); @@ -368,39 +364,6 @@ partial class C })); } - [Fact] - public async Task MarshalAsAttribute_Forwarded_To_ForwardedParameter_Array() - { - string source = @" -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -partial class C -{ - [LibraryImportAttribute(""DoesNotExist"")] - [return: MarshalAs(UnmanagedType.Bool)] - public static partial bool Method1([MarshalAs(UnmanagedType.LPArray, SizeConst = 10, SizeParamIndex = 1, ArraySubType = UnmanagedType.I4)] int[] a, int b); -} -" + CodeSnippets.LibraryImportAttributeDeclaration; - Compilation origComp = await TestUtils.CreateCompilation(source, TestTargetFramework.Standard); - Compilation newComp = TestUtils.RunGenerators(origComp, out _, new Microsoft.Interop.LibraryImportGenerator()); - - IMethodSymbol targetMethod = GetGeneratedPInvokeTargetFromCompilation(newComp); - - INamedTypeSymbol marshalAsAttribute = newComp.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_MarshalAsAttribute)!; - Assert.Collection(targetMethod.Parameters, - param => Assert.Collection(param.GetAttributes(), - attr => - { - Assert.Equal(marshalAsAttribute, attr.AttributeClass, SymbolEqualityComparer.Default); - Assert.Equal(UnmanagedType.LPArray, (UnmanagedType)attr.ConstructorArguments[0].Value!); - var namedArgs = attr.NamedArguments.ToImmutableDictionary(); - Assert.Equal(10, namedArgs["SizeConst"].Value); - Assert.Equal((short)1, namedArgs["SizeParamIndex"].Value); - Assert.Equal(UnmanagedType.I4, (UnmanagedType)namedArgs["ArraySubType"].Value!); - }), - param => Assert.Equal(SpecialType.System_Int32, param.Type.SpecialType)); - } - private static IMethodSymbol GetGeneratedPInvokeTargetFromCompilation(Compilation newComp) { // The last syntax tree is the generated code diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs index 3312098bfe535e..c5a348a4ce8b88 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs @@ -910,6 +910,15 @@ partial class Test public static partial {typeName} Method(); }}"; + public static string BasicReturnAndParameterByValue(string returnType, string parameterType, string preDeclaration = "") => @$" +using System.Runtime.InteropServices; +{preDeclaration} +partial class Test +{{ + [LibraryImport(""DoesNotExist"")] + public static partial {returnType} Method({parameterType} p); +}}"; + public static string CustomStructMarshallingManagedToNativeOnlyOutParameter => BasicParameterWithByRefModifier("out", "S", DisableRuntimeMarshalling) + @" [NativeMarshalling(typeof(Native))] [StructLayout(LayoutKind.Sequential)] diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs index 4a03e16f48fbeb..9c39f7e6e4a19a 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs @@ -284,7 +284,7 @@ public static IEnumerable CodeSnippetsToValidateFallbackForwarder() yield return new object[] { code, TestTargetFramework.Framework, false }; } - // Confirm that all unsupported target frameworks fallback to a forwarder. + // Confirm that all unsupported target frameworks fall back to a forwarder. { string code = CodeSnippets.BasicParametersAndModifiers(CodeSnippets.LibraryImportAttributeDeclaration); yield return new object[] { code, TestTargetFramework.Net5, true }; @@ -293,7 +293,7 @@ public static IEnumerable CodeSnippetsToValidateFallbackForwarder() yield return new object[] { code, TestTargetFramework.Framework, true }; } - // Confirm that all unsupported target frameworks fallback to a forwarder. + // Confirm that all unsupported target frameworks fall back to a forwarder. { string code = CodeSnippets.BasicParametersAndModifiersWithStringMarshalling(StringMarshalling.Utf16, CodeSnippets.LibraryImportAttributeDeclaration); yield return new object[] { code, TestTargetFramework.Net5, true }; @@ -301,6 +301,15 @@ public static IEnumerable CodeSnippetsToValidateFallbackForwarder() yield return new object[] { code, TestTargetFramework.Standard, true }; yield return new object[] { code, TestTargetFramework.Framework, true }; } + + // Confirm that if support is missing for any type (like arrays), we fall back to a forwarder even if other types are supported. + { + string code = CodeSnippets.BasicReturnAndParameterByValue("System.Runtime.InteropServices.SafeHandle", "int[]", CodeSnippets.LibraryImportAttributeDeclaration); + yield return new object[] { code, TestTargetFramework.Net5, true }; + yield return new object[] { code, TestTargetFramework.Core, true }; + yield return new object[] { code, TestTargetFramework.Standard, true }; + yield return new object[] { code, TestTargetFramework.Framework, true }; + } } [Theory] From da8a8fac722018152d4652610bf3fb3e2097bd73 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 11 Apr 2022 22:37:14 -0700 Subject: [PATCH 07/10] Fix forwarding --- .../src/Interop/Unix/System.IO.Ports.Native/Interop.Serial.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.IO.Ports.Native/Interop.Serial.cs b/src/libraries/Common/src/Interop/Unix/System.IO.Ports.Native/Interop.Serial.cs index 8f79130cf7a99b..96e7c106716aa0 100644 --- a/src/libraries/Common/src/Interop/Unix/System.IO.Ports.Native/Interop.Serial.cs +++ b/src/libraries/Common/src/Interop/Unix/System.IO.Ports.Native/Interop.Serial.cs @@ -10,8 +10,8 @@ internal static partial class Interop { internal static partial class Serial { - [LibraryImport(Libraries.IOPortsNative, EntryPoint = "SystemIoPortsNative_SerialPortOpen", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] - internal static partial SafeSerialDeviceHandle SerialPortOpen(string name); + [LibraryImport(Libraries.IOPortsNative, EntryPoint = "SystemIoPortsNative_SerialPortOpen", SetLastError = true)] + internal static partial SafeSerialDeviceHandle SerialPortOpen([MarshalAs(UnmanagedType.LPUTF8Str)] string name); [LibraryImport(Libraries.IOPortsNative, EntryPoint = "SystemIoPortsNative_SerialPortClose", SetLastError = true)] internal static partial int SerialPortClose(IntPtr handle); From 583364063e20ba965fee5cb2b476f4b5c2258c38 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 12 Apr 2022 13:47:12 -0700 Subject: [PATCH 08/10] Missed nullable annotations --- .../System/Runtime/InteropServices/AnsiStringMarshaller.cs | 4 ++-- .../System/Runtime/InteropServices/Utf16StringMarshaller.cs | 4 ++-- .../System/Runtime/InteropServices/Utf8StringMarshaller.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs index 1468df8072407e..bda83c8cd40d71 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/AnsiStringMarshaller.cs @@ -20,7 +20,7 @@ public unsafe ref struct AnsiStringMarshaller /// Initializes a new instance of the . /// /// The string to marshal. - public AnsiStringMarshaller(string str) + public AnsiStringMarshaller(string? str) : this(str, default) { } @@ -34,7 +34,7 @@ public AnsiStringMarshaller(string str) /// on the managed heap or it should be pinned. /// /// - public AnsiStringMarshaller(string str, Span buffer) + public AnsiStringMarshaller(string? str, Span buffer) { _allocated = null; if (str is null) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs index b952d16c0fcb38..c722adb2c0e205 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf16StringMarshaller.cs @@ -20,7 +20,7 @@ public unsafe ref struct Utf16StringMarshaller /// Initializes a new instance of the . /// /// The string to marshal. - public Utf16StringMarshaller(string str) + public Utf16StringMarshaller(string? str) : this(str, default) { } @@ -35,7 +35,7 @@ public Utf16StringMarshaller(string str) /// on the managed heap or it should be pinned. /// /// - public Utf16StringMarshaller(string str, Span buffer) + public Utf16StringMarshaller(string? str, Span buffer) { _allocated = null; if (str is null) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs index edd54cc538c8b6..119fb3e2d32210 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Utf8StringMarshaller.cs @@ -21,7 +21,7 @@ public unsafe ref struct Utf8StringMarshaller /// Initializes a new instance of the . /// /// The string to marshal. - public Utf8StringMarshaller(string str) + public Utf8StringMarshaller(string? str) : this(str, default) { } @@ -35,7 +35,7 @@ public Utf8StringMarshaller(string str) /// on the managed heap or it should be pinned. /// /// - public Utf8StringMarshaller(string str, Span buffer) + public Utf8StringMarshaller(string? str, Span buffer) { _allocated = null; if (str is null) From 93376a5bfc423ece6d30715ff05b302b81dea5ca Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 12 Apr 2022 13:50:59 -0700 Subject: [PATCH 09/10] Delete unused code --- ...nditionalStackallocMarshallingGenerator.cs | 255 ------------------ .../Marshalling/MarshallerHelpers.cs | 51 ---- 2 files changed, 306 deletions(-) delete mode 100644 src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ConditionalStackallocMarshallingGenerator.cs diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ConditionalStackallocMarshallingGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ConditionalStackallocMarshallingGenerator.cs deleted file mode 100644 index 27655f3e305494..00000000000000 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ConditionalStackallocMarshallingGenerator.cs +++ /dev/null @@ -1,255 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; - -namespace Microsoft.Interop -{ - public abstract class ConditionalStackallocMarshallingGenerator : IMarshallingGenerator - { - protected static string GetAllocationMarkerIdentifier(TypePositionInfo info, StubCodeContext context) => context.GetAdditionalIdentifier(info, "allocated"); - - private static string GetByteLengthIdentifier(TypePositionInfo info, StubCodeContext context) => context.GetAdditionalIdentifier(info, "bytelen"); - - private static string GetStackAllocIdentifier(TypePositionInfo info, StubCodeContext context) => context.GetAdditionalIdentifier(info, "stackptr"); - - protected static bool UsesConditionalStackAlloc(TypePositionInfo info, StubCodeContext context) - { - return context.SingleFrameSpansNativeContext - && (!info.IsByRef || info.RefKind == RefKind.In) - && !info.IsManagedReturnPosition - && context.AdditionalTemporaryStateLivesAcrossStages; - } - - protected static bool TryGenerateSetupSyntax(TypePositionInfo info, StubCodeContext context, out StatementSyntax statement) - { - statement = EmptyStatement(); - - if (!UsesConditionalStackAlloc(info, context)) - return false; - - string allocationMarkerIdentifier = GetAllocationMarkerIdentifier(info, context); - - // bool = false; - statement = LocalDeclarationStatement( - VariableDeclaration( - PredefinedType(Token(SyntaxKind.BoolKeyword)), - SingletonSeparatedList( - VariableDeclarator(allocationMarkerIdentifier) - .WithInitializer(EqualsValueClause(LiteralExpression(SyntaxKind.FalseLiteralExpression)))))); - return true; - } - - protected IEnumerable GenerateConditionalAllocationSyntax( - TypePositionInfo info, - StubCodeContext context, - int stackallocMaxSize) - { - (_, string nativeIdentifier) = context.GetIdentifiers(info); - - string allocationMarkerIdentifier = GetAllocationMarkerIdentifier(info, context); - string byteLenIdentifier = GetByteLengthIdentifier(info, context); - string stackAllocPtrIdentifier = GetStackAllocIdentifier(info, context); - // = ; - ExpressionStatementSyntax allocationStatement = ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - IdentifierName(nativeIdentifier), - GenerateAllocationExpression(info, context, Identifier(byteLenIdentifier), out bool allocationRequiresByteLength))); - - // int = ; - LocalDeclarationStatementSyntax byteLenAssignment = LocalDeclarationStatement( - VariableDeclaration( - PredefinedType(Token(SyntaxKind.IntKeyword)), - SingletonSeparatedList( - VariableDeclarator(byteLenIdentifier) - .WithInitializer(EqualsValueClause( - GenerateByteLengthCalculationExpression(info, context)))))); - - if (!UsesConditionalStackAlloc(info, context)) - { - List statements = new List(); - if (allocationRequiresByteLength) - { - statements.Add(byteLenAssignment); - } - statements.Add(allocationStatement); - yield return ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, - IdentifierName(nativeIdentifier), - LiteralExpression(SyntaxKind.NullLiteralExpression))); - yield return IfStatement( - GenerateNullCheckExpression(info, context), - Block(statements)); - yield break; - } - - // Code block for stackalloc if number of bytes is below threshold size - BlockSyntax marshalOnStack = Block( - // byte* = stackalloc byte[]; - LocalDeclarationStatement( - VariableDeclaration( - PointerType(PredefinedType(Token(SyntaxKind.ByteKeyword))), - SingletonSeparatedList( - VariableDeclarator(stackAllocPtrIdentifier) - .WithInitializer(EqualsValueClause( - StackAllocArrayCreationExpression( - ArrayType( - PredefinedType(Token(SyntaxKind.ByteKeyword)), - SingletonList( - ArrayRankSpecifier(SingletonSeparatedList( - IdentifierName(byteLenIdentifier))))))))))), - GenerateStackallocOnlyValueMarshalling(info, context, Identifier(byteLenIdentifier), Identifier(stackAllocPtrIdentifier)), - // = ; - ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - IdentifierName(nativeIdentifier), - CastExpression( - AsNativeType(info), - IdentifierName(stackAllocPtrIdentifier))))); - - // if ( > ) - // { - // ; - // = true; - // } - // else - // { - // byte* = stackalloc byte[]; - // ; - // = (); - // } - IfStatementSyntax allocBlock = IfStatement( - BinaryExpression( - SyntaxKind.GreaterThanExpression, - IdentifierName(byteLenIdentifier), - LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(stackallocMaxSize))), - Block( - allocationStatement, - ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - IdentifierName(allocationMarkerIdentifier), - LiteralExpression(SyntaxKind.TrueLiteralExpression)))), - ElseClause(marshalOnStack)); - - yield return IfStatement( - GenerateNullCheckExpression(info, context), - Block(byteLenAssignment, allocBlock)); - } - - protected StatementSyntax GenerateConditionalAllocationFreeSyntax( - TypePositionInfo info, - StubCodeContext context) - { - string allocationMarkerIdentifier = GetAllocationMarkerIdentifier(info, context); - if (!UsesConditionalStackAlloc(info, context)) - { - return ExpressionStatement(GenerateFreeExpression(info, context)); - } - else - { - // if () - // { - // ; - // } - return IfStatement( - IdentifierName(allocationMarkerIdentifier), - Block(ExpressionStatement(GenerateFreeExpression(info, context)))); - } - } - - /// - /// Generate an expression that allocates memory for the native representation of the object. - /// - /// Object to marshal - /// Code generation context - /// An identifier that represents how many bytes must be allocated. - /// If the allocation expression uses , true; otherwise false. - /// An expression that allocates memory for the native representation of the object. - protected abstract ExpressionSyntax GenerateAllocationExpression( - TypePositionInfo info, - StubCodeContext context, - SyntaxToken byteLengthIdentifier, - out bool allocationRequiresByteLength); - - /// - /// Generates an expression that represents the number of bytes that need to be allocated. - /// - /// Object to marshal - /// Code generation context - /// An expression that results in the number of bytes to allocate as a C# int. - protected abstract ExpressionSyntax GenerateByteLengthCalculationExpression( - TypePositionInfo info, - StubCodeContext context); - - /// - /// Generate a statement that is only executed when memory is stack allocated. - /// - /// Object to marshal - /// Code generation context - /// An identifier that represents the number of bytes allocated. - /// An identifier that represents a pointer to the stack allocated memory (of type byte*). - /// A statement that is only executed when memory is stack allocated. - protected abstract StatementSyntax GenerateStackallocOnlyValueMarshalling( - TypePositionInfo info, - StubCodeContext context, - SyntaxToken byteLengthIdentifier, - SyntaxToken stackAllocPtrIdentifier); - - /// - /// Generate code to free native allocated memory used during marshalling. - /// - /// Object to marshal - /// Code generation context - /// An expression that frees allocated memory. - protected abstract ExpressionSyntax GenerateFreeExpression( - TypePositionInfo info, - StubCodeContext context); - - /// - /// Generate code to check if the managed value is not null. - /// - /// Object to marshal - /// Code generation context - /// An expression that checks if the managed value is not null. - protected virtual ExpressionSyntax GenerateNullCheckExpression( - TypePositionInfo info, - StubCodeContext context) - { - return BinaryExpression( - SyntaxKind.NotEqualsExpression, - IdentifierName(context.GetIdentifiers(info).managed), - LiteralExpression(SyntaxKind.NullLiteralExpression)); - } - - /// - public virtual bool IsSupported(TargetFramework target, Version version) - { - return target is TargetFramework.Net && version.Major >= 6; - } - - /// - public abstract TypeSyntax AsNativeType(TypePositionInfo info); - - /// - public abstract SignatureBehavior GetNativeSignatureBehavior(TypePositionInfo info); - - /// - public abstract ValueBoundaryBehavior GetValueBoundaryBehavior(TypePositionInfo info, StubCodeContext context); - - /// - public abstract IEnumerable Generate(TypePositionInfo info, StubCodeContext context); - - /// - public abstract bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context); - - /// - public abstract bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context); - } -} diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs index 993334ac988960..c6479390652a97 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs @@ -12,14 +12,6 @@ namespace Microsoft.Interop { public static class MarshallerHelpers { - public static readonly ExpressionSyntax IsWindows = InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - ParseTypeName("System.OperatingSystem"), - IdentifierName("IsWindows"))); - - public static readonly TypeSyntax InteropServicesMarshalType = ParseTypeName(TypeNames.System_Runtime_InteropServices_Marshal); - public static readonly TypeSyntax SystemIntPtrType = ParseTypeName(TypeNames.System_IntPtr); public static ForStatementSyntax GetForLoop(ExpressionSyntax lengthExpression, string indexerIdentifier) @@ -246,48 +238,5 @@ public static IEnumerable GetDependentElementsOfMarshallingInf } } } - - public static class StringMarshaller - { - public static ExpressionSyntax AllocationExpression(CharEncoding encoding, string managedIdentifier) - { - string methodName = encoding switch - { - CharEncoding.Utf8 => "StringToCoTaskMemUTF8", // Not in .NET Standard 2.0, so we use the hard-coded name - CharEncoding.Utf16 => nameof(System.Runtime.InteropServices.Marshal.StringToCoTaskMemUni), - CharEncoding.Ansi => nameof(System.Runtime.InteropServices.Marshal.StringToCoTaskMemAnsi), - _ => throw new System.ArgumentOutOfRangeException(nameof(encoding)) - }; - - // Marshal.StringToCoTaskMemUTF8() - // or - // Marshal.StringToCoTaskMemUni() - // or - // Marshal.StringToCoTaskMemAnsi() - return InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - InteropServicesMarshalType, - IdentifierName(methodName)), - ArgumentList( - SingletonSeparatedList( - Argument(IdentifierName(managedIdentifier))))); - } - - public static ExpressionSyntax FreeExpression(string nativeIdentifier) - { - // Marshal.FreeCoTaskMem((IntPtr)) - return InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - InteropServicesMarshalType, - IdentifierName(nameof(System.Runtime.InteropServices.Marshal.FreeCoTaskMem))), - ArgumentList(SingletonSeparatedList( - Argument( - CastExpression( - SystemIntPtrType, - IdentifierName(nativeIdentifier)))))); - } - } } } From e90b17b629db1d02b2c78dfc2cfdb0df4941fe03 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 14 Apr 2022 09:21:47 -0700 Subject: [PATCH 10/10] Update src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs Co-authored-by: Aaron Robinson --- .../MarshallingAttributeInfo.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs index 9e8f1203032838..4f38c9e8ec9f0d 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs @@ -90,6 +90,8 @@ public sealed record MarshalAsInfo( UnmanagedType UnmanagedType, CharEncoding CharEncoding) : MarshallingInfoStringSupport(CharEncoding) { + // UnmanagedType.LPUTF8Str is not in netstandard2.0, so we define a constant for the value here. + // See https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedtype internal const UnmanagedType UnmanagedType_LPUTF8Str = (UnmanagedType)0x30; }