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

Add new SafeHandleMarshaller type to provide out-of-generator marshalling support. #85419

Merged
merged 8 commits into from
May 3, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Marshalling\NativeMarshallingAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Marshalling\PointerArrayMarshaller.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Marshalling\ReadOnlySpanMarshaller.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Marshalling\SafeHandleMarshaller.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Marshalling\SpanMarshaller.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Marshalling\Utf16StringMarshaller.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Marshalling\Utf8StringMarshaller.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace System.Runtime.InteropServices.Marshalling
{
/// <summary>
/// A marshaller for <see cref="SafeHandle"/>-derived types that marshals the handle following the lifetime rules for <see cref="SafeHandle"/>s.
/// </summary>
/// <typeparam name="T">The <see cref="SafeHandle"/>-derived type.</typeparam>
[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder), MarshalMode.ManagedToUnmanagedIn, typeof(SafeHandleMarshaller<>.ManagedToUnmanagedIn))]
[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder), MarshalMode.ManagedToUnmanagedRef, typeof(SafeHandleMarshaller<>.ManagedToUnmanagedRef))]
[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder), MarshalMode.ManagedToUnmanagedOut, typeof(SafeHandleMarshaller<>.ManagedToUnmanagedOut))]
public static class SafeHandleMarshaller<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> where T : SafeHandle
{
/// <summary>
/// Custom marshaller to marshal a <see cref="SafeHandle"/> as its underlying handle value.
/// </summary>
public struct ManagedToUnmanagedIn
{
private bool _addRefd;
private T? _handle;

/// <summary>
/// Initializes the marshaller from a managed handle.
/// </summary>
/// <param name="handle">The managed handle.</param>
public void FromManaged(T handle)
{
_handle = handle;
handle.DangerousAddRef(ref _addRefd);
}

/// <summary>
/// Get the unmanaged handle.
/// </summary>
/// <returns>The unmanaged handle.</returns>
public IntPtr ToUnmanaged() => _handle!.DangerousGetHandle();

/// <summary>
/// Release any references keeping the managed handle alive.
/// </summary>
public void Free()
{
if (_addRefd)
{
_handle!.DangerousRelease();
}
}
}

jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Custom marshaller to marshal a <see cref="SafeHandle"/> as its underlying handle value.
/// </summary>
public struct ManagedToUnmanagedRef
{
private bool _addRefd;
private bool _callInvoked;
private T? _handle;
private IntPtr _originalHandleValue;
private T _newHandle;
private T? _handleToReturn;

/// <summary>
/// Create the marshaller in a default state.
/// </summary>
public ManagedToUnmanagedRef()
{
_addRefd = false;
_callInvoked = false;
_newHandle = (T)Activator.CreateInstance(typeof(T), nonPublic: true)!;
}

/// <summary>
/// Initialize the marshaller from a managed handle.
/// </summary>
/// <param name="handle">The managed handle</param>
public void FromManaged(T handle)
{
_handle = handle;
handle.DangerousAddRef(ref _addRefd);
_originalHandleValue = handle.DangerousGetHandle();
}

/// <summary>
/// Retrieve the unmanaged handle.
/// </summary>
/// <returns>The unmanaged handle</returns>
public IntPtr ToUnmanaged() => _originalHandleValue;

/// <summary>
/// Initialize the marshaller from an unmanaged handle.
/// </summary>
/// <param name="value">The unmanaged handle.</param>
public void FromUnmanaged(IntPtr value)
{
if (value == _originalHandleValue)
{
_handleToReturn = _handle;
}
else
{
Marshal.InitHandle(_newHandle, value);
_handleToReturn = _newHandle;
}
}

/// <summary>
/// Notify the marshaller that the native call has been invoked.
/// </summary>
public void OnInvoked()
{
_callInvoked = true;
}

/// <summary>
/// Retrieve the managed handle from the marshaller.
/// </summary>
/// <returns>The managed handle.</returns>
public T ToManagedFinally() => _handleToReturn!;

/// <summary>
/// Free any resources and reference counts owned by the marshaller.
/// </summary>
public void Free()
{
if (_addRefd)
{
_handle!.DangerousRelease();
}

// If we never invoked the call, then we aren't going to use the
// new handle. Dispose it now to avoid clogging up the finalizer queue
// unnecessarily.
if (!_callInvoked)
{
_newHandle.Dispose();
}
}
}

/// <summary>
/// Custom marshaller to marshal a <see cref="SafeHandle"/> as its underlying handle value.
/// </summary>
public struct ManagedToUnmanagedOut
{
private bool _callInvoked;
private T _newHandle;

/// <summary>
/// Create the marshaller in a default state.
/// </summary>
public ManagedToUnmanagedOut()
{
_callInvoked = false;
_newHandle = (T)Activator.CreateInstance(typeof(T), nonPublic: true)!;
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
/// Initialize the marshaller from an unmanaged handle.
/// </summary>
/// <param name="value">The unmanaged handle.</param>
public void FromUnmanaged(IntPtr value)
{
Marshal.InitHandle(_newHandle, value);
}

/// <summary>
/// Notify the marshaller that the native call has been invoked.
/// </summary>
public void OnInvoked()
{
_callInvoked = true;
}

/// <summary>
/// Retrieve the managed handle from the marshaller.
/// </summary>
/// <returns>The managed handle.</returns>
public T ToManaged() => _newHandle;

/// <summary>
/// Free any resources and reference counts owned by the marshaller.
/// </summary>
public void Free()
{
// If we never invoked the call, then we aren't going to use the
// new handle. Dispose it now to avoid clogging up the finalizer queue
// unnecessarily.
if (!_callInvoked)
{
_newHandle!.Dispose();
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</ItemGroup>

<ItemGroup>
<Compile Include="$(CommonPath)Roslyn\GetBestTypeByMetadataName.cs" Link="Common\Roslyn\GetBestTypeByMetadataName.cs" />
<Compile Include="../../tests/Common/MarshalDirection.cs" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.DotnetRuntime.Extensions;

namespace Microsoft.Interop
{
Expand All @@ -19,11 +21,13 @@ public sealed record SafeHandleMarshallingInfo(bool AccessibleDefaultConstructor
public sealed class SafeHandleMarshallingInfoProvider : ITypeBasedMarshallingInfoProvider
{
private readonly Compilation _compilation;
private readonly INamedTypeSymbol _safeHandleMarshallerType;
private readonly ITypeSymbol _containingScope;

public SafeHandleMarshallingInfoProvider(Compilation compilation, ITypeSymbol containingScope)
{
_compilation = compilation;
_safeHandleMarshallerType = compilation.GetBestTypeByMetadataName(TypeNames.System_Runtime_InteropServices_Marshalling_SafeHandleMarshaller_Metadata);
_containingScope = containingScope;
}

Expand All @@ -47,18 +51,50 @@ public bool CanProvideMarshallingInfoForType(ITypeSymbol type)

public MarshallingInfo GetMarshallingInfo(ITypeSymbol type, int indirectionDepth, UseSiteAttributeProvider useSiteAttributes, GetMarshallingInfoCallback marshallingInfoCallback)
{
bool hasDefaultConstructor = false;
bool hasAccessibleDefaultConstructor = false;
if (type is INamedTypeSymbol named && !named.IsAbstract && named.InstanceConstructors.Length > 0)
{
foreach (IMethodSymbol ctor in named.InstanceConstructors)
{
if (ctor.Parameters.Length == 0)
{
hasDefaultConstructor = true;
hasAccessibleDefaultConstructor = _compilation.IsSymbolAccessibleWithin(ctor, _containingScope);
break;
}
}
}

if (_safeHandleMarshallerType is not null)
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
{

AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
INamedTypeSymbol entryPointType = _safeHandleMarshallerType.Construct(type);
if (!ManualTypeMarshallingHelper.TryGetValueMarshallersFromEntryType(
entryPointType,
type,
_compilation,
out CustomTypeMarshallers? marshallers))
{
return NoMarshallingInfo.Instance;
}

// If the SafeHandle-derived type doesn't have a default constructor or is abstract,
// we only support managed-to-unmanaged marshalling
if (!hasDefaultConstructor || type.IsAbstract)
{
marshallers = marshallers.Value with
{
Modes = ImmutableDictionary<MarshalMode, CustomTypeMarshallerData>.Empty
.Add(
MarshalMode.ManagedToUnmanagedIn,
marshallers.Value.GetModeOrDefault(MarshalMode.ManagedToUnmanagedIn))
};
}

return new NativeMarshallingAttributeInfo(ManagedTypeInfo.CreateTypeInfoForTypeSymbol(entryPointType), marshallers.Value);
}

return new SafeHandleMarshallingInfo(hasAccessibleDefaultConstructor, type.IsAbstract);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,7 @@ public static string MarshalEx(InteropGenerationOptions options)
public const string GeneratedComClassAttribute = "System.Runtime.InteropServices.Marshalling.GeneratedComClassAttribute";
public const string ComExposedClassAttribute = "System.Runtime.InteropServices.Marshalling.ComExposedClassAttribute";
public const string IComExposedClass = "System.Runtime.InteropServices.Marshalling.IComExposedClass";

public const string System_Runtime_InteropServices_Marshalling_SafeHandleMarshaller_Metadata = "System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller`1";
}
}
59 changes: 59 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13486,6 +13486,65 @@ public static class UnmanagedToManagedOut
public static System.Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) { throw null; }
}
}

[System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute(typeof(System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute.GenericPlaceholder),
System.Runtime.InteropServices.Marshalling.MarshalMode.ManagedToUnmanagedIn,
typeof(System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<>.ManagedToUnmanagedIn))]
[System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute(typeof(System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute.GenericPlaceholder),
System.Runtime.InteropServices.Marshalling.MarshalMode.ManagedToUnmanagedRef,
typeof(System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<>.ManagedToUnmanagedRef))]
[System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute(typeof(System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute.GenericPlaceholder),
System.Runtime.InteropServices.Marshalling.MarshalMode.ManagedToUnmanagedOut,
typeof(System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller<>.ManagedToUnmanagedOut))]
public static class SafeHandleMarshaller<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> where T : SafeHandle
{
public struct ManagedToUnmanagedIn
{
private int _dummyPrimitive;
private T _handle;
public void FromManaged(T handle) { }

public nint ToUnmanaged() { throw null; }

public void Free() { }
}

public struct ManagedToUnmanagedRef
{
private int _dummyPrimitive;
private T _handle;

public ManagedToUnmanagedRef() { }

public void FromManaged(T handle) { }

public nint ToUnmanaged() { throw null; }

public void FromUnmanaged(nint value) { }

public void OnInvoked() { }

public T ToManagedFinally() { throw null; }

public void Free() { }
}

public struct ManagedToUnmanagedOut
{
private int _dummyPrimitive;
private T _newHandle;
public ManagedToUnmanagedOut() { }

public void FromUnmanaged(nint value) { }

public void OnInvoked() { }

public T ToManaged() { throw null; }

public void Free() { }
}
}

[System.CLSCompliant(false)]
[System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute(typeof(System.Span<>),
System.Runtime.InteropServices.Marshalling.MarshalMode.Default,
Expand Down