Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LibraryImportGenerator] Add/use CustomTypeMarshaller implementations for string marshalling #67635

Merged
merged 11 commits into from
Apr 14, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,17 @@ 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")]
public static partial void Rect_objc_msgSend_stret(out Rect arect, IntPtr basePtr, IntPtr selector);
[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")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\ExceptionServices\HandleProcessCorruptedStateExceptionsAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\GCSettings.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\AllowReversePInvokeCallsAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\AnsiStringMarshaller.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Architecture.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ArrayWithOffset.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\BestFitMappingAttribute.cs" />
Expand Down Expand Up @@ -891,6 +892,8 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedCallersOnlyAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedFunctionPointerAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedType.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Utf16StringMarshaller.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Utf8StringMarshaller.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\VarEnum.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\VariantWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Intrinsics\Scalar.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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
{
/// <summary>
/// Marshaller for ANSI strings
/// </summary>
[CLSCompliant(false)]
[CustomTypeMarshaller(typeof(string), BufferSize = 0x100,
Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer)]
public unsafe ref struct AnsiStringMarshaller
{
private byte* _allocated;
private readonly Span<byte> _span;

/// <summary>
/// Initializes a new instance of the <see cref="AnsiStringMarshaller"/>.
/// </summary>
/// <param name="str">The string to marshal.</param>
public AnsiStringMarshaller(string? str)
: this(str, default)
{ }

/// <summary>
/// Initializes a new instance of the <see cref="AnsiStringMarshaller"/>.
/// </summary>
/// <param name="str">The string to marshal.</param>
/// <param name="buffer">Buffer that may be used for marshalling.</param>
elinor-fung marked this conversation as resolved.
Show resolved Hide resolved
/// <remarks>
/// The <paramref name="buffer"/> must not be movable - that is, it should not be
/// on the managed heap or it should be pinned.
/// <seealso cref="CustomTypeMarshallerFeatures.CallerAllocatedBuffer"/>
/// </remarks>
public AnsiStringMarshaller(string? str, Span<byte> buffer)
{
_allocated = null;
if (str is null)
{
_span = default;
return;
}

// + 1 for null terminator
int maxByteCount = (str.Length + 1) * Marshal.SystemMaxDBCSCharSize + 1;
if (buffer.Length >= maxByteCount)
{
Marshal.StringToAnsiString(str, (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(buffer)), buffer.Length);
_span = buffer;
}
else
{
_allocated = (byte*)Marshal.StringToCoTaskMemAnsi(str);
_span = default;
}
}

/// <summary>
/// Returns the native value representing the string.
/// </summary>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerFeatures.TwoStageMarshalling"/>
/// </remarks>
public byte* ToNativeValue() => _allocated != null ? _allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(_span));

/// <summary>
/// Sets the native value representing the string.
/// </summary>
/// <param name="value">The native value.</param>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerFeatures.TwoStageMarshalling"/>
/// </remarks>
public void FromNativeValue(byte* value) => _allocated = value;

/// <summary>
/// Returns the managed string.
/// </summary>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerDirection.Out"/>
/// </remarks>
public string? ToManaged() => _allocated == null ? null : new string((sbyte*)_allocated);

/// <summary>
/// Frees native resources.
/// </summary>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerFeatures.UnmanagedResources"/>
/// </remarks>
public void FreeNative()
{
if (_allocated != null)
Marshal.FreeCoTaskMem((IntPtr)_allocated);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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
{
/// <summary>
/// Marshaller for UTF-16 strings
/// </summary>
[CLSCompliant(false)]
[CustomTypeMarshaller(typeof(string), BufferSize = 0x100,
Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer)]
public unsafe ref struct Utf16StringMarshaller
{
private ushort* _allocated;
private readonly Span<ushort> _span;

/// <summary>
/// Initializes a new instance of the <see cref="Utf16StringMarshaller"/>.
/// </summary>
/// <param name="str">The string to marshal.</param>
public Utf16StringMarshaller(string? str)
: this(str, default)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="Utf16StringMarshaller"/>.
/// </summary>
/// <param name="str">The string to marshal.</param>
/// <param name="buffer">Buffer that may be used for marshalling.</param>
elinor-fung marked this conversation as resolved.
Show resolved Hide resolved
/// <remarks>
/// The <paramref name="buffer"/> must not be movable - that is, it should not be
/// on the managed heap or it should be pinned.
/// <seealso cref="CustomTypeMarshallerFeatures.CallerAllocatedBuffer"/>
/// </remarks>
public Utf16StringMarshaller(string? str, Span<ushort> buffer)
{
_allocated = null;
if (str is null)
{
_span = default;
return;
}

// + 1 for null terminator
if (buffer.Length >= str.Length + 1)
{
_span = buffer;
str.CopyTo(MemoryMarshal.Cast<ushort, char>(buffer));
_span[str.Length] = '\0'; // null-terminate
}
else
{
_allocated = (ushort*)Marshal.StringToCoTaskMemUni(str);
_span = default;
}
}

/// <summary>
/// Returns a reference to the marshalled string.
/// </summary>
public ref ushort GetPinnableReference()
{
if (_allocated != null)
return ref Unsafe.AsRef<ushort>(_allocated);

return ref _span.GetPinnableReference();
}

/// <summary>
/// Returns the native value representing the string.
/// </summary>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerFeatures.TwoStageMarshalling"/>
/// </remarks>
public ushort* ToNativeValue() => _allocated != null ? _allocated : (ushort*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(_span));

/// <summary>
/// Sets the native value representing the string.
/// </summary>
/// <param name="value">The native value.</param>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerFeatures.TwoStageMarshalling"/>
/// </remarks>
public void FromNativeValue(ushort* value) => _allocated = value;

/// <summary>
/// Returns the managed string.
/// </summary>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerDirection.Out"/>
/// </remarks>
public string? ToManaged() => _allocated == null ? null : new string((char*)_allocated);

/// <summary>
/// Frees native resources.
/// </summary>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerFeatures.UnmanagedResources"/>
/// </remarks>
public void FreeNative()
{
if (_allocated != null)
Marshal.FreeCoTaskMem((IntPtr)_allocated);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// 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
{
/// <summary>
/// Marshaller for UTF-8 strings
/// </summary>
[CLSCompliant(false)]
[CustomTypeMarshaller(typeof(string), BufferSize = 0x100,
Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer)]
public unsafe ref struct Utf8StringMarshaller
{
private byte* _allocated;
private readonly Span<byte> _span;

/// <summary>
/// Initializes a new instance of the <see cref="Utf8StringMarshaller"/>.
/// </summary>
/// <param name="str">The string to marshal.</param>
public Utf8StringMarshaller(string? str)
: this(str, default)
{ }

/// <summary>
/// Initializes a new instance of the <see cref="Utf8StringMarshaller"/>.
/// </summary>
/// <param name="str">The string to marshal.</param>
/// <param name="buffer">Buffer that may be used for marshalling.</param>
/// <remarks>
/// The <paramref name="buffer"/> must not be movable - that is, it should not be
/// on the managed heap or it should be pinned.
/// <seealso cref="CustomTypeMarshallerFeatures.CallerAllocatedBuffer"/>
/// </remarks>
public Utf8StringMarshaller(string? str, Span<byte> buffer)
{
_allocated = null;
if (str is null)
{
_span = default;
return;
}

// + 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;
}
else
{
_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;
}
}

/// <summary>
/// Returns the native value representing the string.
/// </summary>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerFeatures.TwoStageMarshalling"/>
/// </remarks>
public byte* ToNativeValue() => _allocated != null ? _allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(_span));

/// <summary>
/// Sets the native value representing the string.
/// </summary>
/// <param name="value">The native value.</param>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerFeatures.TwoStageMarshalling"/>
/// </remarks>
public void FromNativeValue(byte* value) => _allocated = value;

/// <summary>
/// Returns the managed string.
/// </summary>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerDirection.Out"/>
/// </remarks>
public string? ToManaged() => _allocated == null ? null : Marshal.PtrToStringUTF8((IntPtr)_allocated);

/// <summary>
/// Frees native resources.
/// </summary>
/// <remarks>
/// <seealso cref="CustomTypeMarshallerFeatures.UnmanagedResources"/>
/// </remarks>
public void FreeNative()
{
if (_allocated != null)
Marshal.FreeCoTaskMem((IntPtr)_allocated);
}
}
}
Loading