File: Marshalling\StatefulMarshallingStrategy.cs
Web Access
Project: Microsoft.Interop.SourceGeneration.csproj (Microsoft.Interop.SourceGeneration)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Interop
{
    internal sealed class StatefulValueMarshalling(TypePositionInfo info, StubCodeContext stubContext, ManagedTypeInfo marshallerType, ManagedTypeInfo unmanagedType, MarshallerShape shape) : ICustomTypeMarshallingStrategy
    {
        internal const string MarshallerIdentifier = "marshaller";

        public ManagedTypeInfo NativeType => unmanagedType;
        public bool UsesNativeIdentifier => true;
        public TypePositionInfo TypeInfo => info;
        public StubCodeContext CodeContext => stubContext;

        public void GenerateCleanupCallerAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (MarshallerHelpers.GetCleanupStage(info, stubContext) is StubIdentifierContext.Stage.CleanupCallerAllocated
                && shape.HasFlag(MarshallerShape.Free))
            {
                writer.WriteLine($"{GetMarshallerIdentifier(info, context)}.{ShapeMemberNames.Free}();");
            }
        }

        public void GenerateCleanupCalleeAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (MarshallerHelpers.GetCleanupStage(info, stubContext) is StubIdentifierContext.Stage.CleanupCalleeAllocated
                && shape.HasFlag(MarshallerShape.Free))
            {
                writer.WriteLine($"{GetMarshallerIdentifier(info, context)}.{ShapeMemberNames.Free}();");
            }
        }

        public void GenerateGuaranteedUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (shape.HasFlag(MarshallerShape.GuaranteedUnmarshal))
            {
                writer.WriteLine($"{context.GetIdentifiers(info).managed} = {GetMarshallerIdentifier(info, context)}.{ShapeMemberNames.Value.Stateful.ToManagedFinally}();");
            }
        }

        public void GenerateMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (shape.HasFlag(MarshallerShape.ToUnmanaged))
            {
                writer.WriteLine($"{GetMarshallerIdentifier(info, context)}.{ShapeMemberNames.Value.Stateful.FromManaged}({context.GetIdentifiers(info).managed});");
            }
        }

        public void GeneratePinnedMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (shape.HasFlag(MarshallerShape.ToUnmanaged) || shape.HasFlag(MarshallerShape.CallerAllocatedBuffer))
            {
                writer.WriteLine($"{context.GetIdentifiers(info).native} = {GetMarshallerIdentifier(info, context)}.{ShapeMemberNames.Value.Stateful.ToUnmanaged}();");
            }
        }

        public void GenerateUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (shape.HasFlag(MarshallerShape.ToManaged))
            {
                writer.WriteLine($"{context.GetIdentifiers(info).managed} = {GetMarshallerIdentifier(info, context)}.{ShapeMemberNames.Value.Stateful.ToManaged}();");
            }
        }

        public void GenerateUnmarshalCaptureStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (shape.HasFlag(MarshallerShape.ToManaged) || shape.HasFlag(MarshallerShape.GuaranteedUnmarshal))
            {
                writer.WriteLine($"{GetMarshallerIdentifier(info, context)}.{ShapeMemberNames.Value.Stateful.FromUnmanaged}({context.GetIdentifiers(info).native});");
            }
        }

        public void GenerateSetupStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            // Scoped marshaller locals prevent caller-allocated buffers from escaping through ref-like state.
            string scoped = marshallerType is ValueTypeInfo { IsByRefLike: true } ? "scoped " : string.Empty;
            writer.WriteLine($"{scoped}{marshallerType.FullTypeName} {GetMarshallerIdentifier(info, context)} = new();");
        }

        public void GeneratePinStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (shape.HasFlag(MarshallerShape.StatefulPinnableReference))
            {
                string unusedIdentifier = context.GetAdditionalIdentifier(info, "unused");
                writer.WriteLine($"fixed (void* {unusedIdentifier} = {GetMarshallerIdentifier(info, context)})");
            }
        }

        public void GenerateNotifyForSuccessfulInvokeStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (shape.HasFlag(MarshallerShape.OnInvoked))
            {
                writer.WriteLine($"{GetMarshallerIdentifier(info, context)}.{ShapeMemberNames.Value.Stateful.OnInvoked}();");
            }
        }

        public static string GetMarshallerIdentifier(TypePositionInfo info, StubIdentifierContext context)
        {
            return context.GetAdditionalIdentifier(info, MarshallerIdentifier);
        }
    }

    /// <summary>
    /// Marshaller that enables support for a stackalloc constructor variant on a native type.
    /// </summary>
    internal sealed class StatefulCallerAllocatedBufferMarshalling(ICustomTypeMarshallingStrategy innerMarshaller, string marshallerType, string bufferElementType) : ICustomTypeMarshallingStrategy
    {
        public ManagedTypeInfo NativeType => innerMarshaller.NativeType;
        public bool UsesNativeIdentifier => innerMarshaller.UsesNativeIdentifier;
        public TypePositionInfo TypeInfo => innerMarshaller.TypeInfo;
        public StubCodeContext CodeContext => innerMarshaller.CodeContext;

        public void GenerateCleanupCallerAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateCleanupCallerAllocatedResourcesStatements(writer, context);
        public void GenerateCleanupCalleeAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateCleanupCalleeAllocatedResourcesStatements(writer, context);

        public void GenerateMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (MarshallerHelpers.CanUseCallerAllocatedBuffer(TypeInfo, CodeContext))
            {
                string managedIdentifier = context.GetIdentifiers(TypeInfo).managed;
                string marshaller = StatefulValueMarshalling.GetMarshallerIdentifier(TypeInfo, context);
                writer.WriteLine($"{marshaller}.{ShapeMemberNames.Value.Stateful.FromManaged}({managedIdentifier}, stackalloc {bufferElementType}[{marshallerType}.{ShapeMemberNames.BufferSize}]);");
                return;
            }

            innerMarshaller.GenerateMarshalStatements(writer, context);
        }

        public void GeneratePinnedMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GeneratePinnedMarshalStatements(writer, context);
        public void GeneratePinStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GeneratePinStatements(writer, context);
        public void GenerateSetupStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateSetupStatements(writer, context);
        public void GenerateUnmarshalCaptureStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateUnmarshalCaptureStatements(writer, context);
        public void GenerateUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateUnmarshalStatements(writer, context);
        public void GenerateGuaranteedUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateGuaranteedUnmarshalStatements(writer, context);
        public void GenerateNotifyForSuccessfulInvokeStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateNotifyForSuccessfulInvokeStatements(writer, context);
    }

    internal sealed class StatefulLinearCollectionSource(TypePositionInfo info, StubCodeContext codeContext) : IElementsMarshallingCollectionSource
    {
        public TypePositionInfo TypeInfo => info;
        public StubCodeContext CodeContext => codeContext;

        public string GetUnmanagedValuesDestination(StubIdentifierContext context)
        {
            string marshaller = StatefulValueMarshalling.GetMarshallerIdentifier(info, context);
            return $"{marshaller}.{ShapeMemberNames.LinearCollection.Stateful.GetUnmanagedValuesDestination}()";
        }

        public string GetManagedValuesSource(StubIdentifierContext context)
        {
            string marshaller = StatefulValueMarshalling.GetMarshallerIdentifier(info, context);
            return $"{marshaller}.{ShapeMemberNames.LinearCollection.Stateful.GetManagedValuesSource}()";
        }

        public string GetUnmanagedValuesSource(StubIdentifierContext context)
        {
            string marshaller = StatefulValueMarshalling.GetMarshallerIdentifier(info, context);
            string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(info, context);
            return $"{marshaller}.{ShapeMemberNames.LinearCollection.Stateful.GetUnmanagedValuesSource}({numElementsIdentifier})";
        }

        public string GetManagedValuesDestination(StubIdentifierContext context)
        {
            string marshaller = StatefulValueMarshalling.GetMarshallerIdentifier(info, context);
            string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(info, context);
            return $"{marshaller}.{ShapeMemberNames.LinearCollection.Stateful.GetManagedValuesDestination}({numElementsIdentifier})";
        }
    }

    /// <summary>
    /// Marshaller that marshals elements through a stateful linear collection marshaller.
    /// </summary>
    internal sealed class StatefulLinearCollectionMarshalling(
        ICustomTypeMarshallingStrategy innerMarshaller,
        MarshallerShape shape,
        CountInfo countInfo,
        bool castCountInfo,
        ElementsMarshalling elementsMarshalling,
        bool cleanupElements) : ICustomTypeMarshallingStrategy
    {
        public ManagedTypeInfo NativeType => innerMarshaller.NativeType;
        public bool UsesNativeIdentifier => true;
        public TypePositionInfo TypeInfo => innerMarshaller.TypeInfo;
        public StubCodeContext CodeContext => innerMarshaller.CodeContext;

        public void GenerateCleanupCallerAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            // The element marshaller chooses its cleanup stage. StatefulFreeMarshalling frees the container.
            if (cleanupElements)
            {
                elementsMarshalling.GenerateElementCleanupStatement(writer, context);
            }
        }

        public void GenerateCleanupCalleeAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (cleanupElements)
            {
                elementsMarshalling.GenerateElementCleanupStatement(writer, context);
            }
        }

        public void GenerateGuaranteedUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateGuaranteedUnmarshalStatements(writer, context);

        public void GenerateMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            innerMarshaller.GenerateMarshalStatements(writer, context);

            if (CodeContext.Direction == MarshalDirection.ManagedToUnmanaged && !TypeInfo.IsByRef && TypeInfo.ByValueContentsMarshalKind == ByValueContentsMarshalKind.Out)
            {
                // Clear output-only storage so cleanup cannot observe uninitialized elements.
                elementsMarshalling.GenerateClearUnmanagedDestination(writer, context);
                return;
            }
            if (CodeContext.Direction == MarshalDirection.UnmanagedToManaged && !TypeInfo.IsByRef && TypeInfo.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out))
            {
                elementsMarshalling.GenerateUnmanagedToManagedByValueOutMarshalStatement(writer, context);
                return;
            }
            if (shape.HasFlag(MarshallerShape.ToUnmanaged) || shape.HasFlag(MarshallerShape.CallerAllocatedBuffer))
            {
                elementsMarshalling.GenerateMarshalStatement(writer, context);
            }
        }

        public void GenerateNotifyForSuccessfulInvokeStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateNotifyForSuccessfulInvokeStatements(writer, context);
        public void GeneratePinnedMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GeneratePinnedMarshalStatements(writer, context);
        public void GeneratePinStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GeneratePinStatements(writer, context);

        public void GenerateSetupStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            innerMarshaller.GenerateSetupStatements(writer, context);

            // A managed-to-unmanaged-only collection does not otherwise use a count local.
            if (MarshallerHelpers.GetMarshalDirection(TypeInfo, CodeContext) is not MarshalDirection.ManagedToUnmanaged
                || TypeInfo.ByValueContentsMarshalKind != ByValueContentsMarshalKind.Default)
            {
                string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(TypeInfo, context);
                writer.WriteLine($"int {numElementsIdentifier};");
                writer.WriteLine(MarshallerHelpers.SkipInitOrDefaultInit(
                    new TypePositionInfo(SpecialTypeInfo.Int32, NoMarshallingInfo.Instance)
                    {
                        InstanceIdentifier = numElementsIdentifier
                    }, context));
            }

            elementsMarshalling.GenerateSetupStatement(writer, context);
        }

        public void GenerateUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (CodeContext.Direction == MarshalDirection.ManagedToUnmanaged && !TypeInfo.IsByRef && TypeInfo.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out))
            {
                elementsMarshalling.GenerateManagedToUnmanagedByValueOutUnmarshalStatement(writer, context);
                return;
            }
            if (CodeContext.Direction == MarshalDirection.UnmanagedToManaged && !TypeInfo.IsByRef && TypeInfo.ByValueContentsMarshalKind == ByValueContentsMarshalKind.Out)
            {
                elementsMarshalling.GenerateClearManagedValuesDestination(writer, context);
                return;
            }
            if (!shape.HasFlag(MarshallerShape.ToManaged))
            {
                return;
            }

            string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(TypeInfo, context);
            writer.WriteLine($"{numElementsIdentifier} = {ElementsMarshalling.GenerateNumElementsExpression(countInfo, castCountInfo, CodeContext, context)};");
            elementsMarshalling.GenerateUnmarshalStatement(writer, context);
            innerMarshaller.GenerateUnmarshalStatements(writer, context);
        }

        public void GenerateUnmarshalCaptureStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateUnmarshalCaptureStatements(writer, context);
    }

    /// <summary>
    /// Marshaller that enables calling the Free method on a stateful marshaller.
    /// </summary>
    internal sealed class StatefulFreeMarshalling(ICustomTypeMarshallingStrategy innerMarshaller) : ICustomTypeMarshallingStrategy
    {
        public ManagedTypeInfo NativeType => innerMarshaller.NativeType;
        public bool UsesNativeIdentifier => innerMarshaller.UsesNativeIdentifier;
        public TypePositionInfo TypeInfo => innerMarshaller.TypeInfo;
        public StubCodeContext CodeContext => innerMarshaller.CodeContext;

        public void GenerateCleanupCallerAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            innerMarshaller.GenerateCleanupCallerAllocatedResourcesStatements(writer, context);
            if (MarshallerHelpers.GetCleanupStage(TypeInfo, CodeContext) is StubIdentifierContext.Stage.CleanupCallerAllocated)
            {
                writer.WriteLine($"{StatefulValueMarshalling.GetMarshallerIdentifier(TypeInfo, context)}.{ShapeMemberNames.Free}();");
            }
        }

        public void GenerateCleanupCalleeAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            innerMarshaller.GenerateCleanupCalleeAllocatedResourcesStatements(writer, context);
            if (MarshallerHelpers.GetCleanupStage(TypeInfo, CodeContext) is StubIdentifierContext.Stage.CleanupCalleeAllocated)
            {
                writer.WriteLine($"{StatefulValueMarshalling.GetMarshallerIdentifier(TypeInfo, context)}.{ShapeMemberNames.Free}();");
            }
        }

        public void GenerateGuaranteedUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateGuaranteedUnmarshalStatements(writer, context);
        public void GenerateMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateMarshalStatements(writer, context);
        public void GenerateNotifyForSuccessfulInvokeStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateNotifyForSuccessfulInvokeStatements(writer, context);
        public void GeneratePinnedMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GeneratePinnedMarshalStatements(writer, context);
        public void GeneratePinStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GeneratePinStatements(writer, context);
        public void GenerateSetupStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateSetupStatements(writer, context);
        public void GenerateUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateUnmarshalStatements(writer, context);
        public void GenerateUnmarshalCaptureStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateUnmarshalCaptureStatements(writer, context);
    }
}