diff --git a/src/CsWin32Generator/Program.cs b/src/CsWin32Generator/Program.cs
index 6d4b0191..82a3e80e 100644
--- a/src/CsWin32Generator/Program.cs
+++ b/src/CsWin32Generator/Program.cs
@@ -23,6 +23,7 @@ public partial class Program
private bool verbose;
private string? assemblyName;
private FileInfo? assemblyOriginatorKeyFile;
+ private LanguageVersion languageVersion = LanguageVersion.CSharp13;
///
/// Initializes a new instance of the class.
@@ -118,6 +119,11 @@ public async Task Main(
Description = "Path to the strong name key file (.snk) for signing.",
};
+ var languageVersionOption = new Option("--language-version")
+ {
+ Description = "C# language version (e.g., 10, 11, 12, 13, Latest, Preview).",
+ };
+
var verboseOption = new Option("--verbose");
var rootCommand = new RootCommand("CsWin32 Code Generator - Generates P/Invoke methods and supporting types from Windows metadata.")
@@ -133,6 +139,7 @@ public async Task Main(
referencesOption,
assemblyNameOption,
keyFileOption,
+ languageVersionOption,
verboseOption,
};
@@ -149,6 +156,7 @@ public async Task Main(
this.assemblyName = parseResult.GetValue(assemblyNameOption);
this.assemblyOriginatorKeyFile = parseResult.GetValue(keyFileOption);
this.verbose = parseResult.GetValue(verboseOption);
+ string? languageVersionString = parseResult.GetValue(languageVersionOption);
// Check for errors before continuing.
if (parseResult.Errors.Count > 0)
@@ -161,6 +169,15 @@ public async Task Main(
return 1;
}
+ if (languageVersionString is not null)
+ {
+ if (!LanguageVersionFacts.TryParse(languageVersionString, out this.languageVersion))
+ {
+ this.ReportError($"Invalid language version: {languageVersionString}");
+ return 1;
+ }
+ }
+
try
{
var result = await this.GenerateCode(
@@ -261,7 +278,7 @@ private Task GenerateCode(
// Create compilation context
CSharpCompilation? compilation = this.CreateCompilation(allowUnsafeBlocks: true, platform, references);
- CSharpParseOptions? parseOptions = this.CreateParseOptions(targetFramework);
+ CSharpParseOptions? parseOptions = this.CreateParseOptions(targetFramework)?.WithLanguageVersion(this.languageVersion);
this.VerboseWriteLine($"Created compilation context with platform: {platform}, language version: {parseOptions?.LanguageVersion}");
// Load docs if available
diff --git a/src/Microsoft.Windows.CsWin32.BuildTasks/CsWin32CodeGeneratorTask.cs b/src/Microsoft.Windows.CsWin32.BuildTasks/CsWin32CodeGeneratorTask.cs
index db10312c..c6958271 100644
--- a/src/Microsoft.Windows.CsWin32.BuildTasks/CsWin32CodeGeneratorTask.cs
+++ b/src/Microsoft.Windows.CsWin32.BuildTasks/CsWin32CodeGeneratorTask.cs
@@ -104,6 +104,11 @@ public CsWin32CodeGeneratorTask(IToolExecutor? toolExecutor)
///
public string? KeyFile { get; set; }
+ ///
+ /// Gets or sets the C# language version (e.g., 10, 11, 12, 13, Latest, Preview).
+ ///
+ public string? LangVersion { get; set; }
+
///
/// Gets the generated source files.
///
@@ -191,6 +196,7 @@ protected override string GenerateResponseFileCommands()
commandLine.AppendSwitchIfNotNull("--platform ", this.Platform);
commandLine.AppendSwitchIfNotNull("--assembly-name ", this.AssemblyName);
commandLine.AppendSwitchIfNotNull("--key-file ", this.KeyFile);
+ commandLine.AppendSwitchIfNotNull("--language-version ", this.LangVersion);
commandLine.AppendSwitch("--verbose ");
if (this.References?.Length > 0)
diff --git a/src/Microsoft.Windows.CsWin32/Generator.Features.cs b/src/Microsoft.Windows.CsWin32/Generator.Features.cs
index 333ffaf7..311fca2f 100644
--- a/src/Microsoft.Windows.CsWin32/Generator.Features.cs
+++ b/src/Microsoft.Windows.CsWin32/Generator.Features.cs
@@ -20,6 +20,7 @@ public partial class Generator
private readonly bool canMarshalNativeDelegateParams;
private readonly bool overloadResolutionPriorityAttributePredefined;
private readonly bool unscopedRefAttributePredefined;
+ private readonly bool canUseComVariant;
private readonly INamedTypeSymbol? runtimeFeatureClass;
private readonly bool generateSupportedOSPlatformAttributes;
private readonly bool generateSupportedOSPlatformAttributesOnInterfaces; // only supported on net6.0 (https://github.com/dotnet/runtime/pull/48838)
@@ -30,6 +31,8 @@ public partial class Generator
internal bool CanUseIPropertyValue => this.canUseIPropertyValue;
+ internal bool CanUseComVariant => this.canUseComVariant;
+
private void DeclareOverloadResolutionPriorityAttributeIfNecessary()
{
// This attribute may only be applied for C# 13 and later, or else C# errors out.
diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs
index 1e3f6c13..d12900cd 100644
--- a/src/Microsoft.Windows.CsWin32/Generator.cs
+++ b/src/Microsoft.Windows.CsWin32/Generator.cs
@@ -124,6 +124,7 @@ public Generator(string metadataLibraryPath, Docs? docs, IEnumerable add
this.getDelegateForFunctionPointerGenericExists = this.compilation?.GetTypeByMetadataName(typeof(Marshal).FullName)?.GetMembers(nameof(Marshal.GetDelegateForFunctionPointer)).Any(m => m is IMethodSymbol { IsGenericMethod: true }) is true;
this.generateDefaultDllImportSearchPathsAttribute = this.compilation?.GetTypeByMetadataName(typeof(DefaultDllImportSearchPathsAttribute).FullName) is object;
this.canUseIPropertyValue = this.compilation?.GetTypeByMetadataName("Windows.Foundation.IPropertyValue")?.DeclaredAccessibility == Accessibility.Public;
+ this.canUseComVariant = this.compilation?.GetTypeByMetadataName("System.Runtime.InteropServices.Marshalling.ComVariant") is not null;
if (this.FindTypeSymbolIfAlreadyAvailable("System.Runtime.Versioning.SupportedOSPlatformAttribute") is { } attribute)
{
this.generateSupportedOSPlatformAttributes = true;
diff --git a/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs b/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs
index bd95fb82..0d39caee 100644
--- a/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs
+++ b/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs
@@ -159,7 +159,7 @@ internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs
return new TypeSyntaxAndMarshaling(IdentifierName(specialName));
}
}
- else if (useComSourceGenerators && simpleName is "VARIANT")
+ else if (useComSourceGenerators && simpleName is "VARIANT" && this.Generator.CanUseComVariant)
{
return new TypeSyntaxAndMarshaling(QualifiedName(ParseName("global::System.Runtime.InteropServices.Marshalling"), IdentifierName("ComVariant")));
}
@@ -311,8 +311,13 @@ private static bool TryMarshalAsObject(TypeSyntaxSettings inputs, string name, [
marshalAs = new MarshalAsAttribute(UnmanagedType.IDispatch);
return true;
case "VARIANT":
- marshalAs = new MarshalAsAttribute(UnmanagedType.Struct);
- return true;
+ if (inputs.Generator?.UseSourceGenerators != true)
+ {
+ marshalAs = new MarshalAsAttribute(UnmanagedType.Struct);
+ return true;
+ }
+
+ break;
}
}
diff --git a/src/Microsoft.Windows.CsWin32/build/Microsoft.Windows.CsWin32.targets b/src/Microsoft.Windows.CsWin32/build/Microsoft.Windows.CsWin32.targets
index 432961d0..b28d755e 100644
--- a/src/Microsoft.Windows.CsWin32/build/Microsoft.Windows.CsWin32.targets
+++ b/src/Microsoft.Windows.CsWin32/build/Microsoft.Windows.CsWin32.targets
@@ -67,6 +67,7 @@
GeneratorToolPath="$(CsWin32GeneratorToolPath)CsWin32Generator.dll"
TargetFramework="$(TargetFramework)"
Platform="$(Platform)"
+ LangVersion="$(LangVersion)"
References="@(ReferencePath)"
ContinueOnError="$(CsWin32ContinueOnError)"
Condition="@(CsWin32NativeMethodsTxt->Count()) > 0">
diff --git a/test/CsWin32Generator.Tests/CsWin32GeneratorFullTests.cs b/test/CsWin32Generator.Tests/CsWin32GeneratorFullTests.cs
index bd94c19d..1a137583 100644
--- a/test/CsWin32Generator.Tests/CsWin32GeneratorFullTests.cs
+++ b/test/CsWin32Generator.Tests/CsWin32GeneratorFullTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using Microsoft.CodeAnalysis.CSharp;
using Xunit;
namespace CsWin32Generator.Tests;
@@ -12,11 +13,15 @@ public CsWin32GeneratorFullTests(ITestOutputHelper logger)
{
}
- [Fact]
+ [Theory]
[Trait("TestCategory", "FailsInCloudTest")] // these take ~4GB of memory to run.
- public async Task FullGeneration()
+ [InlineData("net8.0", LanguageVersion.CSharp12)]
+ [InlineData("net9.0", LanguageVersion.CSharp13)]
+ public async Task FullGeneration(string tfm, LanguageVersion langVersion)
{
this.fullGeneration = true;
- await this.InvokeGeneratorAndCompile(TestOptions.None, "FullGeneration");
+ this.compilation = this.starterCompilations[tfm];
+ this.parseOptions = this.parseOptions.WithLanguageVersion(langVersion);
+ await this.InvokeGeneratorAndCompile($"FullGeneration_{tfm}_{langVersion}", TestOptions.None);
}
}
diff --git a/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs b/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs
index cf367f1d..bc777caa 100644
--- a/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs
+++ b/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs
@@ -1,10 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-#pragma warning disable SA1402
+#pragma warning disable SA1402,SA1201,SA1202
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
using Xunit;
namespace CsWin32Generator.Tests;
@@ -21,7 +22,7 @@ public async Task TestGenerateIDispatch()
{
// IDispatch is not normally emitted, but we need it for source generated com so check that it got generated.
this.nativeMethods.Add("IDispatch");
- await this.InvokeGeneratorAndCompile();
+ await this.InvokeGeneratorAndCompileFromFact();
var idispatchType = this.FindGeneratedType("IDispatch");
Assert.NotEmpty(idispatchType);
@@ -31,7 +32,7 @@ public async Task TestGenerateIDispatch()
public async Task TestGenerateIShellWindows()
{
this.nativeMethods.Add("IShellWindows");
- await this.InvokeGeneratorAndCompile();
+ await this.InvokeGeneratorAndCompileFromFact();
var ishellWindowsType = this.FindGeneratedType("IShellWindows");
Assert.NotEmpty(ishellWindowsType);
@@ -44,7 +45,7 @@ public async Task TestGenerateIShellWindows()
public async Task TestNativeMethods()
{
this.nativeMethodsTxt = "NativeMethods.txt";
- await this.InvokeGeneratorAndCompile();
+ await this.InvokeGeneratorAndCompileFromFact();
}
[Fact]
@@ -52,7 +53,7 @@ public async Task CheckITypeCompIsUnmanaged()
{
// Request DebugPropertyInfo and we should see ITypeComp_unmanaged get generated because it has an embedded managed field
this.nativeMethods.Add("DebugPropertyInfo");
- await this.InvokeGeneratorAndCompile();
+ await this.InvokeGeneratorAndCompileFromFact();
var iface = this.FindGeneratedType("ITypeComp_unmanaged");
Assert.True(iface.Any());
@@ -63,7 +64,7 @@ public async Task CheckIAudioProcessingObjectConfigurationDoesNotGenerateUnmanag
{
// Request IAudioProcessingObjectConfiguration and it should request IAudioMediaType_unmanaged that's embedded in a struct
this.nativeMethods.Add("IAudioProcessingObjectConfiguration");
- await this.InvokeGeneratorAndCompile();
+ await this.InvokeGeneratorAndCompileFromFact();
var iface = this.FindGeneratedType("IAudioMediaType_unmanaged");
Assert.True(iface.Any());
@@ -75,7 +76,7 @@ public async Task TestGenerateIUnknownAndID3D11DeviceContext()
// If IUnknown is requested first and then it's needed as an unmanaged type, we fail to generate it.
this.nativeMethods.Add("IUnknown");
this.nativeMethods.Add("ID3D11DeviceContext");
- await this.InvokeGeneratorAndCompile();
+ await this.InvokeGeneratorAndCompileFromFact();
}
[Fact]
@@ -84,7 +85,7 @@ public async Task TestGenerateSomethingInWin32System()
// If we need CharSet _and_ we generate something in Windows.Win32.System, the partially qualified reference breaks.
this.nativeMethods.Add("GetDistanceOfClosestLanguageInList");
this.nativeMethods.Add("ADVANCED_FEATURE_FLAGS");
- await this.InvokeGeneratorAndCompile();
+ await this.InvokeGeneratorAndCompileFromFact();
}
[Theory]
@@ -94,67 +95,87 @@ public async Task VerifySignature(string api, string member, string signature)
{
// If we need CharSet _and_ we generate something in Windows.Win32.System, the partially qualified reference breaks.
this.nativeMethods.Add(api);
- await this.InvokeGeneratorAndCompile(TestOptions.None, $"{api}_{member}");
+ await this.InvokeGeneratorAndCompile($"{api}_{member}");
var generatedMemberSignatures = this.FindGeneratedMethod(member).Select(x => x.ParameterList.ToString());
Assert.Contains($"({signature})", generatedMemberSignatures);
}
+ public static IList