File: Marshalling\CharMarshaller.cs
Web Access
Project: src\src\libraries\System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\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.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
 
namespace Microsoft.Interop
{
    public sealed class Utf16CharMarshaller : IUnboundMarshallingGenerator
    {
        private static readonly ManagedTypeInfo s_nativeType = SpecialTypeInfo.UInt16;
 
        public ValueBoundaryBehavior GetValueBoundaryBehavior(TypePositionInfo info, StubCodeContext context)
        {
            if (IsPinningPathSupported(info, context))
            {
                return ValueBoundaryBehavior.NativeIdentifier;
            }
            else if (!UsesNativeIdentifier(info, context))
            {
                return ValueBoundaryBehavior.ManagedIdentifier;
            }
            else if (info.IsByRef)
            {
                return ValueBoundaryBehavior.AddressOfNativeIdentifier;
            }
 
            return ValueBoundaryBehavior.NativeIdentifier;
        }
 
        public ManagedTypeInfo AsNativeType(TypePositionInfo info)
        {
            Debug.Assert(info.ManagedType is SpecialTypeInfo {SpecialType: SpecialType.System_Char });
            return s_nativeType;
        }
 
        public SignatureBehavior GetNativeSignatureBehavior(TypePositionInfo info)
        {
            return info.IsByRef ? SignatureBehavior.PointerToNativeType : SignatureBehavior.NativeType;
        }
 
        public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeContext codeContext, StubIdentifierContext context)
        {
            (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info);
 
            if (IsPinningPathSupported(info, codeContext))
            {
                if (context.CurrentStage == StubIdentifierContext.Stage.Pin)
                {
                    // fixed (char* <pinned> = &<managed>)
                    yield return FixedStatement(
                        VariableDeclaration(
                            PointerType(PredefinedType(Token(SyntaxKind.CharKeyword))),
                            SingletonSeparatedList(
                                VariableDeclarator(Identifier(PinnedIdentifier(info.InstanceIdentifier)))
                                    .WithInitializer(EqualsValueClause(
                                        PrefixUnaryExpression(
                                            SyntaxKind.AddressOfExpression,
                                            IdentifierName(Identifier(managedIdentifier)))
                                    ))
                            )
                        ),
                        // ushort* <native> = (ushort*)<pinned>;
                        LocalDeclarationStatement(
                            VariableDeclaration(PointerType(AsNativeType(info).Syntax),
                                SingletonSeparatedList(
                                    VariableDeclarator(nativeIdentifier)
                                        .WithInitializer(EqualsValueClause(
                                            CastExpression(
                                                PointerType(AsNativeType(info).Syntax),
                                                IdentifierName(PinnedIdentifier(info.InstanceIdentifier))))))))
                    );
                }
                yield break;
            }
 
            MarshalDirection elementMarshalDirection = MarshallerHelpers.GetMarshalDirection(info, codeContext);
 
            switch (context.CurrentStage)
            {
                case StubIdentifierContext.Stage.Setup:
                    break;
                case StubIdentifierContext.Stage.Marshal:
                    if (elementMarshalDirection is MarshalDirection.ManagedToUnmanaged or MarshalDirection.Bidirectional)
                    {
                        // There's an implicit conversion from char to ushort,
                        // so we simplify the generated code to just pass the char value directly
                        if (info.IsByRef)
                        {
                            yield return ExpressionStatement(
                                AssignmentExpression(
                                    SyntaxKind.SimpleAssignmentExpression,
                                    IdentifierName(nativeIdentifier),
                                    IdentifierName(managedIdentifier)));
                        }
                    }
 
                    break;
                case StubIdentifierContext.Stage.Unmarshal:
                    if (elementMarshalDirection is MarshalDirection.UnmanagedToManaged or MarshalDirection.Bidirectional)
                    {
                        yield return ExpressionStatement(
                            AssignmentExpression(
                                SyntaxKind.SimpleAssignmentExpression,
                                IdentifierName(managedIdentifier),
                                CastExpression(
                                    PredefinedType(
                                        Token(SyntaxKind.CharKeyword)),
                                    IdentifierName(nativeIdentifier))));
                    }
 
                    break;
                default:
                    break;
            }
        }
 
        public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context)
        {
            MarshalDirection elementMarshalDirection = MarshallerHelpers.GetMarshalDirection(info, context);
            return !IsPinningPathSupported(info, context) && (elementMarshalDirection != MarshalDirection.ManagedToUnmanaged || info.IsByRef);
        }
 
        private static bool IsPinningPathSupported(TypePositionInfo info, StubCodeContext context)
        {
            return context.SingleFrameSpansNativeContext
                && !context.IsInStubReturnPosition(info)
                && info.IsByRef;
        }
 
        private static string PinnedIdentifier(string identifier) => $"{identifier}__pinned";
        public ByValueMarshalKindSupport SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, TypePositionInfo info, out GeneratorDiagnostic? diagnostic)
        {
            return ByValueMarshalKindSupportDescriptor.Default.GetSupport(marshalKind, info, out diagnostic);
        }
 
    }
}