diff --git a/src/Microsoft.Windows.CsWin32/Generator.GeneratedCode.cs b/src/Microsoft.Windows.CsWin32/Generator.GeneratedCode.cs index 6e8a7177..86ed9d93 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.GeneratedCode.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.GeneratedCode.cs @@ -34,6 +34,11 @@ private class GeneratedCode /// private readonly Dictionary methodsGenerating = new(); + /// + /// The constants that are or have been generated. + /// + private readonly Dictionary constantsGenerating = new(); + /// /// A collection of the names of special types we are or have generated. /// @@ -303,12 +308,26 @@ internal void GenerateConstant(FieldDefinitionHandle fieldDefinitionHandle, Acti { this.ThrowIfNotGenerating(); - if (this.fieldsToSyntax.ContainsKey(fieldDefinitionHandle) || this.parent?.fieldsToSyntax.ContainsKey(fieldDefinitionHandle) is true) + if (this.constantsGenerating.TryGetValue(fieldDefinitionHandle, out Exception? failure) || this.parent?.constantsGenerating.TryGetValue(fieldDefinitionHandle, out failure) is true) { + if (failure is object) + { + throw new GenerationFailedException("This constant already failed in generation previously.", failure); + } + return; } - generator(); + this.constantsGenerating.Add(fieldDefinitionHandle, null); + try + { + generator(); + } + catch (Exception ex) + { + this.constantsGenerating[fieldDefinitionHandle] = ex; + throw; + } } internal void GenerateMacro(string macroName, Action generator) diff --git a/src/Microsoft.Windows.CsWin32/Generator.TypeDef.cs b/src/Microsoft.Windows.CsWin32/Generator.TypeDef.cs index 7d8ee528..85a2babe 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.TypeDef.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.TypeDef.cs @@ -200,6 +200,11 @@ private StructDeclarationSyntax DeclareTypeDefStruct(TypeDefinition typeDef, Typ members = AddOrReplaceMembers(members, this.ExtractMembersFromTemplate(name.Identifier.ValueText)); this.TryGenerateType("Windows.Win32.Foundation.PC" + name.Identifier.ValueText.Substring(1)); // the template references its constant version break; + case "VARIANT_BOOL": + members = AddOrReplaceMembers(members, this.ExtractMembersFromTemplate(name.Identifier.ValueText)); + this.TryGenerateConstant("VARIANT_TRUE", out _); // the template references its constant version + this.TryGenerateConstant("VARIANT_FALSE", out _); // the template references its constant version + break; case "BSTR": case "HRESULT": case "NTSTATUS": diff --git a/src/Microsoft.Windows.CsWin32/templates/VARIANT_BOOL.cs b/src/Microsoft.Windows.CsWin32/templates/VARIANT_BOOL.cs new file mode 100644 index 00000000..425b5c92 --- /dev/null +++ b/src/Microsoft.Windows.CsWin32/templates/VARIANT_BOOL.cs @@ -0,0 +1,6 @@ +partial struct VARIANT_BOOL +{ + internal VARIANT_BOOL(bool value) => this.Value = value ? VARIANT_TRUE : VARIANT_FALSE; + public static implicit operator bool(VARIANT_BOOL value) => value != VARIANT_FALSE; + public static implicit operator VARIANT_BOOL(bool value) => value ? VARIANT_TRUE : VARIANT_FALSE; +} diff --git a/test/GenerationSandbox.Tests/VARIANT_BOOLTests.cs b/test/GenerationSandbox.Tests/VARIANT_BOOLTests.cs new file mode 100644 index 00000000..a3f4a815 --- /dev/null +++ b/test/GenerationSandbox.Tests/VARIANT_BOOLTests.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Windows.Win32.Foundation; + +public class VARIANT_BOOLTests +{ + [Fact] + public void Ctor_bool() + { + VARIANT_BOOL b = true; + bool b2 = b; + Assert.True(b); + Assert.True(b2); + + Assert.False(default(VARIANT_BOOL)); + } + + [Fact] + public void Ctor_int() + { + Assert.Equal(2, new VARIANT_BOOL(2).Value); + } + + [Fact] + public void ExplicitCast() + { + Assert.Equal(2, ((VARIANT_BOOL)2).Value); + } + + [Theory] + [InlineData(3)] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(0xfff)] + public void LossyConversionFromBOOLtoBool(short ordinal) + { + VARIANT_BOOL nativeBool = new VARIANT_BOOL(ordinal); + bool managedBool = nativeBool; + Assert.Equal(ordinal != 0, managedBool); + BOOLEAN roundtrippedNativeBool = managedBool; + Assert.Equal(managedBool ? 1 : 0, roundtrippedNativeBool); + } + + [Fact] + public void BOOLEqualsComparesExactValue() + { + VARIANT_BOOL b1 = new VARIANT_BOOL(1); + VARIANT_BOOL b2 = new VARIANT_BOOL(2); + Assert.Equal(b1, b1); + Assert.NotEqual(b1, b2); + } + + [Fact] + public void BOOL_OverridesEqualityOperator() + { + var @true = new VARIANT_BOOL(true); + var @false = new VARIANT_BOOL(false); + Assert.True(@true == new VARIANT_BOOL(true)); + Assert.False(@true != new VARIANT_BOOL(true)); + Assert.True(@true != @false); + Assert.False(@true == @false); + + var two = new VARIANT_BOOL(2); + Assert.False(two == @true); + Assert.True(two != @true); + } + + [Fact] + public void LogicalOperators_And() + { + VARIANT_BOOL @true = true, @false = false; + Assert.False(@false && @false); + Assert.False(@true && @false); + Assert.True(@true && @true); + } + + [Fact] + public void LogicalOperators_Or() + { + VARIANT_BOOL @true = true, @false = false; + Assert.True(@true || @false); + Assert.False(@false || @false); + Assert.True(@true || @true); + } +} diff --git a/test/Microsoft.Windows.CsWin32.Tests/StructTests.cs b/test/Microsoft.Windows.CsWin32.Tests/StructTests.cs index 9f3e9e53..f8f8d018 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/StructTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/StructTests.cs @@ -173,6 +173,7 @@ public void SpecialStruct_ByRequest(string structName) [CombinatorialData] public void InterestingStructs( [CombinatorialValues( + "VARIANT_BOOL", // has a custom conversion to bool and relies on other members being generated "DRIVER_OBJECT", // has an inline array of delegates "DEVICE_RELATIONS", // ends with an inline "flexible" array "D3DHAL_CONTEXTCREATEDATA", // contains a field that is a pointer to a struct that is normally managed