File: VirtualMethodPointerStubGenerator.cs
Web Access
Project: ComInterfaceGenerator.csproj (Microsoft.Interop.ComInterfaceGenerator)
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis;

namespace Microsoft.Interop
{
    internal static class VirtualMethodPointerStubGenerator
    {
        internal const string NativeThisParameterIdentifier = "__this";
        internal const string VirtualMethodTableIdentifier = "__vtable";
        internal const string VirtualMethodTarget = "__target";
        private const string ManagedThisParameterIdentifier = "@this";

        public static (GeneratedComMember, ImmutableArray<DiagnosticInfo>) GenerateManagedToNativeStub(
            SourceAvailableIncrementalMethodStubGenerationContext methodStub,
            Func<EnvironmentFlags, MarshalDirection, IMarshallingGeneratorResolver> generatorResolverCreator)
        {
            var diagnostics = new GeneratorDiagnosticsBag(new DiagnosticDescriptorProvider(), methodStub.DiagnosticLocation, SR.ResourceManager, typeof(FxResources.Microsoft.Interop.ComInterfaceGenerator.SR));

            ImmutableArray<TypePositionInfo> elements = methodStub.VtableIndexData.ImplicitThisParameter
                ? AddManagedToUnmanagedImplicitThis(methodStub)
                : methodStub.SignatureContext.ElementTypeInformation;

            var stubGenerator = new ManagedToNativeStubGenerator(
                elements,
                methodStub.VtableIndexData.SetLastError,
                diagnostics,
                generatorResolverCreator(methodStub.EnvironmentFlags, MarshalDirection.ManagedToUnmanaged),
                new CodeEmitOptions(SkipInit: true));

            string functionPointerType = stubGenerator.GenerateTargetMethodSignatureData()
                .GetFunctionPointerType(methodStub.CallingConvention.Array);
            var writer = new IndentedTextWriter();
            using (writer.WriteBlock())
            {
                writer.WriteLine("unsafe");
                using (writer.WriteBlock())
                {
                    writer.WriteLine($"var ({NativeThisParameterIdentifier}, {VirtualMethodTableIdentifier}) = (({TypeNames.GlobalAlias}{TypeNames.IUnmanagedVirtualMethodTableProvider})this).GetVirtualMethodTableInfoForKey(typeof({methodStub.TypeKeyOwner.FullTypeName}));");
                    writer.WriteLine($"var {VirtualMethodTarget} = (({functionPointerType}){VirtualMethodTableIdentifier}[{methodStub.VtableIndexData.Index}]);");
                    stubGenerator.GenerateStubBody(writer, VirtualMethodTarget);
                }
            }

            return (
                new GeneratedComMember(
                    methodStub.MemberKind,
                    methodStub.TemplateName,
                    GetManagedSignature(methodStub),
                    methodStub.SignatureContext.AdditionalAttributes.ToSequenceEqual(),
                    writer.ToString(),
                    string.Join(" ", methodStub.StubMethodSyntaxTemplate.Modifiers)),
                methodStub.Diagnostics.Array.AddRange(diagnostics.Diagnostics));
        }

        internal static GeneratedMethodSignature GetManagedSignature(IncrementalMethodStubGenerationContext methodStub)
        {
            ImmutableArray<GeneratedParameter> parameters = methodStub.SignatureContext.StubParameters.ToImmutableArray();
            string returnType = methodStub.SignatureContext.StubReturnType;
            if (methodStub.MemberKind.IsAccessorSetter())
            {
                returnType = parameters[parameters.Length - 1].Type;
                parameters = parameters.RemoveAt(parameters.Length - 1);
            }

            return new GeneratedMethodSignature(parameters, returnType);
        }

        public static (GeneratedComMember, ImmutableArray<DiagnosticInfo>) GenerateNativeToManagedStub(
            SourceAvailableIncrementalMethodStubGenerationContext methodStub,
            Func<EnvironmentFlags, MarshalDirection, IMarshallingGeneratorResolver> generatorResolverCreator)
        {
            var diagnostics = new GeneratorDiagnosticsBag(new DiagnosticDescriptorProvider(), methodStub.DiagnosticLocation, SR.ResourceManager, typeof(FxResources.Microsoft.Interop.ComInterfaceGenerator.SR));

            var stubGenerator = new UnmanagedToManagedStubGenerator(
                AddUnmanagedToManagedImplicitElementInfos(methodStub),
                diagnostics,
                generatorResolverCreator(methodStub.EnvironmentFlags, MarshalDirection.UnmanagedToManaged));

            string body;
            if (methodStub.MemberKind.IsPropertyOrIndexerAccessor())
            {
                bool isSetter = methodStub.MemberKind.IsAccessorSetter();
                body = methodStub.MemberKind.IsIndexerAccessor()
                    ? stubGenerator.GenerateStubBodyForIndexer(ManagedThisParameterIdentifier, isSetter)
                    : stubGenerator.GenerateStubBodyForProperty($"{ManagedThisParameterIdentifier}.{methodStub.TemplateName}", isSetter);
            }
            else
            {
                Debug.Assert(methodStub.MemberKind is StubMemberKind.Method);
                body = stubGenerator.GenerateStubBodyForMethod($"{ManagedThisParameterIdentifier}.{methodStub.StubMethodSyntaxTemplate.Identifier}");
            }

            string unmanagedCallersOnlyAttribute = TypeNames.GlobalAlias + TypeNames.UnmanagedCallersOnlyAttribute;
            if (methodStub.CallingConvention.Array.Length != 0)
            {
                unmanagedCallersOnlyAttribute += "(CallConvs = new[] { "
                    + string.Join(", ", methodStub.CallingConvention.Select(static convention => $"typeof(global::System.Runtime.CompilerServices.CallConv{convention})"))
                    + " })";
            }

            var writer = new IndentedTextWriter();
            using (writer.WriteBlock())
            {
                writer.WriteLine("unsafe");
                writer.Write(body);
            }

            return (
                new GeneratedComMember(
                    StubMemberKind.Method,
                    methodStub.AbiMethodIdentifier,
                    stubGenerator.GenerateAbiMethodSignatureData(),
                    ImmutableArray.Create(unmanagedCallersOnlyAttribute).ToSequenceEqual(),
                    writer.ToString(),
                    "internal static unsafe"),
                methodStub.Diagnostics.Array.AddRange(diagnostics.Diagnostics));
        }

        private static ImmutableArray<TypePositionInfo> AddManagedToUnmanagedImplicitThis(SourceAvailableIncrementalMethodStubGenerationContext methodStub)
        {
            ImmutableArray<TypePositionInfo> originalElements = methodStub.SignatureContext.ElementTypeInformation;
            var elements = ImmutableArray.CreateBuilder<TypePositionInfo>(originalElements.Length + 2);

            elements.Add(new TypePositionInfo(new PointerTypeInfo("void*", "void*", false), methodStub.ManagedThisMarshallingInfo)
            {
                InstanceIdentifier = NativeThisParameterIdentifier,
                NativeIndex = 0,
            });
            foreach (TypePositionInfo element in originalElements)
            {
                elements.Add(element with
                {
                    NativeIndex = TypePositionInfo.IncrementIndex(element.NativeIndex)
                });
            }

            return elements.ToImmutable();
        }

        private static ImmutableArray<TypePositionInfo> AddUnmanagedToManagedImplicitElementInfos(IncrementalMethodStubGenerationContext methodStub)
        {
            ImmutableArray<TypePositionInfo> originalElements = methodStub.SignatureContext.ElementTypeInformation;
            var elements = ImmutableArray.CreateBuilder<TypePositionInfo>(originalElements.Length + 2);

            elements.Add(new TypePositionInfo(methodStub.TypeKeyOwner, methodStub.ManagedThisMarshallingInfo)
            {
                InstanceIdentifier = ManagedThisParameterIdentifier,
                NativeIndex = 0,
            });
            foreach (TypePositionInfo element in originalElements)
            {
                TypePositionInfo unmanagedToManagedElement = element;
                if (unmanagedToManagedElement.IsErrorHandlingPosition)
                {
                    unmanagedToManagedElement = unmanagedToManagedElement with
                    {
                        IsErrorHandlingPosition = false,
                    };
                }

                elements.Add(unmanagedToManagedElement with
                {
                    NativeIndex = TypePositionInfo.IncrementIndex(unmanagedToManagedElement.NativeIndex)
                });
            }

            if (methodStub.ExceptionMarshallingInfo != NoMarshallingInfo.Instance)
            {
                elements.Add(
                    new TypePositionInfo(
                        new ReferenceTypeInfo(TypeNames.GlobalAlias + TypeNames.System_Exception, TypeNames.System_Exception),
                        methodStub.ExceptionMarshallingInfo)
                    {
                        InstanceIdentifier = "__exception",
                        ManagedIndex = TypePositionInfo.ExceptionIndex,
                        NativeIndex = TypePositionInfo.ReturnIndex,
                        IsErrorHandlingPosition = true,
                    });
            }

            return elements.ToImmutable();
        }

        public static string GenerateUnmanagedFunctionPointerTypeForMethod(
            IncrementalMethodStubGenerationContext method,
            Func<EnvironmentFlags, MarshalDirection, IMarshallingGeneratorResolver> generatorResolverCreator)
        {
            var diagnostics = new GeneratorDiagnosticsBag(new DiagnosticDescriptorProvider(), method.DiagnosticLocation, SR.ResourceManager, typeof(FxResources.Microsoft.Interop.ComInterfaceGenerator.SR));
            var stubGenerator = new UnmanagedToManagedStubGenerator(
                AddUnmanagedToManagedImplicitElementInfos(method),
                diagnostics,
                generatorResolverCreator(method.EnvironmentFlags, MarshalDirection.UnmanagedToManaged));

            return stubGenerator.GenerateAbiMethodSignatureData().GetFunctionPointerType(method.CallingConvention.Array);
        }

        public static ImmutableArray<string> GetCallingConventionsFromAttributes(
            AttributeData? suppressGCTransitionAttribute,
            AttributeData? unmanagedCallConvAttribute,
            ImmutableArray<string> defaultCallingConventions)
        {
            var callingConventions = ImmutableArray.CreateBuilder<string>();
            if (suppressGCTransitionAttribute is not null)
            {
                callingConventions.Add("SuppressGCTransition");
            }

            // UnmanagedCallConvAttribute overrides the default calling convention rules.
            if (unmanagedCallConvAttribute is not null)
            {
                foreach (KeyValuePair<string, TypedConstant> arg in unmanagedCallConvAttribute.NamedArguments)
                {
                    if (arg.Key == "CallConvs")
                    {
                        foreach (TypedConstant callConv in arg.Value.Values)
                        {
                            ITypeSymbol callConvSymbol = (ITypeSymbol)callConv.Value!;
                            if (callConvSymbol.Name.StartsWith("CallConv", StringComparison.Ordinal))
                            {
                                callingConventions.Add(callConvSymbol.Name.Substring("CallConv".Length));
                            }
                        }
                    }
                }
            }
            else
            {
                callingConventions.AddRange(defaultCallingConventions);
            }

            return callingConventions.ToImmutable();
        }
    }
}