| File: GeneratedStatements.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. using System; using System.Linq; namespace Microsoft.Interop { public readonly record struct GeneratedStatements { public GeneratedStatements() { } public string Setup { get; init; } = ""; public string Marshal { get; init; } = ""; public string Pin { get; init; } = ""; public string PinnedMarshal { get; init; } = ""; public string InvokeStatement { get; init; } = ""; public string ErrorUnmarshalCapture { get; init; } = ""; public string ErrorUnmarshal { get; init; } = ""; public string Unmarshal { get; init; } = ""; public string NotifyForSuccessfulInvoke { get; init; } = ""; public string GuaranteedUnmarshal { get; init; } = ""; public string ErrorCleanupCalleeAllocated { get; init; } = ""; public string CleanupCallerAllocated { get; init; } = ""; public string CleanupCalleeAllocated { get; init; } = ""; public string ManagedExceptionCatchClauses { get; init; } = ""; public static GeneratedStatements Create(BoundGenerators marshallers, StubIdentifierContext context) { var writer = new IndentedTextWriter(); return new GeneratedStatements { Setup = GenerateStage(StubIdentifierContext.Stage.Setup), Marshal = GenerateStage(StubIdentifierContext.Stage.Marshal), Pin = GenerateStage(StubIdentifierContext.Stage.Pin), PinnedMarshal = GenerateStage(StubIdentifierContext.Stage.PinnedMarshal), InvokeStatement = ";\r\n", ErrorUnmarshalCapture = GenerateStage(StubIdentifierContext.Stage.UnmarshalCapture, errorHandlingOnly: true), ErrorUnmarshal = GenerateStage(StubIdentifierContext.Stage.Unmarshal, errorHandlingOnly: true), Unmarshal = GenerateStage(StubIdentifierContext.Stage.UnmarshalCapture) + GenerateStage(StubIdentifierContext.Stage.Unmarshal), NotifyForSuccessfulInvoke = GenerateStage(StubIdentifierContext.Stage.NotifyForSuccessfulInvoke), GuaranteedUnmarshal = GenerateStage(StubIdentifierContext.Stage.GuaranteedUnmarshal), ErrorCleanupCalleeAllocated = GenerateStage(StubIdentifierContext.Stage.CleanupCalleeAllocated, errorHandlingOnly: true), CleanupCallerAllocated = GenerateStage(StubIdentifierContext.Stage.CleanupCallerAllocated), CleanupCalleeAllocated = GenerateStage(StubIdentifierContext.Stage.CleanupCalleeAllocated), ManagedExceptionCatchClauses = GenerateCatchClauseForManagedException(marshallers, context, writer) }; string GenerateStage(StubIdentifierContext.Stage stage, bool errorHandlingOnly = false) { writer.Clear(); StubIdentifierContext stageContext = context with { CurrentStage = stage }; foreach (IBoundMarshallingGenerator marshaller in marshallers.SignatureMarshallers) { if (stage is StubIdentifierContext.Stage.UnmarshalCapture or StubIdentifierContext.Stage.Unmarshal or StubIdentifierContext.Stage.CleanupCalleeAllocated && marshaller.TypeInfo.IsErrorHandlingPosition != errorHandlingOnly) { continue; } marshaller.Generate(writer, stageContext); } return writer.Length == 0 ? "" : $"// {stage} - {GetStageDescription(stage)}\r\n{writer}"; } } public static GeneratedStatements Create(BoundGenerators marshallers, StubCodeContext codeContext, StubIdentifierContext context, string expressionToInvoke) { GeneratedStatements statements = Create(marshallers, context); StubIdentifierContext invokeContext = context with { CurrentStage = StubIdentifierContext.Stage.Invoke }; return statements with { InvokeStatement = codeContext.Direction switch { MarshalDirection.ManagedToUnmanaged => GenerateStatementForNativeInvoke(marshallers, invokeContext, expressionToInvoke), MarshalDirection.UnmanagedToManaged => GenerateStatementForManagedInvoke(marshallers, invokeContext, expressionToInvoke), _ => throw new ArgumentException("Direction must be ManagedToUnmanaged or UnmanagedToManaged", nameof(codeContext)) } }; } /// <summary> /// Creates statements for a property or indexer accessor. The caller supplies the access /// expression, including any marshalled index arguments. /// </summary> public static GeneratedStatements CreateForProperty(BoundGenerators marshallers, StubIdentifierContext context, string propertyAccess, bool isSetter) { GeneratedStatements statements = Create(marshallers, context); StubIdentifierContext invokeContext = context with { CurrentStage = StubIdentifierContext.Stage.Invoke }; if (isSetter) { // The value parameter follows all index parameters for an indexer setter. IBoundMarshallingGenerator valueMarshaller = marshallers.ManagedParameterMarshallers.Last(); return statements with { InvokeStatement = $"{propertyAccess} = {invokeContext.GetIdentifiers(valueMarshaller.TypeInfo).managed};\r\n" }; } return statements with { InvokeStatement = $"{invokeContext.GetIdentifiers(marshallers.ManagedReturnMarshaller.TypeInfo).managed} = {propertyAccess};\r\n" }; } private static string GenerateStatementForNativeInvoke(BoundGenerators marshallers, StubIdentifierContext context, string expressionToInvoke) { string arguments = string.Join(", ", marshallers.NativeParameterMarshallers.Select(marshaller => marshaller.AsArgument(context))); string invoke = $"{expressionToInvoke}({arguments});\r\n"; if (marshallers.NativeReturnMarshaller.TypeInfo.ManagedType == SpecialTypeInfo.Void) { return invoke; } (string managed, string native) = context.GetIdentifiers(marshallers.NativeReturnMarshaller.TypeInfo); string targetIdentifier = marshallers.NativeReturnMarshaller.UsesNativeIdentifier ? native : managed; return $"{targetIdentifier} = {invoke}"; } private static string GenerateStatementForManagedInvoke(BoundGenerators marshallers, StubIdentifierContext context, string expressionToInvoke) { string arguments = string.Join(", ", marshallers.ManagedParameterMarshallers.Select(marshaller => marshaller.AsManagedArgument(context))); string invoke = $"{expressionToInvoke}({arguments});\r\n"; if (marshallers.ManagedReturnMarshaller.TypeInfo.ManagedType == SpecialTypeInfo.Void) { return invoke; } return $"{context.GetIdentifiers(marshallers.ManagedReturnMarshaller.TypeInfo).managed} = {invoke}"; } private static string GenerateCatchClauseForManagedException(BoundGenerators marshallers, StubIdentifierContext context, IndentedTextWriter writer) { if (!marshallers.HasManagedExceptionMarshaller) { return ""; } writer.Clear(); IBoundMarshallingGenerator marshaller = marshallers.ManagedExceptionMarshaller; string managed = context.GetIdentifiers(marshaller.TypeInfo).managed; writer.WriteLine($"catch ({TypeNames.GlobalAlias}{TypeNames.System_Exception} {managed})"); using (writer.WriteBlock()) { marshaller.Generate(writer, context with { CurrentStage = StubIdentifierContext.Stage.Marshal }); marshaller.Generate(writer, context with { CurrentStage = StubIdentifierContext.Stage.PinnedMarshal }); } return writer.ToString(); } private static string GetStageDescription(StubIdentifierContext.Stage stage) { return stage switch { StubIdentifierContext.Stage.Setup => "Perform required setup.", StubIdentifierContext.Stage.Marshal => "Convert managed data to native data.", StubIdentifierContext.Stage.Pin => "Pin data in preparation for calling the P/Invoke.", StubIdentifierContext.Stage.PinnedMarshal => "Convert managed data to native data that requires the managed data to be pinned.", StubIdentifierContext.Stage.Invoke => "Call the P/Invoke.", StubIdentifierContext.Stage.UnmarshalCapture => "Capture the native data into marshaller instances in case conversion to managed data throws an exception.", StubIdentifierContext.Stage.Unmarshal => "Convert native data to managed data.", StubIdentifierContext.Stage.CleanupCallerAllocated => "Perform cleanup of caller allocated resources.", StubIdentifierContext.Stage.CleanupCalleeAllocated => "Perform cleanup of callee allocated resources.", StubIdentifierContext.Stage.NotifyForSuccessfulInvoke => "Keep alive any managed objects that need to stay alive across the call.", StubIdentifierContext.Stage.GuaranteedUnmarshal => "Convert native data to managed data even in the case of an exception during the non-cleanup phases.", _ => throw new ArgumentOutOfRangeException(nameof(stage)) }; } } }