diff --git a/test/Microsoft.Windows.CsWin32.Tests/InteriorPointerTests.cs b/test/Microsoft.Windows.CsWin32.Tests/InteriorPointerTests.cs
index 4b82dcdd..d47a8152 100644
--- a/test/Microsoft.Windows.CsWin32.Tests/InteriorPointerTests.cs
+++ b/test/Microsoft.Windows.CsWin32.Tests/InteriorPointerTests.cs
@@ -2,127 +2,10 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
///
-/// Tests projections of native buffers that contain borrowed interior pointers.
+/// Tests projections of native buffers using the published Win32 metadata package.
///
public class InteriorPointerTests : GeneratorTestBase
{
- private const string MetadataSource = """
- using System;
- using System.Runtime.InteropServices;
- using Windows.Win32.Foundation.Metadata;
-
- namespace Windows.Win32.Foundation.Metadata
- {
- [AttributeUsage(AttributeTargets.Parameter)]
- public sealed class ContainsInteriorPointersAttribute : Attribute { }
-
- [AttributeUsage(AttributeTargets.Parameter)]
- public sealed class MemorySizeAttribute : Attribute
- {
- public short BytesParamIndex { get; set; }
- }
-
- [AttributeUsage(AttributeTargets.Parameter)]
- public sealed class NativeArrayInfoAttribute : Attribute
- {
- public short CountParamIndex { get; set; }
- public int CountConst { get; set; }
- }
-
- [AttributeUsage(AttributeTargets.Parameter)]
- public sealed class ConstAttribute : Attribute { }
- }
-
- namespace Windows.Win32.Test
- {
- public unsafe struct RECORD
- {
- public byte* Value;
- }
-
- [UnmanagedFunctionPointer(CallingConvention.Winapi)]
- public delegate void CALLBACK();
-
- public static unsafe class Apis
- {
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void ByteBuffer(
- [ContainsInteriorPointers] [Out] byte* buffer, uint capacity, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void MemorySizedByteBuffer(
- [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 1)] byte* buffer,
- uint capacity, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void VoidBuffer(
- [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 1)] void* buffer,
- uint capacity, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void RecordBuffer(
- [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 1)] RECORD* buffer,
- uint capacity, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void CountedRecordBuffer(
- [ContainsInteriorPointers] [Out, NativeArrayInfo(CountParamIndex = 1)] RECORD* buffer,
- uint capacity, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void ConstantRecordBuffer(
- [ContainsInteriorPointers] [Out, NativeArrayInfo(CountConst = 1)] RECORD* buffer,
- [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void OptionalRecordBuffer(
- [ContainsInteriorPointers] [Out, Optional, MemorySize(BytesParamIndex = 1)] RECORD* buffer,
- uint capacity, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void OptionalCapacityBuffer(
- [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 1)] byte* buffer,
- [In, Out, Optional] uint* capacity, [Out, Optional] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void ConstRecordBuffer(
- [ContainsInteriorPointers] [In, Const, MemorySize(BytesParamIndex = 1)] RECORD* buffer,
- uint capacity, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void SingleRecord(
- [ContainsInteriorPointers] [Out] RECORD* buffer, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void ScalarBuffer(
- [ContainsInteriorPointers] [Out] uint* buffer, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void CallbackBuffer(
- [ContainsInteriorPointers] [Out, NativeArrayInfo(CountParamIndex = 1)] CALLBACK* buffer,
- uint capacity, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void MixedBuffers(
- [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 1)] void* buffer,
- uint capacity, [Out, MemorySize(BytesParamIndex = 3)] byte* ordinaryBuffer,
- uint ordinaryCapacity, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void SharedCapacity(
- [ContainsInteriorPointers] [Out, MemorySize(BytesParamIndex = 2)] byte* buffer,
- [Out, MemorySize(BytesParamIndex = 2)] byte* ordinaryBuffer,
- uint capacity, [Out] uint* written);
-
- [DllImport("kernel32.dll", ExactSpelling = true)]
- public static extern void SharedCapacityReversed(
- [Out, NativeArrayInfo(CountParamIndex = 2)] RECORD* ordinaryBuffer,
- [ContainsInteriorPointers] [Out, NativeArrayInfo(CountParamIndex = 2)] RECORD* buffer,
- uint capacity, [Out] uint* written);
- }
- }
- """;
-
///
/// Initializes a new instance of the class.
///
@@ -228,154 +111,40 @@ public void PublishedMetadataBuffersRemainPointers(string api, string bufferName
}
///
- /// Verifies that the published annotation does not suppress ordinary buffer projections.
+ /// Verifies that annotated real APIs retain friendly handle and scalar output projections.
///
- /// Whether to enable marshaling.
- [Theory, CombinatorialData]
- public void PublishedMetadataUnannotatedBuffersStillUseSpans(bool allowMarshaling)
+ /// The annotated API to generate.
+ /// The handle parameter.
+ /// The scalar output parameter.
+ [Theory]
+ [InlineData("GetPrinterW", "hPrinter", "pcbNeeded")]
+ [InlineData("GetTokenInformation", "TokenHandle", "ReturnLength")]
+ public void PublishedMetadataOtherParametersRemainFriendly(string api, string handleName, string outputName)
{
this.compilation = this.starterCompilations["net8.0"];
- this.generator = this.CreateGenerator(DefaultTestGeneratorOptions with { AllowMarshaling = allowMarshaling });
- this.GenerateApi("ReadFile");
-
- Assert.Contains(this.FindGeneratedMethod("ReadFile"), method =>
- method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "lpBuffer"
- && parameter.Type is GenericNameSyntax { Identifier.ValueText: "Span" }));
- }
-
- ///
- /// Verifies that annotated buffers retain native pointer types and explicit capacities in every overload.
- ///
- /// The buffer shape to generate.
- /// Whether to enable marshaling.
- /// Whether additional pointer overloads are requested.
- [Theory, CombinatorialData]
- public void AnnotatedBuffersRemainPointers(
- [CombinatorialValues("ByteBuffer", "MemorySizedByteBuffer", "VoidBuffer", "RecordBuffer", "CountedRecordBuffer", "ConstantRecordBuffer", "OptionalRecordBuffer", "OptionalCapacityBuffer", "ConstRecordBuffer", "SingleRecord", "ScalarBuffer", "CallbackBuffer")] string api,
- bool allowMarshaling,
- bool includePointerOverloads)
- {
- this.GenerateFromMetadata(api, containsInteriorPointers: true, allowMarshaling, includePointerOverloads);
-
- MethodDeclarationSyntax[] methods = this.FindGeneratedMethod(api).ToArray();
- Assert.NotEmpty(methods);
- Assert.All(methods, method =>
- {
- ParameterSyntax buffer = Assert.Single(method.ParameterList.Parameters, parameter => parameter.Identifier.ValueText == "buffer");
- Assert.IsType(buffer.Type);
- Assert.Empty(buffer.Modifiers);
- Assert.Equal(api is not ("ConstantRecordBuffer" or "SingleRecord" or "ScalarBuffer"), method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "capacity"));
- });
-
- Assert.Contains(methods, method => !IsOrContainsExternMethod(method)
- && method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "written" && parameter.Modifiers.Any(SyntaxKind.OutKeyword)));
- }
-
- ///
- /// Verifies that ordinary buffer projections remain available when the annotation is absent.
- ///
- /// The buffer shape to generate.
- /// Whether to enable marshaling.
- [Theory, CombinatorialData]
- public void UnannotatedBuffersStillUseSpans(
- [CombinatorialValues("ByteBuffer", "MemorySizedByteBuffer", "VoidBuffer", "RecordBuffer", "CountedRecordBuffer", "ConstantRecordBuffer", "OptionalRecordBuffer", "OptionalCapacityBuffer", "ConstRecordBuffer")] string api,
- bool allowMarshaling)
- {
- this.GenerateFromMetadata(api, containsInteriorPointers: false, allowMarshaling, includePointerOverloads: false);
- Assert.Contains(this.FindGeneratedMethod(api), method => method.ParameterList.Parameters.Any(parameter =>
- parameter.Identifier.ValueText == "buffer"
- && parameter.Type is GenericNameSyntax { Identifier.ValueText: "Span" or "ReadOnlySpan" }));
- }
-
- ///
- /// Verifies that unrelated buffers and scalar outputs still receive friendly projections.
- ///
- /// Whether to enable marshaling.
- /// Whether additional pointer overloads are requested.
- [Theory, CombinatorialData]
- public void OtherParametersRetainFriendlyProjections(bool allowMarshaling, bool includePointerOverloads)
- {
- const string Api = "MixedBuffers";
- this.GenerateFromMetadata(Api, containsInteriorPointers: true, allowMarshaling, includePointerOverloads);
-
- Assert.All(this.FindGeneratedMethod(Api), method =>
- {
- Assert.IsType(Assert.Single(method.ParameterList.Parameters, parameter => parameter.Identifier.ValueText == "buffer").Type);
- Assert.Contains(method.ParameterList.Parameters, parameter => parameter.Identifier.ValueText == "capacity");
- });
- Assert.Contains(this.FindGeneratedMethod(Api), method =>
- method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "ordinaryBuffer" && parameter.Type is GenericNameSyntax { Identifier.ValueText: "Span" })
- && !method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "ordinaryCapacity"));
- }
+ this.generator = this.CreateGenerator(DefaultTestGeneratorOptions with { WideCharOnly = false });
+ this.GenerateApi(api);
- ///
- /// Verifies that other buffers cannot remove a capacity needed by an annotated pointer buffer.
- ///
- /// The order and size annotation to generate.
- /// Whether to enable marshaling.
- [Theory, CombinatorialData]
- public void SharedCapacitiesRemainExplicit(
- [CombinatorialValues("SharedCapacity", "SharedCapacityReversed")] string api,
- bool allowMarshaling)
- {
- this.GenerateFromMetadata(api, containsInteriorPointers: true, allowMarshaling, includePointerOverloads: false);
- Assert.All(this.FindGeneratedMethod(api), method =>
- {
- Assert.Contains(method.ParameterList.Parameters, parameter => parameter.Identifier.ValueText == "capacity");
- Assert.All(method.ParameterList.Parameters.Where(parameter => parameter.Identifier.ValueText is "buffer" or "ordinaryBuffer"), parameter => Assert.IsType(parameter.Type));
- });
+ Assert.Contains(this.FindGeneratedMethod(api), method =>
+ method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == handleName
+ && parameter.Type is IdentifierNameSyntax { Identifier.ValueText: "SafeHandle" })
+ && method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == outputName
+ && parameter.Modifiers.Any(SyntaxKind.OutKeyword)));
}
///
- /// Verifies that optional capacities remain explicit while unrelated optional outputs can still be omitted.
+ /// Verifies that the published annotation does not suppress ordinary buffer projections.
///
/// Whether to enable marshaling.
[Theory, CombinatorialData]
- public void OptionalCapacitiesRemainExplicit(bool allowMarshaling)
- {
- const string Api = "OptionalCapacityBuffer";
- this.GenerateFromMetadata(Api, containsInteriorPointers: true, allowMarshaling, includePointerOverloads: false);
-
- MethodDeclarationSyntax[] methods = this.FindGeneratedMethod(Api).ToArray();
- Assert.All(methods, method =>
- Assert.Contains(method.ParameterList.Parameters, parameter => parameter.Identifier.ValueText == "capacity"));
- Assert.Contains(methods, method =>
- method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "capacity" && parameter.Modifiers.Any(SyntaxKind.RefKeyword)));
- Assert.Contains(methods, method =>
- !method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "written"));
- }
-
- private void GenerateFromMetadata(string api, bool containsInteriorPointers, bool allowMarshaling, bool includePointerOverloads)
+ public void PublishedMetadataUnannotatedBuffersStillUseSpans(bool allowMarshaling)
{
this.compilation = this.starterCompilations["net8.0"];
- string source = containsInteriorPointers ? MetadataSource : MetadataSource.Replace("[ContainsInteriorPointers]", string.Empty, StringComparison.Ordinal);
- CSharpCompilation metadataCompilation = CSharpCompilation.Create(
- "Windows.Win32",
- [CSharpSyntaxTree.ParseText(source, this.parseOptions)],
- this.compilation.References,
- this.compilation.Options);
- string metadataPath = Path.Combine(Path.GetTempPath(), $"CsWin32-InteriorPointers-{Guid.NewGuid():N}.winmd");
- try
- {
- using (FileStream stream = File.Create(metadataPath))
- {
- var result = metadataCompilation.Emit(stream);
- Assert.True(result.Success, string.Join(Environment.NewLine, result.Diagnostics));
- }
+ this.generator = this.CreateGenerator(DefaultTestGeneratorOptions with { AllowMarshaling = allowMarshaling });
+ this.GenerateApi("ReadFile");
- GeneratorOptions options = DefaultTestGeneratorOptions with
- {
- AllowMarshaling = allowMarshaling,
- FriendlyOverloads = new() { IncludePointerOverloads = includePointerOverloads },
- };
- this.generator = new Generator(metadataPath, null, [], options, this.compilation, this.parseOptions);
- this.GenerateApi(api);
- }
- finally
- {
- this.generator?.Dispose();
- this.generator = null;
- File.Delete(metadataPath);
- }
+ Assert.Contains(this.FindGeneratedMethod("ReadFile"), method =>
+ method.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == "lpBuffer"
+ && parameter.Type is GenericNameSyntax { Identifier.ValueText: "Span" }));
}
}