Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions src/Microsoft.Windows.CsWin32/Generator.Constant.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.Windows.CsWin32;
Expand Down Expand Up @@ -143,28 +143,46 @@ internal void RequestConstant(FieldDefinitionHandle fieldDefHandle)

// Decode the field type up front to learn whether this constant is typed as a typedef struct
// (e.g. HRESULT, NTSTATUS, HWND). Two pieces of information come out of this:
// * fieldType — a TypeDefinitionHandle in THIS metadata, set only when the struct is defined
// * fieldType - a TypeDefinitionHandle in THIS metadata, set only when the struct is defined
// in this same winmd. It drives constant "nesting" (injecting the field directly into the
// struct declaration when that struct is generated locally).
// * extensionStructName — the fully-qualified metadata name of the typedef struct, captured
// * extensionStructName - the fully-qualified metadata name of the typedef struct, captured
// even for cross-winmd references (e.g. a Windows.Wdk constant typed as the SDK's NTSTATUS).
// It drives the extensionReceiver feature's `extension(<struct>)` attachment.
TypeHandleInfo fieldTypeInfo = fieldDef.DecodeSignature<TypeHandleInfo, SignatureHandleProvider.IGenericContext?>(this.SignatureHandleProvider, null) with { IsConstantField = true };
TypeDefinitionHandle? fieldType = null;
string? extensionStructName = null;
if (fieldTypeInfo is HandleTypeHandleInfo handleInfo && this.IsTypeDefStruct(handleInfo) && handleInfo.Handle.Kind == HandleKind.TypeReference)
if (fieldTypeInfo is HandleTypeHandleInfo handleInfo && this.IsTypeDefStruct(handleInfo))
{
TypeReference tr = this.Reader.GetTypeReference((TypeReferenceHandle)handleInfo.Handle);
string fieldTypeName = this.Reader.GetString(tr.Name);
if (!TypeDefsThatDoNotNestTheirConstants.Contains(fieldTypeName))
string? fieldTypeName = null;
string? fieldTypeNamespace = null;
if (handleInfo.Handle.Kind == HandleKind.TypeReference)
{
string fieldTypeNamespace = this.Reader.GetString(tr.Namespace);
extensionStructName = fieldTypeNamespace.Length == 0 ? fieldTypeName : $"{fieldTypeNamespace}.{fieldTypeName}";
TypeReference tr = this.Reader.GetTypeReference((TypeReferenceHandle)handleInfo.Handle);
fieldTypeName = this.Reader.GetString(tr.Name);
fieldTypeNamespace = this.Reader.GetString(tr.Namespace);
if (this.TryGetTypeDefHandle(tr, out TypeDefinitionHandle candidate))
{
fieldType = candidate;
}
}
else if (handleInfo.Handle.Kind == HandleKind.TypeDefinition)
{
TypeDefinitionHandle tdh = (TypeDefinitionHandle)handleInfo.Handle;
TypeDefinition td = this.Reader.GetTypeDefinition(tdh);
fieldTypeName = this.Reader.GetString(td.Name);
fieldTypeNamespace = this.Reader.GetString(td.Namespace);
fieldType = tdh;
}

if (fieldTypeName is not null && !TypeDefsThatDoNotNestTheirConstants.Contains(fieldTypeName))
{
extensionStructName = string.IsNullOrEmpty(fieldTypeNamespace) ? fieldTypeName : $"{fieldTypeNamespace}.{fieldTypeName}";
}
else
{
fieldType = null;
}
}

// Multi-assembly composition: if another assembly already exposes this constant, skip regeneration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ ServiceFabric.winmd | [youyuanwu/fabric-metadata](https://github.com/youyuanwu/f
CustomIInspectable.winmd | Generated using WinMDGenerator toolchain. Project in the subdirectory [CustomIInspectable](../../CustomIInspectable) with build instructions in [readme.md](../../CustomIInspectable/readme.md).
WnfWithoutStatusSuccess.winmd | Authored fixture for issue #1677 regression coverage. Its source is checked in as [WnfWithoutStatusSuccess.il](WnfWithoutStatusSuccess.il). We keep this one as IL because that is much easier to maintain than reproducing the full end-to-end WinmdGenerator workflow for a focused test asset.
SelfContained.winmd | Authored fixture reproducing the OSClient single-`Generator` build configuration (a non-`Windows.Win32` metadata assembly with the Windows.Win32 projection provided by a reference), where special string typedefs (`PCWSTR`) must be referenced under `Windows.Win32.Foundation`. Its source is checked in as [SelfContained.il](SelfContained.il). Kept as IL for the same reason as WnfWithoutStatusSuccess.winmd.
FlatNamespace.winmd | Authored fixture for issue #1848 regression coverage, with native typedefs and associated enums at the common namespace root and in a nested namespace. Its source and regeneration command are checked in as [FlatNamespace.il](FlatNamespace.il).
FlatNamespace.winmd | Authored fixture for issue #1848 regression coverage, with native typedefs and associated enums at the common namespace root and in a nested namespace. Its source and regeneration command are checked in as [FlatNamespace.il](FlatNamespace.il).
TypeDefConstant.winmd | Authored fixture verifying that struct-typed constants defined in the same metadata module using TypeDefinition handles (rather than TypeReference handles) correctly nest into the struct and attach to extension blocks. Its source is checked in as [TypeDefConstant.il](TypeDefConstant.il). Kept as IL for the same reason as WnfWithoutStatusSuccess.winmd.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Source for TypeDefConstant.winmd.
// Fixture verifying that constants typed as typedef structs defined in the same
// metadata module (encoded as TypeDefinition handles rather than TypeReference handles)
// correctly nest into the struct declaration and attach to extension blocks.

.assembly extern netstandard { .publickeytoken = (CC 7B 13 FF CD 2D DD 51 ) .ver 2:1:0:0 }
.assembly extern Windows.Win32.winmd { .ver 0:0:0:0 }
.assembly TypeDefConstant { .ver 1:0:0:0 }
.module TypeDefConstant.winmd
.module extern somedll.dll
.corflags 0x00000001

.class public sequential ansi sealed beforefieldinit TypeDefConstant.Foundation.CUSTOM_HANDLE
extends [netstandard]System.ValueType
{
.custom instance void [Windows.Win32.winmd]Windows.Win32.Foundation.Metadata.NativeTypedefAttribute::.ctor() = ( 01 00 00 00 )
.field public native int Value
}

.class public abstract auto autochar sealed beforefieldinit TypeDefConstant.Apis
extends [netstandard]System.Object
{
.field public static initonly valuetype TypeDefConstant.Foundation.CUSTOM_HANDLE CUSTOM_HANDLE_VALUE = int32(1)
}
Binary file not shown.
65 changes: 64 additions & 1 deletion test/Microsoft.Windows.CsWin32.Tests/MultiMetadataTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

public class MultiMetadataTests : GeneratorTestBase
Expand Down Expand Up @@ -83,4 +83,67 @@ public void SingleGeneratorQualifiesSpecialTypeDefToWin32Namespace(string api)
Assert.DoesNotContain("winmdroot.Foundation.PCWSTR", tree.ToString());
}
}

[Fact]
public void TypeDefConstant_NestsConstantIntoStruct()
{
string metadataPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location!)!, "ExternalMetadata", "TypeDefConstant.winmd");

this.generator = new Generator(metadataPath, null, [], DefaultTestGeneratorOptions, this.compilation, this.parseOptions);
Assert.True(this.generator.TryGenerate("CUSTOM_HANDLE_VALUE", TestContext.Current.CancellationToken));
this.CollectGeneratedCode(this.generator);
this.AssertNoDiagnostics();

// The constant should be nested inside CUSTOM_HANDLE struct, not dumped as a top-level field on the host class.
StructDeclarationSyntax customHandle = Assert.Single(this.FindGeneratedType("CUSTOM_HANDLE").OfType<StructDeclarationSyntax>());
Assert.Contains(customHandle.Members.OfType<FieldDeclarationSyntax>(), f => f.Declaration.Variables.Any(v => v.Identifier.ValueText == "CUSTOM_HANDLE_VALUE"));

// No top-level constant on the host class should be generated.
Assert.Empty(this.FindGeneratedType(DefaultTestGeneratorOptions.ClassName));
}

[Fact]
public void TypeDefConstant_WithExtensionReceiver_AttachesToStructExtensionBlock()
{
string metadataPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location!)!, "ExternalMetadata", "TypeDefConstant.winmd");

this.parseOptions = this.parseOptions.WithLanguageVersion(LanguageVersion.CSharp14);

// Provide the receiver and the struct externally so extensionReceiver attaches forwarders to the struct.
this.compilation = this.AddCode(
@"namespace TypeDefConstant { internal static class PInvoke { } }
namespace TypeDefConstant.Foundation
{
internal readonly struct CUSTOM_HANDLE
{
internal readonly int Value;
internal CUSTOM_HANDLE(int value) => this.Value = value;
public static explicit operator CUSTOM_HANDLE(int value) => new CUSTOM_HANDLE(value);
}
}",
fileName: "Refs.cs");

GeneratorOptions options = new()
{
EmitSingleFile = true,
ClassName = "PInvokeExtensions",
ExtensionReceiver = "PInvoke",
Comment thread
AustinAviv marked this conversation as resolved.
};
this.generator = new Generator(metadataPath, null, [], options, this.compilation, this.parseOptions);

Assert.True(this.generator.TryGenerate("CUSTOM_HANDLE_VALUE", TestContext.Current.CancellationToken));
this.CollectGeneratedCode(this.generator);
Comment thread
AustinAviv marked this conversation as resolved.
this.AssertNoDiagnostics();

ClassDeclarationSyntax hostClass = Assert.Single(
this.FindGeneratedType("PInvokeExtensions").OfType<ClassDeclarationSyntax>(),
c => c.Members.Count > 0);

// Under extensionReceiver when the struct is external, CUSTOM_HANDLE_VALUE attaches to extension(CUSTOM_HANDLE),
// not to extension(PInvoke).
ExtensionBlockDeclarationSyntax structExtension = Assert.Single(
hostClass.Members.OfType<ExtensionBlockDeclarationSyntax>(),
b => b.ParameterList!.Parameters.Single().Type!.ToString().Trim() == "global::TypeDefConstant.Foundation.CUSTOM_HANDLE");
Assert.Contains(structExtension.Members.OfType<PropertyDeclarationSyntax>(), p => p.Identifier.ValueText == "CUSTOM_HANDLE_VALUE");
}
}
Loading