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
4 changes: 2 additions & 2 deletions src/Microsoft.Windows.CsWin32/Generator.CustomMarshaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal string RequestCustomEnumMarshaller(string qualifiedEnumTypeName, Unmana
}

// Custom marshallers should go in a InteropServices sub-namespace.
shortNamespace += ".InteropServices";
shortNamespace = shortNamespace.Length > 0 ? $"{shortNamespace}.InteropServices" : "InteropServices";

string customTypeMarshallerName = $"{enumTypeName}To{unmanagedType}Marshaller";

Expand Down Expand Up @@ -115,7 +115,7 @@ internal string RequestCustomTypeDefMarshaller(string fullyQualifiedTypeName, Ty
}

// Custom marshallers should go in a InteropServices sub-namespace.
shortNamespace += ".InteropServices";
shortNamespace = shortNamespace.Length > 0 ? $"{shortNamespace}.InteropServices" : "InteropServices";

string customTypeMarshallerName = $"{typeDefName}Marshaller";

Expand Down
58 changes: 58 additions & 0 deletions test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,64 @@ public async Task CrossWinMD_NTSTATUSSafeHandleConstant()
Assert.Contains(this.FindGeneratedType("RtlUnsubscribeWnfStateChangeNotificationSafeHandle"), static type => type is ClassDeclarationSyntax);
}

/// <summary>
/// Verifies native typedef marshaller namespaces through build-task generation and compilation.
/// </summary>
/// <param name="nestedNamespace">Whether the typedef is in a child of the common namespace.</param>
/// <param name="tfm">The target framework of the generated projection.</param>
[Theory, CombinatorialData]
public async Task NativeTypedefMarshallerNamespace(
bool nestedNamespace,
[CombinatorialValues("net8.0", "net9.0", "net10.0")] string tfm)
{
this.compilation = this.starterCompilations[tfm];
this.parseOptions = this.parseOptions.WithLanguageVersion(GetLanguageVersionForTfm(tfm)!.Value);
this.tfm = tfm;
this.win32winmdPaths = [FlatNamespaceMetadataPath];
string typeName = nestedNamespace ? "NESTED_HANDLE" : "TEST_HANDLE";
string typeNamespace = nestedNamespace ? "FlatNamespace.Nested" : "FlatNamespace";
string marshallerName = $"{typeNamespace}.InteropServices.{typeName}Marshaller";
this.nativeMethods.Add(nestedNamespace ? "UseNestedHandle" : "UseRootHandle");

await this.InvokeGeneratorAndCompile($"{nameof(this.NativeTypedefMarshallerNamespace)}_{nestedNamespace}_{tfm}");

StructDeclarationSyntax typedef = Assert.Single(this.FindGeneratedType(typeName).OfType<StructDeclarationSyntax>());
AttributeSyntax attribute = Assert.Single(FindAttribute(typedef.AttributeLists, "NativeMarshalling"));
TypeOfExpressionSyntax marshallerType = Assert.IsType<TypeOfExpressionSyntax>(Assert.Single(attribute.ArgumentList!.Arguments).Expression);
Assert.Equal($"global::{marshallerName}", marshallerType.Type.ToString());
Assert.NotNull(this.compilation.GetTypeByMetadataName(marshallerName));
}

/// <summary>
/// Verifies associated enum marshaller namespaces through build-task generation and compilation.
/// </summary>
/// <param name="nestedNamespace">Whether the enum is in a child of the common namespace.</param>
/// <param name="tfm">The target framework of the generated projection.</param>
[Theory, CombinatorialData]
public async Task AssociatedEnumMarshallerNamespace(
bool nestedNamespace,
[CombinatorialValues("net8.0", "net9.0", "net10.0")] string tfm)
{
this.compilation = this.starterCompilations[tfm];
this.parseOptions = this.parseOptions.WithLanguageVersion(GetLanguageVersionForTfm(tfm)!.Value);
this.tfm = tfm;
this.win32winmdPaths = [FlatNamespaceMetadataPath];
string typeName = nestedNamespace ? "NESTED_ENUM" : "TEST_ENUM";
string typeNamespace = nestedNamespace ? "FlatNamespace.Nested" : "FlatNamespace";
string marshallerName = $"{typeNamespace}.InteropServices.{typeName}ToU4Marshaller";
string apiName = nestedNamespace ? "UseNestedEnum" : "UseRootEnum";
this.nativeMethods.Add(apiName);

await this.InvokeGeneratorAndCompile($"{nameof(this.AssociatedEnumMarshallerNamespace)}_{nestedNamespace}_{tfm}");

MethodDeclarationSyntax method = Assert.Single(this.FindGeneratedMethod(apiName), m => FindAttribute(m.AttributeLists, "LibraryImport").Any());
ParameterSyntax parameter = Assert.Single(method.ParameterList.Parameters);
AttributeSyntax attribute = Assert.Single(FindAttribute(parameter.AttributeLists, "global::System.Runtime.InteropServices.Marshalling.MarshalUsing"));
TypeOfExpressionSyntax marshallerType = Assert.IsType<TypeOfExpressionSyntax>(Assert.Single(attribute.ArgumentList!.Arguments).Expression);
Assert.Equal($"global::{marshallerName}", marshallerType.Type.ToString());
Assert.NotNull(this.compilation.GetTypeByMetadataName(marshallerName));
}

[Fact]
public async Task TestComVariantReturnValue()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Source for FlatNamespace.winmd.
// Root and nested native typedefs and associated enums exercise custom marshaller namespaces.
// Regenerate with: ilasm FlatNamespace.il /dll /output=FlatNamespace.winmd

.assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) .ver 4:0:0:0 }
.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 FlatNamespace { .ver 1:0:0:0 }
.module FlatNamespace.winmd
.module extern example.dll
.corflags 0x00000001

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

.class public auto ansi sealed FlatNamespace.TEST_ENUM
extends [netstandard]System.Enum
{
.field public specialname rtspecialname int32 value__
.field public static literal valuetype FlatNamespace.TEST_ENUM TestValue = int32(1)
}

.class public abstract auto autochar sealed beforefieldinit FlatNamespace.Apis
extends [netstandard]System.Object
{
.method public hidebysig static pinvokeimpl("example.dll" nomangle winapi)
void UseRootHandle([in] valuetype FlatNamespace.TEST_HANDLE handle) cil managed
{
}

.method public hidebysig static pinvokeimpl("example.dll" nomangle winapi)
void UseRootEnum([in] uint32 enumValue) cil managed
{
.param [1]
.custom instance void [Windows.Win32.winmd]Windows.Win32.Foundation.Metadata.AssociatedEnumAttribute::.ctor(string) = ( 01 00 09 54 45 53 54 5F 45 4E 55 4D 00 00 )
}
}

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

.class public auto ansi sealed FlatNamespace.Nested.NESTED_ENUM
extends [netstandard]System.Enum
{
.field public specialname rtspecialname int32 value__
.field public static literal valuetype FlatNamespace.Nested.NESTED_ENUM TestValue = int32(1)
}

.class public abstract auto autochar sealed beforefieldinit FlatNamespace.Nested.Apis
extends [netstandard]System.Object
{
.method public hidebysig static pinvokeimpl("example.dll" nomangle winapi)
void UseNestedHandle([in] valuetype FlatNamespace.Nested.NESTED_HANDLE handle) cil managed
{
}

.method public hidebysig static pinvokeimpl("example.dll" nomangle winapi)
void UseNestedEnum([in] uint32 enumValue) cil managed
{
.param [1]
.custom instance void [Windows.Win32.winmd]Windows.Win32.Foundation.Metadata.AssociatedEnumAttribute::.ctor(string) = ( 01 00 0B 4E 45 53 54 45 44 5F 45 4E 55 4D 00 00 )
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ Metadata | Source
ServiceFabric.winmd | [youyuanwu/fabric-metadata](https://github.com/youyuanwu/fabric-metadata/raw/a1bcca6ad6f6a772c9e5ff4bdba80ae5e5f24cfc/.windows/winmd/ServiceFabric.winmd)
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.
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).
1 change: 1 addition & 0 deletions test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public abstract class GeneratorTestBase : IDisposable, IAsyncLifetime
protected static readonly string ServiceFabricMetadataPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!, "ExternalMetadata", "ServiceFabric.winmd");
protected static readonly string CustomIInspectableMetadataPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!, "ExternalMetadata", "CustomIInspectable.winmd");
protected static readonly string WnfWithoutStatusSuccessMetadataPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!, "ExternalMetadata", "WnfWithoutStatusSuccess.winmd");
protected static readonly string FlatNamespaceMetadataPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!, "ExternalMetadata", "FlatNamespace.winmd");
protected static readonly string ApiDocsPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!, "apidocs.msgpack");

protected readonly ITestOutputHelper logger;
Expand Down
Loading