From 73f7e4c2b94009c6377ad1a96d20fd6db3ba91ad Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 15 Jun 2022 23:46:03 -0600 Subject: [PATCH] Add option to generate out IntPtr for COM output pointer parameters Closes #328 --- src/Microsoft.Windows.CsWin32/Generator.cs | 17 +++++++++++------ .../GeneratorOptions.cs | 8 ++++++++ .../PointerTypeHandleInfo.cs | 10 ++++++++++ .../settings.schema.json | 5 +++++ .../GeneratorTests.cs | 15 +++++++++++++-- 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index fc1e7e5f..3cbc65e9 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -424,6 +424,8 @@ private enum FriendlyOverloadOf internal SuperGenerator? SuperGenerator { get; set; } + internal GeneratorOptions Options => this.options; + internal string InputAssemblyName { get; } internal MetadataIndex MetadataIndex { get; } @@ -4216,14 +4218,17 @@ private IEnumerable DeclareFriendlyOverloads(MethodDefi yield return (MethodDeclarationSyntax)templateFriendlyOverload; } - if (this.options.AllowMarshaling && this.TryFetchTemplate("marshaling/" + externMethodDeclaration.Identifier.ValueText, out templateFriendlyOverload)) + if (externMethodDeclaration.Identifier.ValueText != "CoCreateInstance" || !this.options.ComInterop.UseIntPtrForComOutPointers) { - yield return (MethodDeclarationSyntax)templateFriendlyOverload; - } + if (this.options.AllowMarshaling && this.TryFetchTemplate("marshaling/" + externMethodDeclaration.Identifier.ValueText, out templateFriendlyOverload)) + { + yield return (MethodDeclarationSyntax)templateFriendlyOverload; + } - if (!this.options.AllowMarshaling && this.TryFetchTemplate("no_marshaling/" + externMethodDeclaration.Identifier.ValueText, out templateFriendlyOverload)) - { - yield return (MethodDeclarationSyntax)templateFriendlyOverload; + if (!this.options.AllowMarshaling && this.TryFetchTemplate("no_marshaling/" + externMethodDeclaration.Identifier.ValueText, out templateFriendlyOverload)) + { + yield return (MethodDeclarationSyntax)templateFriendlyOverload; + } } #pragma warning disable SA1114 // Parameter list should follow declaration diff --git a/src/Microsoft.Windows.CsWin32/GeneratorOptions.cs b/src/Microsoft.Windows.CsWin32/GeneratorOptions.cs index ba8aa8a7..fa47726a 100644 --- a/src/Microsoft.Windows.CsWin32/GeneratorOptions.cs +++ b/src/Microsoft.Windows.CsWin32/GeneratorOptions.cs @@ -65,5 +65,13 @@ public record ComInteropOptions /// Gets an array of "interface.method" or "interface" strings that identify methods and interfaces that should be generated with . /// public ImmutableArray PreserveSigMethods { get; init; } = ImmutableArray.Create(); + + /// + /// Gets a value indicating whether to emit methods that return COM objects via output parameters using as the parameter type instead of the COM interface. + /// + /// + /// This may be useful on .NET when using ComWrappers. See this issue for more details. + /// + public bool UseIntPtrForComOutPointers { get; init; } } } diff --git a/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs b/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs index 4fd5f1f6..a850b76b 100644 --- a/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs +++ b/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs @@ -5,6 +5,7 @@ using System.Reflection.Metadata; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.Windows.CsWin32.FastSyntaxFactory; namespace Microsoft.Windows.CsWin32; @@ -35,6 +36,15 @@ internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs } else if (xIn || xOut) { + if (elementTypeDetails.Type is PredefinedTypeSyntax { Keyword: { RawKind: (int)SyntaxKind.ObjectKeyword } } && inputs.Generator is not null && inputs.Generator.Options.ComInterop.UseIntPtrForComOutPointers) + { + bool isComOutPtr = inputs.Generator?.Reader is not null && customAttributes is not null && customAttributes.Value.Any(ah => inputs.Generator.IsAttribute(inputs.Generator.Reader.GetCustomAttribute(ah), Generator.InteropDecorationNamespace, "ComOutPtrAttribute")); + return new TypeSyntaxAndMarshaling(IdentifierName(nameof(IntPtr))) + { + ParameterModifier = Token(SyntaxKind.OutKeyword), + }; + } + // But we can use a modifier to emulate a pointer and thereby enable marshaling. return new TypeSyntaxAndMarshaling(elementTypeDetails.Type, elementTypeDetails.MarshalAsAttribute, elementTypeDetails.NativeArrayInfo) { diff --git a/src/Microsoft.Windows.CsWin32/settings.schema.json b/src/Microsoft.Windows.CsWin32/settings.schema.json index 091fb2d3..74f9badb 100644 --- a/src/Microsoft.Windows.CsWin32/settings.schema.json +++ b/src/Microsoft.Windows.CsWin32/settings.schema.json @@ -24,6 +24,11 @@ "uniqueItems": true, "pattern": "^[\\w_]+(?:\\.[\\w_]+)?$" } + }, + "useIntPtrForComOutPointers": { + "description": "Emits methods that return COM objects via output parameters using IntPtr as the parameter type instead of the COM interface. This may be useful on .NET when using ComWrappers. See https://github.com/microsoft/CsWin32/issues/328 for more details.", + "type": "boolean", + "default": false } } }, diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index b0dc3180..832bc03a 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -532,6 +532,17 @@ public void ComOutPtrTypedAsOutObject() Assert.Contains(this.FindGeneratedMethod(methodName), m => m.ParameterList.Parameters.Last() is { } last && last.Modifiers.Any(SyntaxKind.OutKeyword) && last.Type is PredefinedTypeSyntax { Keyword: { RawKind: (int)SyntaxKind.ObjectKeyword } }); } + [Fact] + public void ComOutPtrTypedAsIntPtr() + { + const string methodName = "CoCreateInstance"; + this.generator = this.CreateGenerator(new GeneratorOptions { ComInterop = new() { UseIntPtrForComOutPointers = true } }); + Assert.True(this.generator.TryGenerate(methodName, CancellationToken.None)); + this.CollectGeneratedCode(this.generator); + this.AssertNoDiagnostics(); + Assert.Contains(this.FindGeneratedMethod(methodName), m => m.ParameterList.Parameters.Last() is { } last && last.Modifiers.Any(SyntaxKind.OutKeyword) && last.Type is IdentifierNameSyntax { Identifier: { ValueText: "IntPtr" } }); + } + [Fact] public void AmbiguousApiName() { @@ -1412,9 +1423,9 @@ public void TBButton([CombinatorialMemberData(nameof(SpecificCpuArchitectures))] } [Theory, PairwiseData] - public void FullGeneration(bool allowMarshaling, [CombinatorialMemberData(nameof(AnyCpuArchitectures))] Platform platform) + public void FullGeneration(bool allowMarshaling, bool useIntPtrForComOutPtr, [CombinatorialMemberData(nameof(AnyCpuArchitectures))] Platform platform) { - var generatorOptions = new GeneratorOptions { AllowMarshaling = allowMarshaling }; + var generatorOptions = new GeneratorOptions { AllowMarshaling = allowMarshaling, ComInterop = new() { UseIntPtrForComOutPointers = useIntPtrForComOutPtr } }; this.compilation = this.compilation.WithOptions(this.compilation.Options.WithPlatform(platform)); this.generator = this.CreateGenerator(generatorOptions); this.generator.GenerateAll(CancellationToken.None);