diff --git a/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets b/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets
index 668ea155a0..5564cdb840 100644
--- a/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets
+++ b/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets
@@ -349,6 +349,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
AdditionalArguments="@(CsWinRTGeneratorAdditionalArgument)"
StandardOutputImportance="$(CsWinRTGeneratorStandardOutputImportance)"
StandardErrorImportance="$(CsWinRTGeneratorStandardErrorImportance)"
+ MaxDegreesOfParallelism="$(CsWinRTGeneratorMaxDegreesOfParallelism)"
LogStandardErrorAsError="$(CsWinRTGeneratorLogStandardErrorAsError)" />
@@ -411,6 +412,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
CsWinRTToolsArchitecture="$(CsWinRTToolsArchitecture)"
StandardOutputImportance="$(CsWinRTGeneratorStandardOutputImportance)"
StandardErrorImportance="$(CsWinRTGeneratorStandardErrorImportance)"
+ MaxDegreesOfParallelism="$(CsWinRTGeneratorMaxDegreesOfParallelism)"
LogStandardErrorAsError="$(CsWinRTGeneratorLogStandardErrorAsError)" />
@@ -488,6 +490,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
AdditionalArguments="@(CsWinRTGeneratorAdditionalArgument)"
StandardOutputImportance="$(CsWinRTGeneratorStandardOutputImportance)"
StandardErrorImportance="$(CsWinRTGeneratorStandardErrorImportance)"
+ MaxDegreesOfParallelism="$(CsWinRTGeneratorMaxDegreesOfParallelism)"
LogStandardErrorAsError="$(CsWinRTGeneratorLogStandardErrorAsError)" />
@@ -573,6 +576,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
AdditionalArguments="@(CsWinRTGeneratorAdditionalArgument)"
StandardOutputImportance="$(CsWinRTGeneratorStandardOutputImportance)"
StandardErrorImportance="$(CsWinRTGeneratorStandardErrorImportance)"
+ MaxDegreesOfParallelism="$(CsWinRTGeneratorMaxDegreesOfParallelism)"
LogStandardErrorAsError="$(CsWinRTGeneratorLogStandardErrorAsError)" />
diff --git a/src/Tests/CSWinMDComponent/CSWinMDComponent.vcxproj.filters b/src/Tests/CSWinMDComponent/CSWinMDComponent.vcxproj.filters
new file mode 100644
index 0000000000..c1f12129b1
--- /dev/null
+++ b/src/Tests/CSWinMDComponent/CSWinMDComponent.vcxproj.filters
@@ -0,0 +1,32 @@
+
+
+
+
+ accd3aa8-1ba0-4223-9bbe-0c431709210b
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms
+
+
+ {926ab91d-31b4-48c3-b9a4-e681349f27f0}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/WinRT.Generator.Tasks/RunCsWinRTMergedProjectionGenerator.cs b/src/WinRT.Generator.Tasks/RunCsWinRTMergedProjectionGenerator.cs
index 307304eda4..f8c09e003f 100644
--- a/src/WinRT.Generator.Tasks/RunCsWinRTMergedProjectionGenerator.cs
+++ b/src/WinRT.Generator.Tasks/RunCsWinRTMergedProjectionGenerator.cs
@@ -86,6 +86,12 @@ public sealed class RunCsWinRTMergedProjectionGenerator : ToolTask
///
public bool WindowsUIXamlProjection { get; set; }
+ ///
+ /// Gets or sets the maximum number of parallel tasks the projection writer is allowed to dispatch.
+ ///
+ /// If not set, the default will match the number of available processor cores.
+ public int MaxDegreesOfParallelism { get; set; } = -1;
+
///
protected override string ToolName => "cswinrtprojectiongen.exe";
@@ -208,6 +214,8 @@ protected override string GenerateResponseFileCommands()
AppendResponseFileCommand(args, "--windows-ui-xaml-projection", "true");
}
+ AppendResponseFileCommand(args, "--max-degrees-of-parallelism", MaxDegreesOfParallelism.ToString());
+
// Add any additional arguments that are not statically known
foreach (ITaskItem additionalArgument in AdditionalArguments ?? [])
{
diff --git a/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs
index 46a03b6d76..2d7ea67300 100644
--- a/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs
+++ b/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs
@@ -10,7 +10,7 @@
namespace WindowsRuntime.InteropGenerator.Errors;
///
-/// A well known exception for the interop generator.
+/// A well-known exception for the interop generator.
///
internal sealed class WellKnownInteropException : Exception
{
diff --git a/src/WinRT.Interop.Generator/Errors/WellKnownInteropWarning.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropWarning.cs
index acab2a833e..ae8da9ef17 100644
--- a/src/WinRT.Interop.Generator/Errors/WellKnownInteropWarning.cs
+++ b/src/WinRT.Interop.Generator/Errors/WellKnownInteropWarning.cs
@@ -8,7 +8,7 @@
namespace WindowsRuntime.InteropGenerator.Errors;
///
-/// A well known warning for the interop generator.
+/// A well-known warning for the interop generator.
///
internal sealed class WellKnownInteropWarning
{
diff --git a/src/WinRT.Projection.Generator.Writer.TestRunner/WinRT.Projection.Generator.Writer.TestRunner.csproj b/src/WinRT.Projection.Generator.Writer.TestRunner/WinRT.Projection.Generator.Writer.TestRunner.csproj
deleted file mode 100644
index bcef2c24fc..0000000000
--- a/src/WinRT.Projection.Generator.Writer.TestRunner/WinRT.Projection.Generator.Writer.TestRunner.csproj
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- Exe
- net10.0
- 14.0
- enable
- WindowsRuntime.ProjectionGenerator.Writer.TestRunner
- $(NoWarn);CS1591;CS1573;CS1574;CS1712;CS1734;IDE0005;IDE0010;IDE0011;IDE0022;IDE0028;IDE0046;IDE0057;IDE0078;IDE0090;IDE0270;IDE0290;IDE0300;IDE0301;IDE0305;IDE0306;IDE0058;IDE0008;IDE0007;IDE0150;IDE0042;IDE0017;IDE0019;IDE0021;IDE0040;IDE0044;IDE0050;IDE0052;IDE0055;IDE0059;IDE0060;IDE0063;IDE0066;IDE0083;IDE0130;IDE0180
-
-
-
-
-
diff --git a/src/WinRT.Projection.Generator.Writer/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator.Writer/Generation/ProjectionGenerator.cs
deleted file mode 100644
index 422d99afe7..0000000000
--- a/src/WinRT.Projection.Generator.Writer/Generation/ProjectionGenerator.cs
+++ /dev/null
@@ -1,417 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Threading;
-using AsmResolver.DotNet;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// Orchestrates the projection generation. Mirrors the body of cswinrt::run in main.cpp.
-///
-internal sealed class ProjectionGenerator
-{
- private readonly Settings _settings;
- private readonly MetadataCache _cache;
- private readonly CancellationToken _token;
-
- public ProjectionGenerator(Settings settings, MetadataCache cache, CancellationToken token)
- {
- _settings = settings;
- _cache = cache;
- _token = token;
- }
-
- public void Run()
- {
- // Set the static cache reference for writers that need source-file paths
- CodeWriters.SetMetadataCache(_cache);
-
- // Find component activatable classes (component mode only)
- HashSet componentActivatable = new();
- Dictionary> componentByModule = new(StringComparer.Ordinal);
-
- if (_settings.Component)
- {
- foreach ((_, NamespaceMembers members) in _cache.Namespaces)
- {
- foreach (TypeDefinition type in members.Classes)
- {
- if (!_settings.Filter.Includes(type)) { continue; }
- if (TypeCategorization.HasAttribute(type, "Windows.Foundation.Metadata", "ActivatableAttribute") ||
- TypeCategorization.HasAttribute(type, "Windows.Foundation.Metadata", "StaticAttribute"))
- {
- _ = componentActivatable.Add(type);
- string moduleName = Path.GetFileNameWithoutExtension(_cache.GetSourcePath(type));
- if (!componentByModule.TryGetValue(moduleName, out HashSet? set))
- {
- set = new HashSet();
- componentByModule[moduleName] = set;
- }
- _ = set.Add(type);
- }
- }
- }
- }
-
- if (_settings.Verbose)
- {
- foreach (string p in _settings.Input)
- {
- Console.Out.WriteLine($"input: {p}");
- }
- Console.Out.WriteLine($"output: {_settings.OutputFolder}");
- }
-
- // Write GeneratedInterfaceIIDs file (mirrors main.cpp logic)
- bool iidWritten = false;
- if (!_settings.ReferenceProjection)
- {
- // Collect factory interfaces (Static/Activatable/Composable) referenced by included
- // classes globally. Their IIDs must be present in GeneratedInterfaceIIDs.cs even if
- // the filter excludes them, because static class members reference them.
- HashSet factoryInterfacesGlobal = new();
- foreach ((_, NamespaceMembers nsMembers) in _cache.Namespaces)
- {
- foreach (TypeDefinition type in nsMembers.Classes)
- {
- if (!_settings.Filter.Includes(type)) { continue; }
- // Skip mapped classes whose ABI surface is suppressed (e.g.
- // 'Windows.UI.Xaml.Interop.NotifyCollectionChangedEventArgs' maps to
- // 'System.Collections.Specialized.NotifyCollectionChangedEventArgs' with
- // EmitAbi=false). Their factory/statics interfaces should also be skipped.
- string clsNs = type.Namespace?.Value ?? string.Empty;
- string clsNm = type.Name?.Value ?? string.Empty;
- MappedType? clsMapped = MappedTypes.Get(clsNs, clsNm);
- if (clsMapped is not null && !clsMapped.EmitAbi) { continue; }
- foreach (KeyValuePair kv in AttributedTypes.Get(type, _cache))
- {
- TypeDefinition? facType = kv.Value.Type;
- if (facType is not null) { _ = factoryInterfacesGlobal.Add(facType); }
- }
- }
- }
-
- HashSet interfacesFromClassesEmitted = new();
- TypeWriter guidWriter = new(_settings, "ABI");
- CodeWriters.WriteInterfaceIidsBegin(guidWriter);
- // Iterate namespaces in sorted order (mirrors C++ std::map
- // iteration). Within each namespace, types are already sorted by SortMembersByName.
- // The sorted-by-namespace order produces the parent-before-child grouping in the
- // GeneratedInterfaceIIDs.cs output (e.g. Windows.ApplicationModel.* types before
- // Windows.ApplicationModel.Activation.* types).
- foreach ((string ns, NamespaceMembers members) in _cache.Namespaces.OrderBy(kvp => kvp.Key, System.StringComparer.Ordinal))
- {
- foreach (TypeDefinition type in members.Types)
- {
- bool isFactoryInterface = factoryInterfacesGlobal.Contains(type);
- if (!_settings.Filter.Includes(type) && !isFactoryInterface) { continue; }
- if (TypeCategorization.IsGeneric(type)) { continue; }
- string ns2 = type.Namespace?.Value ?? string.Empty;
- string nm2 = type.Name?.Value ?? string.Empty;
- MappedType? m = MappedTypes.Get(ns2, nm2);
- if (m is not null && !m.EmitAbi) { continue; }
- iidWritten = true;
- TypeCategory cat = TypeCategorization.GetCategory(type);
- switch (cat)
- {
- case TypeCategory.Delegate:
- CodeWriters.WriteIidGuidPropertyFromSignature(guidWriter, type);
- CodeWriters.WriteIidGuidPropertyFromType(guidWriter, type);
- break;
- case TypeCategory.Enum:
- CodeWriters.WriteIidGuidPropertyFromSignature(guidWriter, type);
- break;
- case TypeCategory.Interface:
- CodeWriters.WriteIidGuidPropertyFromType(guidWriter, type);
- break;
- case TypeCategory.Struct:
- CodeWriters.WriteIidGuidPropertyFromSignature(guidWriter, type);
- break;
- case TypeCategory.Class:
- CodeWriters.WriteIidGuidPropertyForClassInterfaces(guidWriter, type, interfacesFromClassesEmitted);
- break;
- }
- }
- }
- CodeWriters.WriteInterfaceIidsEnd(guidWriter);
- if (iidWritten)
- {
- guidWriter.FlushToFile(Path.Combine(_settings.OutputFolder, "GeneratedInterfaceIIDs.cs"));
- }
- }
-
- ConcurrentDictionary defaultInterfaceEntries = new();
- ConcurrentBag> exclusiveToInterfaceEntries = new();
- ConcurrentDictionary authoredTypeNameToMetadataMap = new();
- bool projectionFileWritten = false;
-
- // Process namespaces sequentially for now (C++ used task_group / parallel processing)
- foreach ((string ns, NamespaceMembers members) in _cache.Namespaces)
- {
- _token.ThrowIfCancellationRequested();
- bool wrote = ProcessNamespace(ns, members, componentActivatable, defaultInterfaceEntries, exclusiveToInterfaceEntries, authoredTypeNameToMetadataMap);
- if (wrote)
- {
- projectionFileWritten = true;
- }
- }
-
- // Component mode: write the WinRT_Module.cs file with activation factory entry points
- if (_settings.Component)
- {
- TextWriter wm = new();
- CodeWriters.WriteFileHeader(wm);
- CodeWriters.WriteModuleActivationFactory(wm, componentByModule);
- wm.FlushToFile(Path.Combine(_settings.OutputFolder, "WinRT_Module.cs"));
- projectionFileWritten = true;
- }
-
- // Write WindowsRuntimeDefaultInterfaces.cs and WindowsRuntimeExclusiveToInterfaces.cs
- if (defaultInterfaceEntries.Count > 0 && !_settings.ReferenceProjection)
- {
- List> sorted = new(defaultInterfaceEntries);
- sorted.Sort((a, b) => System.StringComparer.Ordinal.Compare(a.Key, b.Key));
- CodeWriters.WriteDefaultInterfacesClass(_settings, sorted);
- }
-
- if (!exclusiveToInterfaceEntries.IsEmpty && _settings.Component && !_settings.ReferenceProjection)
- {
- List> sorted = new(exclusiveToInterfaceEntries);
- sorted.Sort((a, b) => System.StringComparer.Ordinal.Compare(a.Key, b.Key));
- CodeWriters.WriteExclusiveToInterfacesClass(_settings, sorted);
- }
-
- // Write strings/ base files (ComInteropExtensions etc.)
- if (projectionFileWritten)
- {
- WriteBaseStrings();
- }
- }
-
- ///
- /// Processes a single namespace and writes its projection file. Returns whether a file was written.
- ///
- private bool ProcessNamespace(string ns, NamespaceMembers members, HashSet componentActivatable,
- ConcurrentDictionary defaultInterfaceEntries, ConcurrentBag> exclusiveToInterfaceEntries,
- ConcurrentDictionary authoredTypeNameToMetadataMap)
- {
- TypeWriter w = new(_settings, ns);
- w.WriteFileHeader();
-
- bool written = false;
-
- // Phase 1: TypeMapGroup assembly attributes
- if (!_settings.ReferenceProjection)
- {
- CodeWriters.WritePragmaDisableIL2026(w);
- foreach (TypeDefinition type in members.Types)
- {
- if (!_settings.Filter.Includes(type)) { continue; }
- if (TypeCategorization.IsGeneric(type)) { continue; }
- string ns2 = type.Namespace?.Value ?? string.Empty;
- string nm2 = type.Name?.Value ?? string.Empty;
- MappedType? m = MappedTypes.Get(ns2, nm2);
- if (m is not null && !m.EmitAbi) { continue; }
-
- TypeCategory cat = TypeCategorization.GetCategory(type);
- switch (cat)
- {
- case TypeCategory.Class:
- if (!TypeCategorization.IsStatic(type) && !TypeCategorization.IsAttributeType(type))
- {
- if (_settings.Component)
- {
- CodeWriters.WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(w, type);
- }
- else
- {
- CodeWriters.WriteWinRTComWrappersTypeMapGroupAssemblyAttribute(w, type, false);
- }
- }
- break;
- case TypeCategory.Delegate:
- CodeWriters.WriteWinRTComWrappersTypeMapGroupAssemblyAttribute(w, type, true);
- CodeWriters.WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(w, type);
- break;
- case TypeCategory.Enum:
- CodeWriters.WriteWinRTComWrappersTypeMapGroupAssemblyAttribute(w, type, true);
- CodeWriters.WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(w, type);
- break;
- case TypeCategory.Interface:
- CodeWriters.WriteWinRTIdicTypeMapGroupAssemblyAttribute(w, type);
- CodeWriters.WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(w, type);
- break;
- case TypeCategory.Struct:
- if (!TypeCategorization.IsApiContractType(type))
- {
- CodeWriters.WriteWinRTComWrappersTypeMapGroupAssemblyAttribute(w, type, true);
- CodeWriters.WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(w, type);
- }
- break;
- }
- }
- CodeWriters.WritePragmaRestoreIL2026(w);
- }
-
- // Phase 2: Projected types
- w.WriteBeginProjectedNamespace();
-
- foreach (TypeDefinition type in members.Types)
- {
- if (!_settings.Filter.Includes(type)) { continue; }
- string ns2 = type.Namespace?.Value ?? string.Empty;
- string nm2 = type.Name?.Value ?? string.Empty;
- // Skip generic types and mapped types (mirrors C++ logic)
- if (MappedTypes.Get(ns2, nm2) is not null || TypeCategorization.IsGeneric(type))
- {
- written = true;
- continue;
- }
-
- // Write the projected type per category
- TypeCategory category = TypeCategorization.GetCategory(type);
- CodeWriters.WriteType(w, type, category, _settings, _cache);
-
- if (category == TypeCategory.Class && !TypeCategorization.IsAttributeType(type))
- {
- CodeWriters.AddDefaultInterfaceEntry(w, type, defaultInterfaceEntries);
- CodeWriters.AddExclusiveToInterfaceEntries(w, type, exclusiveToInterfaceEntries);
- CodeWriters.AddMetadataTypeEntry(w, type, authoredTypeNameToMetadataMap);
- if (_settings.Component && componentActivatable.Contains(type))
- {
- CodeWriters.WriteFactoryClass(w, type);
- }
- }
- else if (category is TypeCategory.Delegate or TypeCategory.Enum or TypeCategory.Interface)
- {
- CodeWriters.AddMetadataTypeEntry(w, type, authoredTypeNameToMetadataMap);
- }
- else if (category == TypeCategory.Struct && !TypeCategorization.IsApiContractType(type))
- {
- CodeWriters.AddMetadataTypeEntry(w, type, authoredTypeNameToMetadataMap);
- }
-
- written = true;
- }
-
- w.WriteEndProjectedNamespace();
-
- if (!written)
- {
- return false;
- }
-
- // Phase 3: ABI types (when not reference projection)
- if (!_settings.ReferenceProjection)
- {
- // Collect factory interfaces (Static/Activatable/Composable) referenced by classes
- // included in this namespace. These must have their ABI Methods classes emitted even
- // when the filter excludes them, because the projected static class members dispatch
- // through them. Mirrors C++ behavior of always emitting factory interface ABI for
- // included classes.
- HashSet factoryInterfacesInThisNs = new();
- foreach (TypeDefinition type in members.Types)
- {
- if (!_settings.Filter.Includes(type)) { continue; }
- if (TypeCategorization.GetCategory(type) != TypeCategory.Class) { continue; }
- foreach (KeyValuePair kv in AttributedTypes.Get(type, _cache))
- {
- AttributedType info = kv.Value;
- TypeDefinition? facType = info.Type;
- if (facType is null) { continue; }
- // Only consider factory interfaces in the same namespace as we're processing.
- string facNs = facType.Namespace?.Value ?? string.Empty;
- if (facNs != ns) { continue; }
- _ = factoryInterfacesInThisNs.Add(facType);
- }
- }
-
- w.WriteBeginAbiNamespace();
- foreach (TypeDefinition type in members.Types)
- {
- bool isFactoryInterface = factoryInterfacesInThisNs.Contains(type);
- if (!_settings.Filter.Includes(type) && !isFactoryInterface) { continue; }
- if (TypeCategorization.IsGeneric(type)) { continue; }
- string ns2 = type.Namespace?.Value ?? string.Empty;
- string nm2 = type.Name?.Value ?? string.Empty;
- MappedType? m = MappedTypes.Get(ns2, nm2);
- if (m is not null && !m.EmitAbi) { continue; }
- if (TypeCategorization.IsApiContractType(type)) { continue; }
- if (TypeCategorization.IsAttributeType(type)) { continue; }
-
- TypeCategory category = TypeCategorization.GetCategory(type);
- CodeWriters.WriteAbiType(w, type, category, _settings);
- }
- w.WriteEndAbiNamespace();
- }
-
- // Phase 4: Custom additions to namespaces (mirrors C++ main.cpp)
- foreach ((string addNs, string resName) in Additions.All)
- {
- if (addNs == ns && _settings.AdditionFilter.Includes(ns))
- {
- using System.IO.Stream? stream = typeof(ProjectionWriter).Assembly.GetManifestResourceStream(resName);
- if (stream is null) { continue; }
- using System.IO.StreamReader reader = new(stream);
- string content = reader.ReadToEnd();
- w.Write(content);
- }
- }
-
- // Output to file
- string filename = ns + ".cs";
- string fullPath = Path.Combine(_settings.OutputFolder, filename);
- w.FlushToFile(fullPath);
- return true;
- }
-
- ///
- /// Writes the embedded string resources (e.g., ComInteropExtensions.cs, InspectableVftbl.cs)
- /// to the output folder.
- ///
- private void WriteBaseStrings()
- {
- Assembly asm = typeof(ProjectionWriter).Assembly;
- foreach (string resName in asm.GetManifestResourceNames())
- {
- // Resource names look like 'WindowsRuntime.ProjectionGenerator.Writer.Resources.Base.ComInteropExtensions.cs'
- if (!resName.Contains(".Resources.Base."))
- {
- continue;
- }
- // Skip ComInteropExtensions if Windows is not included
- string fileName = resName[(resName.IndexOf(".Resources.Base.", StringComparison.Ordinal) + ".Resources.Base.".Length)..];
- if (fileName == "ComInteropExtensions.cs" && !_settings.Filter.Includes("Windows"))
- {
- continue;
- }
-
- using Stream stream = asm.GetManifestResourceStream(resName)!;
- using StreamReader reader = new(stream);
- string content = reader.ReadToEnd();
-
- // For ComInteropExtensions, prepend the UAC_VERSION define
- if (fileName == "ComInteropExtensions.cs")
- {
- int uapContractVersion = _cache.Find("Windows.Graphics.Display.DisplayInformation") is not null ? 15 : 7;
- content = $"#define UAC_VERSION_{uapContractVersion}\n" + content;
- }
-
- // Mirror the C++ tool: every emitted .cs file gets the auto-generated header.
- // See main.cpp where 'write_file_header(ws);' is called before each base string is written.
- TextWriter headerWriter = new();
- CodeWriters.WriteFileHeader(headerWriter);
- string header = headerWriter.FlushToString();
-
- string outPath = Path.Combine(_settings.OutputFolder, fileName);
- File.WriteAllText(outPath, header + content);
- }
- }
-}
diff --git a/src/WinRT.Projection.Generator.Writer/Helpers/Additions.cs b/src/WinRT.Projection.Generator.Writer/Helpers/Additions.cs
deleted file mode 100644
index 168ffd98b0..0000000000
--- a/src/WinRT.Projection.Generator.Writer/Helpers/Additions.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Collections.Generic;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// Registry of namespace addition files. Mirrors the C++ strings::additions array.
-/// Each addition is the content of a .cs file that gets appended to the
-/// projection of the matching namespace.
-///
-internal static class Additions
-{
- ///
- /// (namespace, embedded-resource-manifest-name) pairs. The manifest-name resolves to a
- /// call.
- ///
- public static readonly IReadOnlyList<(string Namespace, string ResourceName)> All = new (string, string)[]
- {
- ("Microsoft.UI.Dispatching", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Microsoft.UI.Dispatching.Microsoft.UI.Dispatching.DispatcherQueueSynchronizationContext.cs"),
- ("Microsoft.UI.Xaml", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.CornerRadius.cs"),
- ("Microsoft.UI.Xaml", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.Duration.cs"),
- ("Microsoft.UI.Xaml", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.GridLength.cs"),
- ("Microsoft.UI.Xaml", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.SR.cs"),
- ("Microsoft.UI.Xaml", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.Thickness.cs"),
- ("Microsoft.UI.Xaml.Controls.Primitives", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Microsoft.UI.Xaml.Controls.Primitives.Microsoft.UI.Xaml.Controls.Primitives.GeneratorPosition.cs"),
- ("Microsoft.UI.Xaml.Media", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Microsoft.UI.Xaml.Media.Microsoft.UI.Xaml.Media.Matrix.cs"),
- ("Microsoft.UI.Xaml.Media.Animation", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Microsoft.UI.Xaml.Media.Animation.Microsoft.UI.Xaml.Media.Animation.KeyTime.cs"),
- ("Microsoft.UI.Xaml.Media.Animation", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Microsoft.UI.Xaml.Media.Animation.Microsoft.UI.Xaml.Media.Animation.RepeatBehavior.cs"),
- ("Microsoft.UI.Xaml.Media.Media3D", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Microsoft.UI.Xaml.Media.Media3D.Microsoft.UI.Xaml.Media.Media3D.Matrix3D.cs"),
- ("Windows.Storage", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.Storage.WindowsRuntimeStorageExtensions.cs"),
- ("Windows.UI", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Windows.UI.Color.cs"),
- ("Windows.UI.Xaml", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Xaml.Windows.System.DispatcherQueueSynchronizationContext.cs"),
- ("Windows.UI.Xaml", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.CornerRadius.cs"),
- ("Windows.UI.Xaml", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.Duration.cs"),
- ("Windows.UI.Xaml", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.GridLength.cs"),
- ("Windows.UI.Xaml", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.SR.cs"),
- ("Windows.UI.Xaml", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.Thickness.cs"),
- ("Windows.UI.Xaml.Controls.Primitives", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Xaml.Controls.Primitives.Windows.UI.Xaml.Controls.Primitives.GeneratorPosition.cs"),
- ("Windows.UI.Xaml.Media", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Xaml.Media.Windows.UI.Xaml.Media.Matrix.cs"),
- ("Windows.UI.Xaml.Media.Animation", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Xaml.Media.Animation.Windows.UI.Xaml.Media.Animation.KeyTime.cs"),
- ("Windows.UI.Xaml.Media.Animation", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Xaml.Media.Animation.Windows.UI.Xaml.Media.Animation.RepeatBehavior.cs"),
- ("Windows.UI.Xaml.Media.Media3D", "WindowsRuntime.ProjectionGenerator.Writer.Resources.Additions.Windows.UI.Xaml.Media.Media3D.Windows.UI.Xaml.Media.Media3D.Matrix3D.cs"),
- };
-}
diff --git a/src/WinRT.Projection.Generator.Writer/Helpers/AttributedTypes.cs b/src/WinRT.Projection.Generator.Writer/Helpers/AttributedTypes.cs
deleted file mode 100644
index 536f7d88e9..0000000000
--- a/src/WinRT.Projection.Generator.Writer/Helpers/AttributedTypes.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Collections.Generic;
-using AsmResolver.DotNet;
-using AsmResolver.DotNet.Signatures;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// Information about an [Activatable]/[Static]/[Composable] factory interface.
-/// Mirrors C++ attributed_type.
-///
-internal sealed class AttributedType
-{
- public TypeDefinition? Type { get; set; }
- public bool Activatable { get; set; }
- public bool Statics { get; set; }
- public bool Composable { get; set; }
- public bool Visible { get; set; }
-}
-
-///
-/// Helpers for activator/static/composable factory enumeration. Mirrors C++ get_attributed_types.
-///
-internal static class AttributedTypes
-{
- ///
- /// Returns the (interface_name, AttributedType) entries for the given runtime class type.
- /// Mirrors C++ get_attributed_types.
- ///
- public static IEnumerable> Get(TypeDefinition type, MetadataCache cache)
- {
- Dictionary result = new(System.StringComparer.Ordinal);
-
- for (int i = 0; i < type.CustomAttributes.Count; i++)
- {
- CustomAttribute attr = type.CustomAttributes[i];
- ITypeDefOrRef? attrType = attr.Constructor?.DeclaringType;
- if (attrType is null) { continue; }
- string ns = attrType.Namespace?.Value ?? string.Empty;
- string name = attrType.Name?.Value ?? string.Empty;
- if (ns != "Windows.Foundation.Metadata") { continue; }
-
- AttributedType info = new();
- switch (name)
- {
- case "ActivatableAttribute":
- info.Type = GetSystemType(attr, cache);
- info.Activatable = true;
- break;
- case "StaticAttribute":
- info.Type = GetSystemType(attr, cache);
- info.Statics = true;
- break;
- case "ComposableAttribute":
- info.Type = GetSystemType(attr, cache);
- info.Composable = true;
- info.Visible = GetVisibility(attr) == 2;
- break;
- default:
- continue;
- }
-
- string key = info.Type?.Name?.Value ?? string.Empty;
- result[key] = info;
- }
-
- // C++ uses std::map which iterates in sorted-by-key order.
- // The key is the factory-interface type name (e.g. 'IButtonUtilsStatic'), so the inheritance
- // order in the generated code is alphabetical by interface name.
- SortedDictionary sorted = new(System.StringComparer.Ordinal);
- foreach (KeyValuePair kv in result)
- {
- sorted[kv.Key] = kv.Value;
- }
- return sorted;
- }
-
- ///
- /// Extracts the System.Type argument from the attribute's fixed args.
- ///
- private static TypeDefinition? GetSystemType(CustomAttribute attr, MetadataCache cache)
- {
- if (attr.Signature is null) { return null; }
- for (int i = 0; i < attr.Signature.FixedArguments.Count; i++)
- {
- CustomAttributeArgument arg = attr.Signature.FixedArguments[i];
- // For System.Type args in WinMD, the value is typically a TypeSignature
- if (arg.Element is TypeSignature sig)
- {
- string typeName = sig.FullName ?? string.Empty;
- TypeDefinition? td = cache.Find(typeName);
- if (td is not null) { return td; }
- }
- else if (arg.Element is string s)
- {
- TypeDefinition? td = cache.Find(s);
- if (td is not null) { return td; }
- }
- }
- return null;
- }
-
- ///
- /// Extracts the visibility int from a [ComposableAttribute] (the enum value arg).
- ///
- private static int GetVisibility(CustomAttribute attr)
- {
- if (attr.Signature is { FixedArguments: [_, { Element: int e }, ..] })
- {
- return e;
- }
- return 0;
- }
-}
diff --git a/src/WinRT.Projection.Generator.Writer/Helpers/ContractPlatforms.cs b/src/WinRT.Projection.Generator.Writer/Helpers/ContractPlatforms.cs
deleted file mode 100644
index e21c0a4bd7..0000000000
--- a/src/WinRT.Projection.Generator.Writer/Helpers/ContractPlatforms.cs
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Collections.Generic;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// Maps Windows Runtime API contracts to their first available Windows SDK platform version.
-/// Mirrors the C++ contract_mappings table in helpers.h.
-///
-internal static class ContractPlatforms
-{
- private static readonly Dictionary> s_table = Build();
-
- ///
- /// Returns the platform version (e.g., "10.0.17763.0") that introduced the given contract version,
- /// or empty string if not found.
- ///
- public static string GetPlatform(string contractName, int contractVersion)
- {
- if (!s_table.TryGetValue(contractName, out List<(int Version, string Platform)>? versions))
- {
- return string.Empty;
- }
- // Find the first version >= contractVersion (mirrors std::lower_bound)
- for (int i = 0; i < versions.Count; i++)
- {
- if (versions[i].Version >= contractVersion)
- {
- return versions[i].Platform;
- }
- }
- return string.Empty;
- }
-
- private static Dictionary> Build()
- {
- Dictionary> t = new();
-
- void Add(string name, params (int v, string p)[] vs)
- {
- List<(int, string)> list = new();
- foreach (var (v, p) in vs) { list.Add((v, p)); }
- t[name] = list;
- }
-
- Add("Windows.AI.MachineLearning.MachineLearningContract",
- (1, "10.0.17763.0"), (2, "10.0.18362.0"), (3, "10.0.19041.0"), (4, "10.0.20348.0"), (5, "10.0.22000.0"));
- Add("Windows.AI.MachineLearning.Preview.MachineLearningPreviewContract",
- (1, "10.0.17134.0"), (2, "10.0.17763.0"));
- Add("Windows.ApplicationModel.Calls.Background.CallsBackgroundContract",
- (1, "10.0.17763.0"), (2, "10.0.18362.0"), (3, "10.0.20348.0"), (4, "10.0.22621.0"));
- Add("Windows.ApplicationModel.Calls.CallsPhoneContract",
- (4, "10.0.17763.0"), (5, "10.0.18362.0"), (6, "10.0.20348.0"), (7, "10.0.22621.0"));
- Add("Windows.ApplicationModel.Calls.CallsVoipContract",
- (1, "10.0.10586.0"), (2, "10.0.16299.0"), (3, "10.0.17134.0"), (4, "10.0.17763.0"), (5, "10.0.26100.0"));
- Add("Windows.ApplicationModel.CommunicationBlocking.CommunicationBlockingContract",
- (2, "10.0.17763.0"));
- Add("Windows.ApplicationModel.SocialInfo.SocialInfoContract",
- (1, "10.0.14393.0"), (2, "10.0.15063.0"));
- Add("Windows.ApplicationModel.StartupTaskContract",
- (2, "10.0.16299.0"), (3, "10.0.17134.0"));
- Add("Windows.Devices.Custom.CustomDeviceContract",
- (1, "10.0.16299.0"));
- Add("Windows.Devices.DevicesLowLevelContract",
- (2, "10.0.14393.0"), (3, "10.0.15063.0"));
- Add("Windows.Devices.Printers.PrintersContract",
- (1, "10.0.10586.0"));
- Add("Windows.Devices.SmartCards.SmartCardBackgroundTriggerContract",
- (3, "10.0.16299.0"));
- Add("Windows.Devices.SmartCards.SmartCardEmulatorContract",
- (5, "10.0.16299.0"), (6, "10.0.17763.0"));
- Add("Windows.Foundation.FoundationContract",
- (1, "10.0.10240.0"), (2, "10.0.10586.0"), (3, "10.0.15063.0"), (4, "10.0.19041.0"));
- Add("Windows.Foundation.UniversalApiContract",
- (1, "10.0.10240.0"), (2, "10.0.10586.0"), (3, "10.0.14393.0"), (4, "10.0.15063.0"), (5, "10.0.16299.0"),
- (6, "10.0.17134.0"), (7, "10.0.17763.0"), (8, "10.0.18362.0"), (10, "10.0.19041.0"), (12, "10.0.20348.0"),
- (14, "10.0.22000.0"), (15, "10.0.22621.0"), (19, "10.0.26100.0"));
- Add("Windows.Foundation.VelocityIntegration.VelocityIntegrationContract",
- (1, "10.0.17134.0"));
- Add("Windows.Gaming.XboxLive.StorageApiContract",
- (1, "10.0.16299.0"));
- Add("Windows.Graphics.Printing3D.Printing3DContract",
- (2, "10.0.10586.0"), (3, "10.0.14393.0"), (4, "10.0.16299.0"));
- Add("Windows.Networking.Connectivity.WwanContract",
- (1, "10.0.10240.0"), (2, "10.0.17134.0"), (3, "10.0.26100.0"));
- Add("Windows.Networking.Sockets.ControlChannelTriggerContract",
- (3, "10.0.17763.0"));
- Add("Windows.Security.Isolation.IsolatedWindowsEnvironmentContract",
- (1, "10.0.19041.0"), (3, "10.0.20348.0"), (4, "10.0.22621.0"), (5, "10.0.26100.0"));
- Add("Windows.Services.Maps.GuidanceContract",
- (3, "10.0.17763.0"));
- Add("Windows.Services.Maps.LocalSearchContract",
- (4, "10.0.17763.0"));
- Add("Windows.Services.Store.StoreContract",
- (1, "10.0.14393.0"), (2, "10.0.15063.0"), (3, "10.0.17134.0"), (4, "10.0.17763.0"));
- Add("Windows.Services.TargetedContent.TargetedContentContract",
- (1, "10.0.15063.0"));
- Add("Windows.Storage.Provider.CloudFilesContract",
- (4, "10.0.19041.0"), (6, "10.0.20348.0"), (7, "10.0.22621.0"));
- Add("Windows.System.Profile.ProfileHardwareTokenContract",
- (1, "10.0.14393.0"));
- Add("Windows.System.Profile.ProfileRetailInfoContract",
- (1, "10.0.20348.0"));
- Add("Windows.System.Profile.ProfileSharedModeContract",
- (1, "10.0.14393.0"), (2, "10.0.15063.0"));
- Add("Windows.System.Profile.SystemManufacturers.SystemManufacturersContract",
- (3, "10.0.17763.0"));
- Add("Windows.System.SystemManagementContract",
- (6, "10.0.17763.0"), (7, "10.0.19041.0"));
- Add("Windows.UI.UIAutomation.UIAutomationContract",
- (1, "10.0.20348.0"), (2, "10.0.22000.0"));
- Add("Windows.UI.ViewManagement.ViewManagementViewScalingContract",
- (1, "10.0.14393.0"));
- Add("Windows.UI.Xaml.Core.Direct.XamlDirectContract",
- (1, "10.0.17763.0"), (2, "10.0.18362.0"), (3, "10.0.20348.0"), (5, "10.0.22000.0"));
-
- return t;
- }
-}
-
-///
-/// Static lookup for namespaces with addition files. Mirrors C++ has_addition_to_type.
-///
-internal static class AdditionTypes
-{
- private static readonly Dictionary> s_table = new(System.StringComparer.Ordinal)
- {
- { "Microsoft.UI.Xaml", new(System.StringComparer.Ordinal) { "Thickness" } },
- { "Microsoft.UI.Xaml.Controls.Primitives", new(System.StringComparer.Ordinal) { "GeneratorPosition" } },
- { "Microsoft.UI.Xaml.Media", new(System.StringComparer.Ordinal) { "Matrix" } },
- { "Microsoft.UI.Xaml.Media.Animation", new(System.StringComparer.Ordinal) { "KeyTime" } },
- { "Windows.UI", new(System.StringComparer.Ordinal) { "Color" } },
- { "Windows.UI.Xaml", new(System.StringComparer.Ordinal) { "Thickness" } },
- { "Windows.UI.Xaml.Controls.Primitives", new(System.StringComparer.Ordinal) { "GeneratorPosition" } },
- { "Windows.UI.Xaml.Media", new(System.StringComparer.Ordinal) { "Matrix" } },
- { "Windows.UI.Xaml.Media.Animation", new(System.StringComparer.Ordinal) { "KeyTime" } },
- };
-
- /// Mirrors C++ has_addition_to_type.
- public static bool HasAdditionToType(string typeNamespace, string typeName)
- {
- if (s_table.TryGetValue(typeNamespace, out HashSet? names) && names.Contains(typeName))
- {
- return true;
- }
- return false;
- }
-}
diff --git a/src/WinRT.Projection.Generator.Writer/Helpers/GuidGenerator.cs b/src/WinRT.Projection.Generator.Writer/Helpers/GuidGenerator.cs
deleted file mode 100644
index 0535031628..0000000000
--- a/src/WinRT.Projection.Generator.Writer/Helpers/GuidGenerator.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Security.Cryptography;
-using System.Text;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// Mirrors the C++ guid_generator.h. Generates Windows Runtime parameterized GUIDs (PIIDs)
-/// using the WinRT-defined namespace GUID (d57af411-737b-c042-abae-878b1e16adee) and SHA-1.
-///
-internal static class GuidGenerator
-{
- // The WinRT namespace GUID, in the same byte order as cppwinrt's namespace_guid.
- // Per cppwinrt: { 0xd57af411, 0x737b, 0xc042, { 0xab, 0xae, 0x87, 0x8b, 0x1e, 0x16, 0xad, 0xee } }
- // Layout (little-endian format): bytes are { 0x11, 0xf4, 0x7a, 0xd5, 0x7b, 0x73, 0x42, 0xc0, 0xab, 0xae, 0x87, 0x8b, 0x1e, 0x16, 0xad, 0xee }
- private static readonly byte[] s_namespaceBytes =
- {
- 0x11, 0xf4, 0x7a, 0xd5,
- 0x7b, 0x73,
- 0x42, 0xc0,
- 0xab, 0xae, 0x87, 0x8b, 0x1e, 0x16, 0xad, 0xee
- };
-
- ///
- /// Generates a GUID for the given Windows Runtime parameterized type signature.
- ///
- /// The parameterized signature (e.g., "pinterface({...};Boolean)").
- /// The resulting GUID.
- public static Guid Generate(string signature)
- {
- byte[] sigBytes = Encoding.UTF8.GetBytes(signature);
- byte[] buffer = new byte[s_namespaceBytes.Length + sigBytes.Length];
- Array.Copy(s_namespaceBytes, buffer, s_namespaceBytes.Length);
- Array.Copy(sigBytes, 0, buffer, s_namespaceBytes.Length, sigBytes.Length);
-
- byte[] hash = SHA1.HashData(buffer);
-
- // Take first 16 bytes
- byte[] guidBytes = new byte[16];
- Array.Copy(hash, guidBytes, 16);
-
- // Endian swap (Data1, Data2, Data3 are in big-endian in the SHA1 hash; .NET Guid expects little-endian)
- Swap(guidBytes, 0, 3);
- Swap(guidBytes, 1, 2);
- Swap(guidBytes, 4, 5);
- Swap(guidBytes, 6, 7);
-
- // Set named GUID fields: version 5, variant RFC4122
- guidBytes[7] = (byte)((guidBytes[7] & 0x0f) | 0x50);
- guidBytes[8] = (byte)((guidBytes[8] & 0x3f) | 0x80);
-
- return new Guid(guidBytes);
- }
-
- private static void Swap(byte[] arr, int a, int b)
- {
- (arr[a], arr[b]) = (arr[b], arr[a]);
- }
-}
diff --git a/src/WinRT.Projection.Generator.Writer/Helpers/Helpers.cs b/src/WinRT.Projection.Generator.Writer/Helpers/Helpers.cs
deleted file mode 100644
index 63271bad1b..0000000000
--- a/src/WinRT.Projection.Generator.Writer/Helpers/Helpers.cs
+++ /dev/null
@@ -1,326 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Collections.Generic;
-using AsmResolver.DotNet;
-using AsmResolver.DotNet.Collections;
-using AsmResolver.DotNet.Signatures;
-using AsmResolver.PE.DotNet.Metadata.Tables;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// General-purpose helpers from C++ helpers.h and code_writers.h.
-///
-internal static class Helpers
-{
- private static readonly HashSet s_csharpKeywords = new(System.StringComparer.Ordinal)
- {
- "abstract","as","base","bool","break","byte","case","catch","char","checked","class","const","continue",
- "decimal","default","delegate","do","double","else","enum","event","explicit","extern","false","finally",
- "fixed","float","for","foreach","goto","if","implicit","in","int","interface","internal","is","lock","long",
- "namespace","new","null","object","operator","out","override","params","private","protected","public",
- "readonly","ref","return","sbyte","sealed","short","sizeof","stackalloc","static","string","struct","switch",
- "this","throw","true","try","typeof","uint","ulong","unchecked","unsafe","ushort","using","virtual","void",
- "volatile","while"
- };
-
- /// Mirrors C++ is_keyword.
- public static bool IsKeyword(string s) => s_csharpKeywords.Contains(s);
-
- /// Mirrors C++ write_escaped_identifier: prefix C# keywords with @.
- public static void WriteEscapedIdentifier(TextWriter w, string identifier)
- {
- if (IsKeyword(identifier))
- {
- w.Write("@");
- }
- w.Write(identifier);
- }
-
- /// Mirrors C++ internal_accessibility.
- public static string InternalAccessibility(Settings settings) =>
- settings.Internal || settings.Embedded ? "internal" : "public";
-
- ///
- /// Returns the type referenced by an [ExclusiveTo] attribute on the given interface,
- /// or null if the interface is not exclusive-to anything (or the attribute argument
- /// can't be resolved). Mirrors the C++ logic that walks an interface's
- /// Windows.Foundation.Metadata.ExclusiveToAttribute and reads its System.Type argument.
- ///
- public static TypeDefinition? GetExclusiveToType(TypeDefinition iface, MetadataCache cache)
- {
- for (int i = 0; i < iface.CustomAttributes.Count; i++)
- {
- CustomAttribute attr = iface.CustomAttributes[i];
- ITypeDefOrRef? attrType = attr.Constructor?.DeclaringType;
- if (attrType is null) { continue; }
- string ns = attrType.Namespace?.Value ?? string.Empty;
- string name = attrType.Name?.Value ?? string.Empty;
- if (ns != "Windows.Foundation.Metadata" || name != "ExclusiveToAttribute") { continue; }
- if (attr.Signature is null) { continue; }
- for (int j = 0; j < attr.Signature.FixedArguments.Count; j++)
- {
- CustomAttributeArgument arg = attr.Signature.FixedArguments[j];
- if (arg.Element is TypeSignature sig)
- {
- string typeName = sig.FullName ?? string.Empty;
- TypeDefinition? td = cache.Find(typeName);
- if (td is not null) { return td; }
- }
- else if (arg.Element is AsmResolver.Utf8String s)
- {
- TypeDefinition? td = cache.Find(s.Value);
- if (td is not null) { return td; }
- }
- else if (arg.Element is string ss)
- {
- TypeDefinition? td = cache.Find(ss);
- if (td is not null) { return td; }
- }
- }
- }
- return null;
- }
-
- /// Strip everything from a backtick onwards (C++ write_code behavior for type names).
- public static string StripBackticks(string typeName)
- {
- int idx = typeName.IndexOf('`');
- return idx >= 0 ? typeName.Substring(0, idx) : typeName;
- }
-
- /// Returns true if the type has the named CustomAttribute.
- public static bool HasAttribute(IHasCustomAttribute member, string ns, string name)
- => TypeCategorization.HasAttribute(member, ns, name);
-
- /// Returns the matching CustomAttribute, or null.
- public static CustomAttribute? GetAttribute(IHasCustomAttribute member, string ns, string name)
- => TypeCategorization.GetAttribute(member, ns, name);
-
- /// Returns true if the InterfaceImpl is the [Default] interface.
- public static bool IsDefaultInterface(InterfaceImplementation impl)
- => HasAttribute(impl, "Windows.Foundation.Metadata", "DefaultAttribute");
-
- /// Returns true if the InterfaceImpl is [Overridable].
- public static bool IsOverridable(InterfaceImplementation impl)
- => HasAttribute(impl, "Windows.Foundation.Metadata", "OverridableAttribute");
-
- /// True if a method is the special "remove_xxx" event remover (mirrors C++ is_remove_overload).
- public static bool IsRemoveOverload(MethodDefinition m)
- => m.IsSpecialName && (m.Name?.Value?.StartsWith("remove_", System.StringComparison.Ordinal) == true);
-
- /// Method has [NoExceptionAttribute] or is a remove overload.
- public static bool IsNoExcept(MethodDefinition m)
- => IsRemoveOverload(m) || HasAttribute(m, "Windows.Foundation.Metadata", "NoExceptionAttribute");
-
- /// Property has [NoExceptionAttribute].
- public static bool IsNoExcept(PropertyDefinition p)
- => HasAttribute(p, "Windows.Foundation.Metadata", "NoExceptionAttribute");
-
- /// Mirrors C++ get_default_interface: returns the [Default] interface.
- public static ITypeDefOrRef? GetDefaultInterface(TypeDefinition type)
- {
- foreach (InterfaceImplementation impl in type.Interfaces)
- {
- if (IsDefaultInterface(impl) && impl.Interface is not null)
- {
- return impl.Interface;
- }
- }
- return null;
- }
-
- /// Mirrors C++ get_property_methods: returns (getter, setter) for a property.
- public static (MethodDefinition? Getter, MethodDefinition? Setter) GetPropertyMethods(PropertyDefinition prop)
- {
- return (prop.GetMethod, prop.SetMethod);
- }
-
- /// Mirrors C++ get_event_methods: returns (add, remove) for an event.
- public static (MethodDefinition? Add, MethodDefinition? Remove) GetEventMethods(EventDefinition evt)
- {
- return (evt.AddMethod, evt.RemoveMethod);
- }
-
- /// Mirrors C++ get_delegate_invoke: returns the Invoke method of a delegate type.
- public static MethodDefinition? GetDelegateInvoke(TypeDefinition type)
- {
- foreach (MethodDefinition m in type.Methods)
- {
- if (m.IsSpecialName && m.Name == "Invoke")
- {
- return m;
- }
- }
- return null;
- }
-
- /// Get the (uint32_t arg) value out of a [ContractVersionAttribute] (mirrors C++ get_contract_version).
- public static int? GetContractVersion(TypeDefinition type)
- {
- CustomAttribute? attr = GetAttribute(type, "Windows.Foundation.Metadata", "ContractVersionAttribute");
- if (attr is null) { return null; }
- // C++ reads index 1 - the second positional arg
- if (attr.Signature is not null && attr.Signature.FixedArguments.Count > 1)
- {
- object? v = attr.Signature.FixedArguments[1].Element;
- if (v is uint u) { return (int)u; }
- if (v is int i) { return i; }
- }
- return null;
- }
-
- /// Get the (uint32_t arg) value out of a [VersionAttribute] (mirrors C++ get_version).
- public static int? GetVersion(TypeDefinition type)
- {
- CustomAttribute? attr = GetAttribute(type, "Windows.Foundation.Metadata", "VersionAttribute");
- if (attr is null) { return null; }
- if (attr.Signature is not null && attr.Signature.FixedArguments.Count > 0)
- {
- object? v = attr.Signature.FixedArguments[0].Element;
- if (v is uint u) { return (int)u; }
- if (v is int i) { return i; }
- }
- return null;
- }
-
- /// Mirrors C++ has_default_constructor.
- public static bool HasDefaultConstructor(TypeDefinition type)
- {
- foreach (MethodDefinition m in type.Methods)
- {
- if (m.IsRuntimeSpecialName && m.Name == ".ctor" && m.Parameters.Count == 0)
- {
- return true;
- }
- }
- return false;
- }
-
- /// Mirrors C++ is_constructor.
- public static bool IsConstructor(MethodDefinition m)
- => m.IsRuntimeSpecialName && m.Name == ".ctor";
-
- /// Mirrors C++ is_special.
- public static bool IsSpecial(MethodDefinition m)
- => m.IsSpecialName || m.IsRuntimeSpecialName;
-}
-
-///
-/// Mirrors C++ method_signature: enumerates parameters and return value of a method.
-///
-internal sealed class MethodSig
-{
- public MethodDefinition Method { get; }
- public List Params { get; }
- public ParameterDefinition? ReturnParam { get; }
-
- public MethodSig(MethodDefinition method) : this(method, null) { }
-
- public MethodSig(MethodDefinition method, AsmResolver.DotNet.Signatures.GenericContext? genCtx)
- {
- Method = method;
- Params = new List(method.Parameters.Count);
- // The return parameter is the one with sequence 0 (if any)
- ReturnParam = null;
- foreach (ParameterDefinition p in method.ParameterDefinitions)
- {
- if (p.Sequence == 0)
- {
- ReturnParam = p;
- break;
- }
- }
-
- // Iterate signature parameters
- if (method.Signature is MethodSignature sig)
- {
- _substitutedReturnType = genCtx is not null && sig.ReturnType is not null
- ? sig.ReturnType.InstantiateGenericTypes(genCtx.Value)
- : sig.ReturnType;
- for (int i = 0; i < sig.ParameterTypes.Count; i++)
- {
- TypeSignature pt = sig.ParameterTypes[i];
- if (genCtx is not null) { pt = pt.InstantiateGenericTypes(genCtx.Value); }
- Params.Add(new ParamInfo(method.Parameters[i], pt));
- }
- }
- }
-
-#pragma warning disable IDE0032 // Use auto property — manual backing field needed for substituted return type
- private readonly TypeSignature? _substitutedReturnType;
-#pragma warning restore IDE0032
-
- public TypeSignature? ReturnType => _substitutedReturnType is TypeSignature t &&
- t is not CorLibTypeSignature { ElementType: ElementType.Void }
- ? _substitutedReturnType
- : null;
-
- public string ReturnParamName(string defaultName = "__return_value__")
- => ReturnParam?.Name?.Value ?? defaultName;
-}
-
-/// One param: links the parameter definition to its signature type.
-internal sealed record ParamInfo(Parameter Parameter, TypeSignature Type);
-
-/// Param category mirroring C++ param_category.
-internal enum ParamCategory
-{
- In,
- Ref,
- Out,
- PassArray,
- FillArray,
- ReceiveArray,
-}
-
-/// Helpers for parameter analysis.
-internal static class ParamHelpers
-{
- public static ParamCategory GetParamCategory(ParamInfo p)
- {
- bool isArray = p.Type is SzArrayTypeSignature;
- bool isOut = p.Parameter.Definition?.IsOut == true;
- bool isIn = p.Parameter.Definition?.IsIn == true;
- // Check both the captured signature type and the parameter's own type (handles cases where
- // the signature is wrapped in a ByReferenceTypeSignature only on one side after substitution).
- // Also peel custom modifiers (e.g. modreq[InAttribute]) which can hide a ByRef beneath.
- bool isByRef = IsByRefType(p.Type) || IsByRefType(p.Parameter.ParameterType);
- // If byref and underlying is an array, treat as array param (PassArray/ReceiveArray/FillArray)
- // based on in/out flags. WinRT metadata represents 'out byte[]' as 'byte[]&' with [out].
- bool isByRefArray = isByRef && PeelByRefAndCustomModifiers(p.Type) is SzArrayTypeSignature;
- if (isArray || isByRefArray)
- {
- if (isIn) { return ParamCategory.PassArray; }
- if (isByRef && isOut) { return ParamCategory.ReceiveArray; }
- return ParamCategory.FillArray;
- }
- if (isOut) { return ParamCategory.Out; }
- if (isByRef) { return ParamCategory.Ref; }
- return ParamCategory.In;
- }
-
- private static TypeSignature? PeelByRefAndCustomModifiers(TypeSignature? sig)
- {
- TypeSignature? cur = sig;
- while (true)
- {
- if (cur is CustomModifierTypeSignature cm) { cur = cm.BaseType; continue; }
- if (cur is ByReferenceTypeSignature br) { cur = br.BaseType; continue; }
- break;
- }
- return cur;
- }
-
- private static bool IsByRefType(TypeSignature? sig)
- {
- // Strip custom modifiers (e.g. modreq[InAttribute] or modopt[IsExternalInit]) before checking byref.
- TypeSignature? cur = sig;
- while (cur is CustomModifierTypeSignature cm)
- {
- cur = cm.BaseType;
- }
- return cur is ByReferenceTypeSignature;
- }
-}
diff --git a/src/WinRT.Projection.Generator.Writer/Helpers/Settings.cs b/src/WinRT.Projection.Generator.Writer/Helpers/Settings.cs
deleted file mode 100644
index 1246a17de8..0000000000
--- a/src/WinRT.Projection.Generator.Writer/Helpers/Settings.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Collections.Generic;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// Mirrors the C++ settings_type from settings.h.
-///
-internal sealed class Settings
-{
- public HashSet Input { get; } = new();
- public string OutputFolder { get; set; } = string.Empty;
- public bool Verbose { get; set; }
- public HashSet Include { get; } = new();
- public HashSet Exclude { get; } = new();
- public HashSet AdditionExclude { get; } = new();
- public TypeFilter Filter { get; set; } = TypeFilter.Empty;
- public TypeFilter AdditionFilter { get; set; } = TypeFilter.Empty;
- public bool NetstandardCompat { get; set; }
- public bool Component { get; set; }
- public bool Internal { get; set; }
- public bool Embedded { get; set; }
- public bool PublicEnums { get; set; }
- public bool PublicExclusiveTo { get; set; }
- public bool IdicExclusiveTo { get; set; }
- public bool ReferenceProjection { get; set; }
-}
diff --git a/src/WinRT.Projection.Generator.Writer/Helpers/TypeFilter.cs b/src/WinRT.Projection.Generator.Writer/Helpers/TypeFilter.cs
deleted file mode 100644
index 4a3d7bde12..0000000000
--- a/src/WinRT.Projection.Generator.Writer/Helpers/TypeFilter.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using AsmResolver;
-using AsmResolver.DotNet;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// Mirrors the C++ winmd::reader::filter include/exclude logic.
-/// Filters use longest-prefix-match semantics: type/namespace is checked against
-/// each prefix in include/exclude lists, and the longest matching prefix wins.
-///
-internal readonly struct TypeFilter
-{
- private readonly List _include;
- private readonly List _exclude;
-
- public static TypeFilter Empty { get; } = new(Array.Empty(), Array.Empty());
-
- public TypeFilter(IEnumerable include, IEnumerable exclude)
- {
- _include = include.OrderByDescending(s => s.Length).ToList();
- _exclude = exclude.OrderByDescending(s => s.Length).ToList();
- }
-
- ///
- /// Whether this filter matches everything by default (no include rules).
- ///
- public bool MatchesAllByDefault => _include == null || _include.Count == 0;
-
- ///
- /// Returns whether the given type name passes the include/exclude filter.
- /// Mirrors the C++ winmd::reader::filter algorithm: rules are sorted by descending
- /// prefix length (with includes winning ties over excludes); the first matching rule wins.
- /// Match semantics split the full type name into namespace.typeName parts and treat
- /// the rule prefix as either a namespace-prefix or a namespace + typename-prefix.
- ///
- public bool Includes(string fullName)
- {
- if ((_include == null || _include.Count == 0) && (_exclude == null || _exclude.Count == 0))
- {
- return true;
- }
-
- // Split into namespace + typename at the LAST '.'. Mirrors C++:
- // auto position = type.find_last_of('.');
- // includes(type.substr(0, position), type.substr(position + 1));
- // When there's no '.', position = npos, and both substrings collapse to the full string.
- int dot = fullName.LastIndexOf('.');
- string ns;
- string name;
- if (dot < 0)
- {
- ns = fullName;
- name = fullName;
- }
- else
- {
- ns = fullName.Substring(0, dot);
- name = fullName.Substring(dot + 1);
- }
-
- // Walk both lists in descending length order; on tie, includes win over excludes.
- // (Both _include and _exclude are pre-sorted by descending length in the constructor.)
- int incIdx = 0;
- int excIdx = 0;
- while (true)
- {
- string? incRule = (_include != null && incIdx < _include.Count) ? _include[incIdx] : null;
- string? excRule = (_exclude != null && excIdx < _exclude.Count) ? _exclude[excIdx] : null;
- if (incRule == null && excRule == null) { break; }
-
- bool pickInclude;
- if (incRule == null)
- {
- pickInclude = false;
- }
- else if (excRule == null)
- {
- pickInclude = true;
- }
- else
- {
- // Equal length: include wins (matches C++ sort key 'pair{size, !isInclude}' descending).
- pickInclude = incRule.Length >= excRule.Length;
- }
-
- string rule = pickInclude ? incRule! : excRule!;
- if (Match(ns, name, rule))
- {
- return pickInclude;
- }
- if (pickInclude) { incIdx++; } else { excIdx++; }
- }
-
- // No rule matched. If we have any include rules, default-exclude; else default-include.
- return _include == null || _include.Count == 0;
- }
-
- /// Mirrors C++ filter::match.
- private static bool Match(string typeNamespace, string typeName, string rule)
- {
- if (rule.Length <= typeNamespace.Length)
- {
- return typeNamespace.StartsWith(rule, StringComparison.Ordinal);
- }
- if (!rule.StartsWith(typeNamespace, StringComparison.Ordinal))
- {
- return false;
- }
- if (rule[typeNamespace.Length] != '.')
- {
- return false;
- }
- // The rest of the rule (after 'namespace.') is matched as a prefix against typeName.
- string rest = rule.Substring(typeNamespace.Length + 1);
- return typeName.StartsWith(rest, StringComparison.Ordinal);
- }
-
- public bool Includes(TypeDefinition type)
- {
- return Includes(GetFullName(type));
- }
-
- public static string GetFullName(TypeDefinition type)
- {
- Utf8String? ns = type.Namespace;
- Utf8String? name = type.Name;
- if (ns is null || ns.Length == 0)
- {
- return name?.Value ?? string.Empty;
- }
- return ns + "." + (name?.Value ?? string.Empty);
- }
-}
diff --git a/src/WinRT.Projection.Generator.Writer/Metadata/TypeSemantics.cs b/src/WinRT.Projection.Generator.Writer/Metadata/TypeSemantics.cs
deleted file mode 100644
index 8cac4af294..0000000000
--- a/src/WinRT.Projection.Generator.Writer/Metadata/TypeSemantics.cs
+++ /dev/null
@@ -1,192 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Collections.Generic;
-using AsmResolver.DotNet;
-using AsmResolver.DotNet.Signatures;
-using AsmResolver.PE.DotNet.Metadata.Tables;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// Mirrors C++ fundamental_type in helpers.h.
-///
-internal enum FundamentalType
-{
- Boolean,
- Char,
- Int8,
- UInt8,
- Int16,
- UInt16,
- Int32,
- UInt32,
- Int64,
- UInt64,
- Float,
- Double,
- String,
-}
-
-///
-/// Discriminated union of the type semantics from C++ type_semantics.
-///
-internal abstract record TypeSemantics
-{
- private TypeSemantics() { }
-
- public sealed record Fundamental(FundamentalType Type) : TypeSemantics;
- public sealed record Object_ : TypeSemantics;
- public sealed record Guid_ : TypeSemantics;
- public sealed record Type_ : TypeSemantics;
- public sealed record Definition(TypeDefinition Type) : TypeSemantics;
- public sealed record GenericInstance(TypeDefinition GenericType, List GenericArgs) : TypeSemantics;
- public sealed record GenericInstanceRef(ITypeDefOrRef GenericType, List GenericArgs) : TypeSemantics;
- public sealed record GenericTypeIndex(int Index) : TypeSemantics;
- public sealed record GenericMethodIndex(int Index) : TypeSemantics;
- public sealed record GenericParameter_(GenericParameter Parameter) : TypeSemantics;
- public sealed record Reference(TypeReference Reference_, bool IsValueType = false) : TypeSemantics;
-}
-
-///
-/// Static helpers for converting AsmResolver type signatures into instances.
-/// Mirrors get_type_semantics in helpers.h.
-///
-internal static class TypeSemanticsFactory
-{
- public static TypeSemantics Get(TypeSignature signature)
- {
- return signature switch
- {
- CorLibTypeSignature corlib => GetCorLib(corlib.ElementType),
- GenericInstanceTypeSignature gi => GetGenericInstance(gi),
- GenericParameterSignature gp => gp.ParameterType == GenericParameterType.Type
- ? new TypeSemantics.GenericTypeIndex(gp.Index)
- : new TypeSemantics.GenericMethodIndex(gp.Index),
- TypeDefOrRefSignature tdorref => GetFromTypeDefOrRef(tdorref.Type, tdorref.IsValueType),
- SzArrayTypeSignature sz => Get(sz.BaseType), // SZ arrays handled by callers
- ByReferenceTypeSignature br => Get(br.BaseType),
- _ => GetFromTypeDefOrRef(signature.GetUnderlyingTypeDefOrRef() ?? throw new System.InvalidOperationException("Unsupported signature: " + signature?.ToString())),
- };
- }
-
- public static TypeSemantics GetFromTypeDefOrRef(ITypeDefOrRef type, bool isValueType = false)
- {
- if (type is TypeDefinition def)
- {
- return new TypeSemantics.Definition(def);
- }
- if (type is TypeReference reference)
- {
- string ns = reference.Namespace?.Value ?? string.Empty;
- string name = reference.Name?.Value ?? string.Empty;
- if (ns == "System" && name == "Guid") { return new TypeSemantics.Guid_(); }
- if (ns == "System" && name == "Object") { return new TypeSemantics.Object_(); }
- if (ns == "System" && name == "Type") { return new TypeSemantics.Type_(); }
- return new TypeSemantics.Reference(reference, isValueType);
- }
- if (type is TypeSpecification spec && spec.Signature is GenericInstanceTypeSignature gi)
- {
- return GetGenericInstance(gi);
- }
- return new TypeSemantics.Reference((TypeReference)type, isValueType);
- }
-
- private static TypeSemantics GetCorLib(ElementType elementType)
- {
- return elementType switch
- {
- ElementType.Boolean => new TypeSemantics.Fundamental(FundamentalType.Boolean),
- ElementType.Char => new TypeSemantics.Fundamental(FundamentalType.Char),
- ElementType.I1 => new TypeSemantics.Fundamental(FundamentalType.Int8),
- ElementType.U1 => new TypeSemantics.Fundamental(FundamentalType.UInt8),
- ElementType.I2 => new TypeSemantics.Fundamental(FundamentalType.Int16),
- ElementType.U2 => new TypeSemantics.Fundamental(FundamentalType.UInt16),
- ElementType.I4 => new TypeSemantics.Fundamental(FundamentalType.Int32),
- ElementType.U4 => new TypeSemantics.Fundamental(FundamentalType.UInt32),
- ElementType.I8 => new TypeSemantics.Fundamental(FundamentalType.Int64),
- ElementType.U8 => new TypeSemantics.Fundamental(FundamentalType.UInt64),
- ElementType.R4 => new TypeSemantics.Fundamental(FundamentalType.Float),
- ElementType.R8 => new TypeSemantics.Fundamental(FundamentalType.Double),
- ElementType.String => new TypeSemantics.Fundamental(FundamentalType.String),
- ElementType.Object => new TypeSemantics.Object_(),
- _ => throw new System.InvalidOperationException($"Unsupported corlib element type: {elementType}")
- };
- }
-
- private static TypeSemantics GetGenericInstance(GenericInstanceTypeSignature gi)
- {
- ITypeDefOrRef genericType = gi.GenericType;
- TypeDefinition? def = genericType as TypeDefinition;
- // Always preserve the type arguments.
- List args = new(gi.TypeArguments.Count);
- foreach (TypeSignature arg in gi.TypeArguments)
- {
- args.Add(Get(arg));
- }
- if (def is null)
- {
- // Wrap the generic-type reference along with the resolved type arguments.
- return new TypeSemantics.GenericInstanceRef(genericType, args);
- }
- return new TypeSemantics.GenericInstance(def, args);
- }
-}
-
-///
-/// Type-name kind, mirrors C++ typedef_name_type.
-///
-internal enum TypedefNameType
-{
- Projected,
- CCW,
- ABI,
- NonProjected,
- StaticAbiClass,
- EventSource,
- Marshaller,
- ArrayMarshaller,
- InteropIID,
-}
-
-///
-/// Type mapping helpers (e.g., to_csharp_type, to_dotnet_type) from C++ code_writers.h.
-///
-internal static class FundamentalTypes
-{
- public static string ToCSharpType(FundamentalType t) => t switch
- {
- FundamentalType.Boolean => "bool",
- FundamentalType.Char => "char",
- FundamentalType.Int8 => "sbyte",
- FundamentalType.UInt8 => "byte",
- FundamentalType.Int16 => "short",
- FundamentalType.UInt16 => "ushort",
- FundamentalType.Int32 => "int",
- FundamentalType.UInt32 => "uint",
- FundamentalType.Int64 => "long",
- FundamentalType.UInt64 => "ulong",
- FundamentalType.Float => "float",
- FundamentalType.Double => "double",
- FundamentalType.String => "string",
- _ => "object"
- };
-
- public static string ToDotNetType(FundamentalType t) => t switch
- {
- FundamentalType.Boolean => "Boolean",
- FundamentalType.Char => "Char",
- FundamentalType.Int8 => "SByte",
- FundamentalType.UInt8 => "Byte",
- FundamentalType.Int16 => "Int16",
- FundamentalType.UInt16 => "UInt16",
- FundamentalType.Int32 => "Int32",
- FundamentalType.UInt32 => "UInt32",
- FundamentalType.Int64 => "Int64",
- FundamentalType.UInt64 => "UInt64",
- FundamentalType.Float => "Single",
- FundamentalType.Double => "Double",
- FundamentalType.String => "String",
- _ => "Object"
- };
-}
diff --git a/src/WinRT.Projection.Generator.Writer/ProjectionWriter.cs b/src/WinRT.Projection.Generator.Writer/ProjectionWriter.cs
deleted file mode 100644
index f16052a3c4..0000000000
--- a/src/WinRT.Projection.Generator.Writer/ProjectionWriter.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.IO;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// Public API for generating C# Windows Runtime projections from .winmd metadata.
-/// This is the C# port of the C++ cswinrt.exe tool from src/cswinrt/.
-///
-/// Usage: call with the desired options. The tool will generate
-/// .cs files in the specified output folder, one per Windows Runtime namespace.
-///
-///
-public static class ProjectionWriter
-{
- ///
- /// Runs projection generation. Mirrors the orchestration in the C++ cswinrt::run in main.cpp.
- ///
- /// The generation options (input metadata, output folder, filters).
- public static void Run(ProjectionWriterOptions options)
- {
- ArgumentNullException.ThrowIfNull(options);
- if (options.InputPaths == null || options.InputPaths.Count == 0)
- {
- throw new ArgumentException("At least one input metadata path must be provided.", nameof(options));
- }
- if (string.IsNullOrEmpty(options.OutputFolder))
- {
- throw new ArgumentException("Output folder must be provided.", nameof(options));
- }
-
- // Configure global settings (mirrors C++ settings_type)
- Settings settings = new()
- {
- Verbose = options.Verbose,
- Component = options.Component,
- Internal = options.Internal,
- Embedded = options.Embedded,
- PublicEnums = options.PublicEnums,
- PublicExclusiveTo = options.PublicExclusiveTo,
- IdicExclusiveTo = options.IdicExclusiveTo,
- ReferenceProjection = options.ReferenceProjection,
- OutputFolder = Path.GetFullPath(options.OutputFolder),
- };
-
- foreach (string p in options.InputPaths) { _ = settings.Input.Add(p); }
- foreach (string p in options.Include) { _ = settings.Include.Add(p); }
- foreach (string p in options.Exclude) { _ = settings.Exclude.Add(p); }
- foreach (string p in options.AdditionExclude) { _ = settings.AdditionExclude.Add(p); }
-
- settings.Filter = new TypeFilter(settings.Include, settings.Exclude);
- settings.AdditionFilter = new TypeFilter(settings.Include, settings.AdditionExclude);
-
- _ = Directory.CreateDirectory(settings.OutputFolder);
-
- // Load metadata
- MetadataCache cache = MetadataCache.Load(settings.Input);
-
- // Run the generator
- ProjectionGenerator generator = new(settings, cache, options.CancellationToken);
- generator.Run();
- }
-}
diff --git a/src/WinRT.Projection.Generator.Writer/ProjectionWriterOptions.cs b/src/WinRT.Projection.Generator.Writer/ProjectionWriterOptions.cs
deleted file mode 100644
index b072180615..0000000000
--- a/src/WinRT.Projection.Generator.Writer/ProjectionWriterOptions.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using System.Threading;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// Input parameters for . Mirrors the C++ cswinrt.exe CLI options
-/// from main.cpp's process_args().
-///
-public sealed class ProjectionWriterOptions
-{
- ///
- /// One or more .winmd files (or directories that will be recursively scanned for .winmd files)
- /// providing the Windows Runtime metadata to project.
- ///
- public required IReadOnlyList InputPaths { get; init; }
-
- ///
- /// The output folder where generated .cs files will be placed. Will be created if it doesn't exist.
- ///
- public required string OutputFolder { get; init; }
-
- ///
- /// Optional list of namespace prefixes to include in the projection.
- ///
- public IReadOnlyList Include { get; init; } = Array.Empty();
-
- ///
- /// Optional list of namespace prefixes to exclude from the projection.
- ///
- public IReadOnlyList Exclude { get; init; } = Array.Empty();
-
- ///
- /// Optional list of namespace prefixes to exclude from the projection additions.
- ///
- public IReadOnlyList AdditionExclude { get; init; } = Array.Empty();
-
- /// Generate a Windows Runtime component projection.
- public bool Component { get; init; }
-
- /// Generate an internal (non-public) projection.
- public bool Internal { get; init; }
-
- /// Generate an embedded projection.
- public bool Embedded { get; init; }
-
- /// If true with embedded option, generate enums as public.
- public bool PublicEnums { get; init; }
-
- /// Make exclusive-to interfaces public in the projection (default is internal).
- public bool PublicExclusiveTo { get; init; }
-
- /// Make exclusive-to interfaces support IDynamicInterfaceCastable.
- public bool IdicExclusiveTo { get; init; }
-
- /// Generate a projection to be used as a reference assembly.
- public bool ReferenceProjection { get; init; }
-
- /// Show detailed progress information.
- public bool Verbose { get; init; }
-
- /// Cancellation token for the operation.
- public CancellationToken CancellationToken { get; init; }
-}
diff --git a/src/WinRT.Projection.Generator.Writer/Writers/CodeWriters.Abi.cs b/src/WinRT.Projection.Generator.Writer/Writers/CodeWriters.Abi.cs
deleted file mode 100644
index 28c420d5b2..0000000000
--- a/src/WinRT.Projection.Generator.Writer/Writers/CodeWriters.Abi.cs
+++ /dev/null
@@ -1,6346 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Collections.Generic;
-using AsmResolver.DotNet;
-using AsmResolver.DotNet.Signatures;
-
-namespace WindowsRuntime.ProjectionGenerator.Writer;
-
-///
-/// ABI emission helpers (structs, enums, delegates, interfaces, classes).
-/// Mirrors the C++ write_abi_* family. Initial port: emits the foundational
-/// ABI scaffolding only; full marshaller/vtable emission to be filled in later.
-///
-internal static partial class CodeWriters
-{
- /// Mirrors C++ is_type_blittable partially.
- public static bool IsTypeBlittable(TypeDefinition type)
- {
- TypeCategory cat = TypeCategorization.GetCategory(type);
- if (cat == TypeCategory.Enum) { return true; }
- if (cat != TypeCategory.Struct) { return false; }
- // Mirrors C++ is_type_blittable (code_writers.h:81-124, struct_type branch): if the
- // struct itself has a mapped-type entry, return based on its RequiresMarshaling flag
- // BEFORE walking fields. This is critical for XAML structs like Duration / KeyTime /
- // RepeatBehavior which are self-mapped with RequiresMarshaling=false but have a
- // TimeSpan field (Windows.Foundation.TimeSpan -> System.TimeSpan with RequiresMarshaling=true).
- // Without this check, the field walk would incorrectly classify them as non-blittable.
- string ns = type.Namespace?.Value ?? string.Empty;
- string name = type.Name?.Value ?? string.Empty;
- if (MappedTypes.Get(ns, name) is { } mapping)
- {
- return !mapping.RequiresMarshaling;
- }
- // Walk fields - all must be blittable
- foreach (FieldDefinition field in type.Fields)
- {
- if (field.IsStatic || field.Signature is null) { continue; }
- if (!IsFieldTypeBlittable(field.Signature.FieldType)) { return false; }
- }
- return true;
- }
-
- private static bool IsFieldTypeBlittable(AsmResolver.DotNet.Signatures.TypeSignature sig)
- {
- if (sig is AsmResolver.DotNet.Signatures.CorLibTypeSignature corlib)
- {
- // Mirror C++ is_type_blittable for fundamental_type:
- // return (type != fundamental_type::String);
- // i.e. ALL fundamentals (including Boolean, Char) are considered blittable here;
- // only String is non-blittable. Object isn't a fundamental in C++; handled below.
- return corlib.ElementType switch
- {
- AsmResolver.PE.DotNet.Metadata.Tables.ElementType.String => false,
- AsmResolver.PE.DotNet.Metadata.Tables.ElementType.Object => false,
- _ => true
- };
- }
- // For TypeRef/TypeDef, resolve and check blittability.
- if (sig is AsmResolver.DotNet.Signatures.TypeDefOrRefSignature todr)
- {
- string fNs = todr.Type?.Namespace?.Value ?? string.Empty;
- string fName = todr.Type?.Name?.Value ?? string.Empty;
- // System.Guid is a fundamental blittable type (mirrors C++ guid_type which falls
- // through to the [&](auto&&) catch-all returning true in is_type_blittable).
- // Same applies to System.IntPtr / UIntPtr (used in some struct layouts).
- if (fNs == "System" && (fName == "Guid" || fName == "IntPtr" || fName == "UIntPtr"))
- {
- return true;
- }
- // Mapped struct types: blittable iff the mapping does NOT require marshalling
- // (mirrors C++ is_type_blittable for mapped struct_type case).
- MappedType? mapped = MappedTypes.Get(fNs, fName);
- if (mapped is not null && mapped.RequiresMarshaling) { return false; }
- if (todr.Type is TypeDefinition td)
- {
- return IsTypeBlittable(td);
- }
- // Cross-module: try metadata cache.
- if (todr.Type is TypeReference tr && _cacheRef is not null)
- {
- string ns = tr.Namespace?.Value ?? string.Empty;
- string name = tr.Name?.Value ?? string.Empty;
- TypeDefinition? resolved = _cacheRef.Find(ns + "." + name);
- if (resolved is not null) { return IsTypeBlittable(resolved); }
- }
- return false;
- }
- return false;
- }
-
- ///
- /// Resolves a to its
- /// , handling both in-assembly (already a TypeDefinition) and
- /// cross-assembly/TypeRef-row references via the metadata cache. Returns null when
- /// the reference cannot be resolved.
- ///
- private static TypeDefinition? TryResolveStructTypeDef(AsmResolver.DotNet.Signatures.TypeDefOrRefSignature tdr)
- {
- if (tdr.Type is TypeDefinition td) { return td; }
- if (tdr.Type is TypeReference tr && _cacheRef is not null)
- {
- string ns = tr.Namespace?.Value ?? string.Empty;
- string name = tr.Name?.Value ?? string.Empty;
- return _cacheRef.Find(ns + "." + name);
- }
- return null;
- }
-
- /// Mirrors C++ write_abi_enum.
- public static void WriteAbiEnum(TypeWriter w, TypeDefinition type)
- {
- // The C++ version emits: write_struct_and_enum_marshaller_class, write_interface_entries_impl,
- // write_struct_and_enum_com_wrappers_marshaller_attribute_impl, write_reference_impl.
- // For now, emit a minimal marshaller class so the ComWrappersMarshaller attribute reference resolves.
- string name = type.Name?.Value ?? string.Empty;
- WriteStructEnumMarshallerClass(w, type);
- WriteReferenceImpl(w, type);
-
- // In component mode, the C++ tool also emits the authoring metadata wrapper for enums.
- if (w.Settings.Component)
- {
- WriteAuthoringMetadataType(w, type);
- }
- }
-
- /// Mirrors C++ write_abi_struct.
- public static void WriteAbiStruct(TypeWriter w, TypeDefinition type)
- {
- string name = type.Name?.Value ?? string.Empty;
-
- // Emit the underlying ABI struct only when not blittable AND not a mapped struct
- // (mapped structs like Duration/KeyTime/RepeatBehavior have addition files that
- // replace the public struct's field layout, so a per-field ABI struct can't be
- // built directly from the projected type).
- bool blittable = IsTypeBlittable(type);
- string typeNs = type.Namespace?.Value ?? string.Empty;
- string typeNm = type.Name?.Value ?? string.Empty;
- bool isMappedStruct = MappedTypes.Get(typeNs, typeNm) is not null;
- if (!blittable && !isMappedStruct)
- {
- // Mirror C++ write_abi_struct: in component mode emit metadata typename + mapped
- // type attribute; otherwise emit the ComWrappers attribute. Both branches then
- // emit [WindowsRuntimeClassName] + the struct definition with public ABI fields.
- if (w.Settings.Component)
- {
- WriteWinRTMetadataTypeNameAttribute(w, type);
- WriteWinRTMappedTypeAttribute(w, type);
- }
- else
- {
- WriteComWrapperMarshallerAttribute(w, type);
- }
- WriteValueTypeWinRTClassNameAttribute(w, type);
- w.Write(Helpers.InternalAccessibility(w.Settings));
- w.Write(" unsafe struct ");
- WriteTypedefName(w, type, TypedefNameType.ABI, false);
- w.Write("\n{\n");
- foreach (FieldDefinition field in type.Fields)
- {
- if (field.IsStatic || field.Signature is null) { continue; }
- AsmResolver.DotNet.Signatures.TypeSignature ft = field.Signature.FieldType;
- w.Write("public ");
- // Truth uses void* for string and Nullable fields, the ABI struct for
- // mapped value types (DateTime/TimeSpan), and the projected type for everything
- // else (including enums and bool — their C# layout matches the WinRT ABI directly).
- if (IsString(ft) || TryGetNullablePrimitiveMarshallerName(ft, out _))
- {
- w.Write("void*");
- }
- else if (IsMappedAbiValueType(ft))
- {
- w.Write(GetMappedAbiTypeName(ft));
- }
- else if (ft is AsmResolver.DotNet.Signatures.TypeDefOrRefSignature tdr
- && TryResolveStructTypeDef(tdr) is TypeDefinition fieldTd
- && TypeCategorization.GetCategory(fieldTd) == TypeCategory.Struct
- && !IsTypeBlittable(fieldTd))
- {
- // Mirror C++ write_abi_type: non-blittable struct field uses ABI typedef name.
- WriteTypedefName(w, fieldTd, TypedefNameType.ABI, false);
- }
- else
- {
- WriteProjectedSignature(w, ft, false);
- }
- w.Write(" ");
- w.Write(field.Name?.Value ?? string.Empty);
- w.Write(";\n");
- }
- w.Write("}\n\n");
- }
- else if (blittable && w.Settings.Component)
- {
- // For blittable component structs, the C++ tool emits the authoring metadata wrapper
- // (a 'file static class T {}' with [WindowsRuntimeMetadataTypeName]/[WindowsRuntimeMappedType]/
- // [WindowsRuntimeReferenceType]/[ComWrappersMarshaller]/[WindowsRuntimeClassName]).
- WriteAuthoringMetadataType(w, type);
- }
-
- WriteStructEnumMarshallerClass(w, type);
- WriteReferenceImpl(w, type);
- }
-
- /// Mirrors C++ write_abi_delegate.
- public static void WriteAbiDelegate(TypeWriter w, TypeDefinition type)
- {
- // Mirror the C++ tool's ordering exactly:
- // write_delegate_marshaller
- // write_delegate_vtbl
- // write_native_delegate
- // write_delegate_comwrappers_callback
- // write_delegates_interface_entries_impl
- // write_delegate_com_wrappers_marshaller_attribute_impl
- // write_delegate_impl
- // write_reference_impl
- // (component) write_authoring_metadata_type
- WriteDelegateMarshallerOnly(w, type);
- WriteDelegateVftbl(w, type);
- WriteNativeDelegate(w, type);
- WriteDelegateComWrappersCallback(w, type);
- WriteDelegateInterfaceEntriesImpl(w, type);
- WriteDelegateComWrappersMarshallerAttribute(w, type);
- WriteDelegateImpl(w, type);
- WriteReferenceImpl(w, type);
-
- // In component mode, the C++ tool also emits the authoring metadata wrapper for delegates.
- if (w.Settings.Component)
- {
- WriteAuthoringMetadataType(w, type);
- }
- }
-
- /// Emits the <DelegateName>Impl static class providing the CCW vtable for a delegate.
- private static void WriteDelegateImpl(TypeWriter w, TypeDefinition type)
- {
- if (type.GenericParameters.Count > 0) { return; }
- MethodDefinition? invoke = Helpers.GetDelegateInvoke(type);
- if (invoke is null) { return; }
- MethodSig sig = new(invoke);
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
- string iidExpr = w.WriteTemp("%", new System.Action(_ => WriteIidExpression(w, type)));
-
- w.Write("\ninternal static unsafe class ");
- w.Write(nameStripped);
- w.Write("Impl\n{\n");
- w.Write(" [FixedAddressValueType]\n");
- w.Write(" private static readonly ");
- w.Write(nameStripped);
- w.Write("Vftbl Vftbl;\n\n");
- w.Write(" static ");
- w.Write(nameStripped);
- w.Write("Impl()\n {\n");
- w.Write(" *(IUnknownVftbl*)Unsafe.AsPointer(ref Vftbl) = *(IUnknownVftbl*)IUnknownImpl.Vtable;\n");
- w.Write(" Vftbl.Invoke = &Invoke;\n");
- w.Write(" }\n\n");
- w.Write(" public static nint Vtable\n {\n [MethodImpl(MethodImplOptions.AggressiveInlining)]\n get => (nint)Unsafe.AsPointer(in Vftbl);\n }\n\n");
-
- w.Write("[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]\n");
- w.Write("private static int Invoke(");
- WriteAbiParameterTypesPointer(w, sig, includeParamNames: true);
- w.Write(")");
-
- // Reuse the interface Do_Abi body emitter: delegates dispatch via __target.Invoke(...),
- // which is exactly the same shape as interface CCW dispatch. Pass the delegate's
- // projected name as 'ifaceFullName' and "Invoke" as 'methodName'.
- string projectedDelegateForBody = w.WriteTemp("%", new System.Action(_ => WriteTypedefName(w, type, TypedefNameType.Projected, true)));
- if (!projectedDelegateForBody.StartsWith("global::", System.StringComparison.Ordinal)) { projectedDelegateForBody = "global::" + projectedDelegateForBody; }
- EmitDoAbiBodyIfSimple(w, sig, projectedDelegateForBody, "Invoke");
- w.Write("\n");
-
- w.Write(" public static ref readonly Guid IID\n {\n [MethodImpl(MethodImplOptions.AggressiveInlining)]\n get => ref ");
- w.Write(iidExpr);
- w.Write(";\n }\n}\n");
- }
-
-
- ///
- /// Returns the interop assembly path for an array marshaller of a given element type.
- /// The interop generator names array marshallers ABI.<typeNamespace>.<<assembly>ElementName>ArrayMarshaller
- /// (typeNamespace prefix outside the brackets, and the element inside the brackets uses just the
- /// type name without its namespace because depth=0 in the interop generator's AppendRawTypeName).
- ///
- private static string GetArrayMarshallerInteropPath(TypeWriter w, AsmResolver.DotNet.Signatures.TypeSignature elementType, string encodedElement)
- {
- // The 'encodedElement' passed in uses the depth>0 form (assembly + hyphenated namespace + name),
- // but inside the array brackets the interop generator uses the depth=0 form (assembly + just name).
- // Re-encode the element with the top-level form for accurate matching.
- string topLevelElement = EncodeArrayElementName(elementType);
- // Resolve the element's namespace to determine the path prefix.
- string ns = GetMappedNamespace(elementType);
- if (string.IsNullOrEmpty(ns))
- {
- return "ABI.<" + topLevelElement + ">ArrayMarshaller, WinRT.Interop";
- }
- return "ABI." + ns + ".<" + topLevelElement + ">ArrayMarshaller, WinRT.Interop";
- }
-
- /// Returns the (possibly mapped) namespace of a type signature, or 'System' for fundamentals.
- private static string GetMappedNamespace(AsmResolver.DotNet.Signatures.TypeSignature sig)
- {
- // Fundamentals (string, bool, int, etc.) live in 'System' for ArrayMarshaller path purposes.
- if (sig is AsmResolver.DotNet.Signatures.CorLibTypeSignature) { return "System"; }
- AsmResolver.DotNet.ITypeDefOrRef? td = null;
- if (sig is AsmResolver.DotNet.Signatures.TypeDefOrRefSignature tds) { td = tds.Type; }
- else if (sig is AsmResolver.DotNet.Signatures.GenericInstanceTypeSignature gi) { td = gi.GenericType; }
- if (td is null) { return string.Empty; }
- string typeNs = td.Namespace?.Value ?? string.Empty;
- string typeName = td.Name?.Value ?? string.Empty;
- MappedType? mapped = MappedTypes.Get(typeNs, typeName);
- return mapped is not null ? mapped.MappedNamespace : typeNs;
- }
-
- ///
- /// Encodes the array element type name as the interop generator's AppendRawTypeName at depth=0:
- /// fundamentals use their short C# name; typedefs use just the type name (no namespace) prefixed
- /// with the assembly marker; generic instances include their assembly marker, name, and type arguments.
- ///
- private static string EncodeArrayElementName(AsmResolver.DotNet.Signatures.TypeSignature elementType)
- {
- System.Text.StringBuilder sb = new();
- EncodeArrayElementNameInto(sb, elementType);
- return sb.ToString();
- }
-
- private static void EncodeArrayElementNameInto(System.Text.StringBuilder sb, AsmResolver.DotNet.Signatures.TypeSignature sig)
- {
- // Special case for System.Guid: matches C++ guid_type handler in write_interop_dll_type_name.
- // The depth=0 (top-level array element) form drops the namespace prefix and uses just the
- // assembly marker + type name, so for Guid this becomes "<#corlib>Guid".
- if (sig is AsmResolver.DotNet.Signatures.TypeDefOrRefSignature gtd
- && gtd.Type?.Namespace?.Value == "System"
- && gtd.Type?.Name?.Value == "Guid")
- {
- sb.Append("<#corlib>Guid");
- return;
- }
- switch (sig)
- {
- case AsmResolver.DotNet.Signatures.CorLibTypeSignature corlib:
- EncodeFundamental(sb, corlib, TypedefNameType.Projected);
- return;
- case AsmResolver.DotNet.Signatures.TypeDefOrRefSignature td:
- EncodeArrayElementForTypeDef(sb, td.Type, generic_args: null);
- return;
- case AsmResolver.DotNet.Signatures.GenericInstanceTypeSignature gi:
- EncodeArrayElementForTypeDef(sb, gi.GenericType, generic_args: gi.TypeArguments);
- return;
- default:
- sb.Append(sig.FullName);
- return;
- }
- }
-
- private static void EncodeArrayElementForTypeDef(System.Text.StringBuilder sb, AsmResolver.DotNet.ITypeDefOrRef type, System.Collections.Generic.IList? generic_args)
- {
- string typeNs = type.Namespace?.Value ?? string.Empty;
- string typeName = type.Name?.Value ?? string.Empty;
- // Apply mapped-type remapping (e.g. Windows.Foundation.IReference -> System.Nullable).
- MappedType? mapped = MappedTypes.Get(typeNs, typeName);
- if (mapped is not null)
- {
- typeNs = mapped.MappedNamespace;
- typeName = mapped.MappedName;
- }
- // Replace generic arity backtick with apostrophe.
- typeName = typeName.Replace('`', '\'');
-
- // Assembly marker prefix. Pass the type so that third-party (e.g. component-authored)
- // types resolve to their actual assembly name (e.g. ) instead of
- // defaulting to <#Windows>.
- sb.Append(GetInteropAssemblyMarker(typeNs, typeName, mapped, type));
- // Top-level: just the type name (no namespace).
- sb.Append(typeName);
-
- // Generic arguments use the standard EncodeInteropTypeNameInto (depth > 0).
- if (generic_args is { Count: > 0 })
- {
- sb.Append('<');
- for (int i = 0; i < generic_args.Count; i++)
- {
- if (i > 0) { sb.Append('|'); }
- EncodeInteropTypeNameInto(sb, generic_args[i], TypedefNameType.Projected);
- }
- sb.Append('>');
- }
- }
-
- /// Mirrors C++ write_delegate_vtbl.
- private static void WriteDelegateVftbl(TypeWriter w, TypeDefinition type)
- {
- if (type.GenericParameters.Count > 0) { return; }
- MethodDefinition? invoke = Helpers.GetDelegateInvoke(type);
- if (invoke is null) { return; }
- MethodSig sig = new(invoke);
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
-
- w.Write("\n[StructLayout(LayoutKind.Sequential)]\n");
- w.Write("internal unsafe struct ");
- w.Write(nameStripped);
- w.Write("Vftbl\n{\n");
- w.Write(" public delegate* unmanaged[MemberFunction] QueryInterface;\n");
- w.Write(" public delegate* unmanaged[MemberFunction] AddRef;\n");
- w.Write(" public delegate* unmanaged[MemberFunction] Release;\n");
- w.Write(" public delegate* unmanaged[MemberFunction]<");
- WriteAbiParameterTypesPointer(w, sig);
- w.Write(", int> Invoke;\n");
- w.Write("}\n");
- }
-
- /// Mirrors C++ write_native_delegate.
- private static void WriteNativeDelegate(TypeWriter w, TypeDefinition type)
- {
- if (type.GenericParameters.Count > 0) { return; }
- MethodDefinition? invoke = Helpers.GetDelegateInvoke(type);
- if (invoke is null) { return; }
- MethodSig sig = new(invoke);
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
-
- w.Write("\npublic static unsafe class ");
- w.Write(nameStripped);
- w.Write("NativeDelegate\n{\n");
-
- w.Write(" public static unsafe ");
- WriteProjectionReturnType(w, sig);
- w.Write(" ");
- w.Write(nameStripped);
- w.Write("Invoke(this WindowsRuntimeObjectReference thisReference");
- if (sig.Params.Count > 0) { w.Write(", "); }
- WriteParameterList(w, sig);
- w.Write(")");
-
- // Reuse the interface caller body emitter. Delegate Invoke is at vtable slot 3
- // (after QI/AddRef/Release). Functionally equivalent to the truth's
- // 'var abiInvoke = ((Vftbl*)*(void***)ThisPtr)->Invoke;' form, just routed
- // through the slot-indexed dispatch shared with interface CCW callers.
- EmitAbiMethodBodyIfSimple(w, sig, slot: 3, isNoExcept: Helpers.IsNoExcept(invoke));
-
- w.Write("}\n");
- }
-
- /// Mirrors C++ write_delegates_interface_entries_impl.
- private static void WriteDelegateInterfaceEntriesImpl(TypeWriter w, TypeDefinition type)
- {
- if (type.GenericParameters.Count > 0) { return; }
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
- string iidExpr = w.WriteTemp("%", new System.Action(_ => WriteIidExpression(w, type)));
- string iidRefExpr = w.WriteTemp("%", new System.Action(_ => WriteIidReferenceExpression(w, type)));
-
- w.Write("\nfile static class ");
- w.Write(nameStripped);
- w.Write("InterfaceEntriesImpl\n{\n");
- w.Write(" [FixedAddressValueType]\n");
- w.Write(" public static readonly DelegateReferenceInterfaceEntries Entries;\n\n");
- w.Write(" static ");
- w.Write(nameStripped);
- w.Write("InterfaceEntriesImpl()\n {\n");
- w.Write(" Entries.Delegate.IID = ");
- w.Write(iidExpr);
- w.Write(";\n");
- w.Write(" Entries.Delegate.Vtable = ");
- w.Write(nameStripped);
- w.Write("Impl.Vtable;\n");
- w.Write(" Entries.DelegateReference.IID = ");
- w.Write(iidRefExpr);
- w.Write(";\n");
- w.Write(" Entries.DelegateReference.Vtable = ");
- w.Write(nameStripped);
- w.Write("ReferenceImpl.Vtable;\n");
- w.Write(" Entries.IPropertyValue.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IPropertyValue;\n");
- w.Write(" Entries.IPropertyValue.Vtable = global::WindowsRuntime.InteropServices.IPropertyValueImpl.OtherTypeVtable;\n");
- w.Write(" Entries.IStringable.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IStringable;\n");
- w.Write(" Entries.IStringable.Vtable = global::WindowsRuntime.InteropServices.IStringableImpl.Vtable;\n");
- w.Write(" Entries.IWeakReferenceSource.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IWeakReferenceSource;\n");
- w.Write(" Entries.IWeakReferenceSource.Vtable = global::WindowsRuntime.InteropServices.IWeakReferenceSourceImpl.Vtable;\n");
- w.Write(" Entries.IMarshal.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IMarshal;\n");
- w.Write(" Entries.IMarshal.Vtable = global::WindowsRuntime.InteropServices.IMarshalImpl.Vtable;\n");
- w.Write(" Entries.IAgileObject.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IAgileObject;\n");
- w.Write(" Entries.IAgileObject.Vtable = global::WindowsRuntime.InteropServices.IAgileObjectImpl.Vtable;\n");
- w.Write(" Entries.IInspectable.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IInspectable;\n");
- w.Write(" Entries.IInspectable.Vtable = global::WindowsRuntime.InteropServices.IInspectableImpl.Vtable;\n");
- w.Write(" Entries.IUnknown.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IUnknown;\n");
- w.Write(" Entries.IUnknown.Vtable = global::WindowsRuntime.InteropServices.IUnknownImpl.Vtable;\n");
- w.Write(" }\n}\n");
- }
-
- /// Mirrors C++ write_temp_delegate_event_source_subclass.
- public static void WriteTempDelegateEventSourceSubclass(TypeWriter w, TypeDefinition type)
- {
- // Skip generic delegates: only non-generic delegates get a per-delegate EventSource subclass.
- // Generic delegates (e.g. EventHandler) use the generic EventHandlerEventSource directly.
- if (type.GenericParameters.Count > 0) { return; }
-
- MethodDefinition? invoke = Helpers.GetDelegateInvoke(type);
- if (invoke is null) { return; }
- MethodSig sig = new(invoke);
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
-
- // Compute the projected type name (with global::) used as the generic argument.
- string projectedName = w.WriteTemp("%", new System.Action(_ => WriteTypedefName(w, type, TypedefNameType.Projected, true)));
- if (!projectedName.StartsWith("global::", System.StringComparison.Ordinal))
- {
- projectedName = "global::" + projectedName;
- }
-
- w.Write("\npublic sealed unsafe class ");
- w.Write(nameStripped);
- w.Write("EventSource : EventSource<");
- w.Write(projectedName);
- w.Write(">\n{\n");
- w.Write(" /// \n");
- w.Write(" public ");
- w.Write(nameStripped);
- w.Write("EventSource(WindowsRuntimeObjectReference nativeObjectReference, int index)\n : base(nativeObjectReference, index)\n {\n }\n\n");
- w.Write(" /// \n");
- w.Write(" protected override WindowsRuntimeObjectReferenceValue ConvertToUnmanaged(");
- w.Write(projectedName);
- w.Write(" value)\n {\n return ");
- w.Write(nameStripped);
- w.Write("Marshaller.ConvertToUnmanaged(value);\n }\n\n");
- w.Write(" /// \n");
- w.Write(" protected override EventSourceState<");
- w.Write(projectedName);
- w.Write("> CreateEventSourceState()\n {\n return new EventState(GetNativeObjectReferenceThisPtrUnsafe(), Index);\n }\n\n");
- w.Write(" private sealed class EventState : EventSourceState<");
- w.Write(projectedName);
- w.Write(">\n {\n");
- w.Write(" /// \n");
- w.Write(" public EventState(void* thisPtr, int index)\n : base(thisPtr, index)\n {\n }\n\n");
- w.Write(" /// \n");
- w.Write(" protected override ");
- w.Write(projectedName);
- w.Write(" GetEventInvoke()\n {\n");
- // Build parameter name list for the lambda. Lambda's parameter list MUST match the
- // delegate's signature exactly, including in/out/ref modifiers - otherwise CS1676 fires
- // when calling TargetDelegate.Invoke. Mirror C++ write_parmaeters.
- w.Write(" return (");
- for (int i = 0; i < sig.Params.Count; i++)
- {
- if (i > 0) { w.Write(", "); }
- ParamCategory pc = ParamHelpers.GetParamCategory(sig.Params[i]);
- if (pc == ParamCategory.Ref) { w.Write("in "); }
- else if (pc == ParamCategory.Out || pc == ParamCategory.ReceiveArray) { w.Write("out "); }
- string raw = sig.Params[i].Parameter.Name ?? "p";
- w.Write(Helpers.IsKeyword(raw) ? "@" + raw : raw);
- }
- w.Write(") => TargetDelegate.Invoke(");
- for (int i = 0; i < sig.Params.Count; i++)
- {
- if (i > 0) { w.Write(", "); }
- ParamCategory pc = ParamHelpers.GetParamCategory(sig.Params[i]);
- if (pc == ParamCategory.Ref) { w.Write("in "); }
- else if (pc == ParamCategory.Out || pc == ParamCategory.ReceiveArray) { w.Write("out "); }
- string raw = sig.Params[i].Parameter.Name ?? "p";
- w.Write(Helpers.IsKeyword(raw) ? "@" + raw : raw);
- }
- w.Write(");\n");
- w.Write(" }\n }\n}\n");
- }
-
- /// Mirrors C++ write_abi_class.
- public static void WriteAbiClass(TypeWriter w, TypeDefinition type)
- {
- // Static classes don't get a *Marshaller (no instances).
- if (TypeCategorization.IsStatic(type)) { return; }
- // Mirror C++ write_abi_class: wrap class marshaller emission in #nullable enable/disable.
- w.Write("#nullable enable\n");
- if (w.Settings.Component)
- {
- // Mirror C++ write_component_class_marshaller + write_authoring_metadata_type.
- WriteComponentClassMarshaller(w, type);
- WriteAuthoringMetadataType(w, type);
- }
- else
- {
- // Emit a ComWrappers marshaller class so the attribute reference resolves
- WriteClassMarshallerStub(w, type);
- }
- w.Write("#nullable disable\n");
- }
-
- ///
- /// Emits the simpler component-mode class marshaller. Mirrors C++
- /// write_component_class_marshaller.
- ///
- private static void WriteComponentClassMarshaller(TypeWriter w, TypeDefinition type)
- {
- string nameStripped = Helpers.StripBackticks(type.Name?.Value ?? string.Empty);
- string typeNs = type.Namespace?.Value ?? string.Empty;
- string projectedType = $"global::{typeNs}.{nameStripped}";
-
- ITypeDefOrRef? defaultIface = Helpers.GetDefaultInterface(type);
-
- // Mirror C++ write_component_class_marshaller: if the default interface is a generic
- // instantiation (e.g. IDictionary), emit an UnsafeAccessor extern declaration
- // inside ConvertToUnmanaged that fetches the IID via WinRT.Interop's InterfaceIIDs class
- // (since the IID for a generic instantiation is computed at runtime). The IID expression
- // in the call then becomes '(null)' instead of a static InterfaceIIDs reference.
- AsmResolver.DotNet.Signatures.GenericInstanceTypeSignature? defaultGenericInst = null;
- if (defaultIface is AsmResolver.DotNet.TypeSpecification spec
- && spec.Signature is AsmResolver.DotNet.Signatures.GenericInstanceTypeSignature gi)
- {
- defaultGenericInst = gi;
- }
-
- string defaultIfaceIid;
- if (defaultGenericInst is not null)
- {
- // Call the accessor: '>(null)'.
- string accessorName = BuildIidPropertyNameForGenericInterface(w, defaultGenericInst);
- defaultIfaceIid = accessorName + "(null)";
- }
- else
- {
- defaultIfaceIid = defaultIface is not null
- ? w.WriteTemp("%", new System.Action(_ => WriteIidExpression(w, defaultIface)))
- : "default(global::System.Guid)";
- }
-
- w.Write("\npublic static unsafe class ");
- w.Write(nameStripped);
- w.Write("Marshaller\n{\n");
- w.Write(" public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged(");
- w.Write(projectedType);
- w.Write(" value)\n {\n");
- if (defaultGenericInst is not null)
- {
- // Emit the UnsafeAccessor declaration (uses 'object?' since component-mode
- // marshallers run inside #nullable enable).
- string accessorBlock = w.WriteTemp("%", new System.Action(_ => EmitUnsafeAccessorForIid(w, defaultGenericInst, isInNullableContext: true)));
- // Re-emit each line indented by 8 spaces.
- string[] accessorLines = accessorBlock.TrimEnd('\n').Split('\n');
- foreach (string accessorLine in accessorLines)
- {
- w.Write(" ");
- w.Write(accessorLine);
- w.Write("\n");
- }
- }
- w.Write(" return WindowsRuntimeInterfaceMarshaller<");
- w.Write(projectedType);
- w.Write(">.ConvertToUnmanaged(value, ");
- w.Write(defaultIfaceIid);
- w.Write(");\n }\n\n");
- w.Write(" public static ");
- w.Write(projectedType);
- w.Write("? ConvertToManaged(void* value)\n {\n");
- w.Write(" return (");
- w.Write(projectedType);
- w.Write("?) WindowsRuntimeObjectMarshaller.ConvertToManaged(value);\n }\n}\n");
- }
-
- ///
- /// Emits the metadata wrapper type file static class <Name> {} with the conditional
- /// set of attributes required for the type's category. Mirrors C++
- /// write_authoring_metadata_type.
- ///
- private static void WriteAuthoringMetadataType(TypeWriter w, TypeDefinition type)
- {
- string nameStripped = Helpers.StripBackticks(type.Name?.Value ?? string.Empty);
- string typeNs = type.Namespace?.Value ?? string.Empty;
- string projectedType = string.IsNullOrEmpty(typeNs) ? $"global::{nameStripped}" : $"global::{typeNs}.{nameStripped}";
- string fullName = string.IsNullOrEmpty(typeNs) ? nameStripped : $"{typeNs}.{nameStripped}";
- TypeCategory category = TypeCategorization.GetCategory(type);
-
- // [WindowsRuntimeReferenceType(typeof(?))] for non-delegate, non-class types
- // (i.e. enums, structs, interfaces).
- if (category != TypeCategory.Delegate && category != TypeCategory.Class)
- {
- w.Write("[WindowsRuntimeReferenceType(typeof(");
- w.Write(projectedType);
- w.Write("?))]\n");
- }
-
- // [ABI..ComWrappersMarshaller] for non-struct, non-class types
- // (delegates, enums, interfaces).
- if (category != TypeCategory.Struct && category != TypeCategory.Class)
- {
- w.Write("[ABI.");
- w.Write(typeNs);
- w.Write(".");
- w.Write(nameStripped);
- w.Write("ComWrappersMarshaller]\n");
- }
-
- // [WindowsRuntimeClassName("Windows.Foundation.IReference`1<.>")] for non-class types.
- if (category != TypeCategory.Class)
- {
- w.Write("[WindowsRuntimeClassName(\"Windows.Foundation.IReference`1<");
- w.Write(fullName);
- w.Write(">\")]\n");
- }
-
- w.Write("[WindowsRuntimeMetadataTypeName(\"");
- w.Write(fullName);
- w.Write("\")]\n");
- w.Write("[WindowsRuntimeMappedType(typeof(");
- w.Write(projectedType);
- w.Write("))]\n");
- w.Write("file static class ");
- w.Write(nameStripped);
- w.Write(" {}\n");
- }
-
- /// Mirrors C++ write_abi_interface.
- public static void WriteAbiInterface(TypeWriter w, TypeDefinition type)
- {
- // Generic interfaces are handled by interopgen
- if (type.GenericParameters.Count > 0) { return; }
-
- // The C++ also emits write_static_abi_classes here - we emit a basic stub for now
- WriteInterfaceMarshallerStub(w, type);
-
- // For internal projections, just the static ABI methods class is enough.
- if (TypeCategorization.IsProjectionInternal(type)) { return; }
-
- WriteInterfaceVftbl(w, type);
- WriteInterfaceImpl(w, type);
- WriteInterfaceIdicImpl(w, type);
- WriteInterfaceMarshaller(w, type);
- }
-
- /// Mirrors C++ emit_impl_type.
- public static bool EmitImplType(TypeWriter w, TypeDefinition type)
- {
- if (w.Settings.Component) { return true; }
- if (TypeCategorization.IsExclusiveTo(type) && !w.Settings.PublicExclusiveTo)
- {
- // Mirror C++ emit_impl_type: only emit Impl for exclusive-to interfaces if at least
- // one interface impl on the exclusive_to class is marked [Overridable] and matches
- // this interface. Otherwise the Impl wouldn't be reachable as a CCW.
- TypeDefinition? exclusiveToType = GetExclusiveToType(type);
- if (exclusiveToType is null) { return true; }
- bool hasOverridable = false;
- foreach (InterfaceImplementation impl in exclusiveToType.Interfaces)
- {
- if (impl.Interface is null) { continue; }
- TypeDefinition? ifaceTd = ResolveInterfaceTypeDef(impl.Interface);
- if (ifaceTd == type && Helpers.IsOverridable(impl)) { hasOverridable = true; break; }
- }
- return hasOverridable;
- }
- return true;
- }
-
- ///
- /// Returns the parent class for an interface marked [ExclusiveToAttribute(typeof(T))].
- /// Mirrors C++ get_exclusive_to_type.
- ///
- internal static TypeDefinition? GetExclusiveToType(TypeDefinition iface)
- {
- if (_cacheRef is null) { return null; }
- for (int i = 0; i < iface.CustomAttributes.Count; i++)
- {
- CustomAttribute attr = iface.CustomAttributes[i];
- ITypeDefOrRef? attrType = attr.Constructor?.DeclaringType;
- if (attrType is null) { continue; }
- if (attrType.Namespace?.Value != "Windows.Foundation.Metadata" ||
- attrType.Name?.Value != "ExclusiveToAttribute") { continue; }
- if (attr.Signature is null) { continue; }
- for (int j = 0; j < attr.Signature.FixedArguments.Count; j++)
- {
- AsmResolver.DotNet.Signatures.CustomAttributeArgument arg = attr.Signature.FixedArguments[j];
- if (arg.Element is AsmResolver.DotNet.Signatures.TypeSignature sig)
- {
- string fullName = sig.FullName ?? string.Empty;
- TypeDefinition? td = _cacheRef.Find(fullName);
- if (td is not null) { return td; }
- }
- else if (arg.Element is string s)
- {
- TypeDefinition? td = _cacheRef.Find(s);
- if (td is not null) { return td; }
- }
- }
- }
- return null;
- }
-
- /// Resolves an InterfaceImpl's interface reference to a TypeDefinition (same module or via metadata cache).
- private static TypeDefinition? ResolveInterfaceTypeDef(ITypeDefOrRef ifaceRef)
- {
- if (ifaceRef is TypeDefinition td) { return td; }
- if (ifaceRef is TypeSpecification ts && ts.Signature is AsmResolver.DotNet.Signatures.GenericInstanceTypeSignature gi)
- {
- ITypeDefOrRef? gen = gi.GenericType;
- if (gen is TypeDefinition gtd) { return gtd; }
- if (gen is TypeReference gtr && _cacheRef is not null)
- {
- string ns = gtr.Namespace?.Value ?? string.Empty;
- string nm = gtr.Name?.Value ?? string.Empty;
- return _cacheRef.Find(ns + "." + nm);
- }
- }
- if (ifaceRef is TypeReference tr && _cacheRef is not null)
- {
- string ns = tr.Namespace?.Value ?? string.Empty;
- string nm = tr.Name?.Value ?? string.Empty;
- return _cacheRef.Find(ns + "." + nm);
- }
- return null;
- }
-
- /// Mirrors C++ get_vmethod_name.
- public static string GetVMethodName(TypeDefinition type, MethodDefinition method)
- {
- // Index of method in the type's method list
- int index = 0;
- foreach (MethodDefinition m in type.Methods)
- {
- if (m == method) { break; }
- index++;
- }
- return (method.Name?.Value ?? string.Empty) + "_" + index.ToString(System.Globalization.CultureInfo.InvariantCulture);
- }
-
- /// Mirrors C++ write_abi_parameter_types_pointer.
- public static void WriteAbiParameterTypesPointer(TypeWriter w, MethodSig sig)
- {
- WriteAbiParameterTypesPointer(w, sig, includeParamNames: false);
- }
-
- ///
- /// Writes the ABI parameter types for a vtable function pointer signature, optionally
- /// including parameter names (for method declarations vs. function pointer type lists).
- ///
- public static void WriteAbiParameterTypesPointer(TypeWriter w, MethodSig sig, bool includeParamNames)
- {
- // void* thisPtr, then each param's ABI type, then return type pointer
- w.Write("void*");
- if (includeParamNames) { w.Write(" thisPtr"); }
- for (int i = 0; i < sig.Params.Count; i++)
- {
- w.Write(", ");
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (p.Type is AsmResolver.DotNet.Signatures.SzArrayTypeSignature sz)
- {
- // length pointer + value pointer. Mirrors C++ write_abi_signature for SzArray
- // input params (code_writers.h:1305) which always emits "uint __%Size, void* %"
- // regardless of element type.
- if (includeParamNames)
- {
- w.Write("uint ");
- w.Write("__");
- w.Write(p.Parameter.Name ?? "param");
- w.Write("Size, void* ");
- Helpers.WriteEscapedIdentifier(w, p.Parameter.Name ?? "param");
- }
- else
- {
- w.Write("uint, void*");
- }
- _ = sz;
- }
- else if (p.Type is AsmResolver.DotNet.Signatures.ByReferenceTypeSignature br)
- {
- // Special case: 'out T[]' is a ReceiveArray ABI signature: (uint* size, T** data).
- if (br.BaseType is AsmResolver.DotNet.Signatures.SzArrayTypeSignature brSz && cat == ParamCategory.ReceiveArray)
- {
- bool isRefElemBr = IsString(brSz.BaseType) || IsRuntimeClassOrInterface(brSz.BaseType) || IsObject(brSz.BaseType) || IsGenericInstance(brSz.BaseType);
- if (includeParamNames)
- {
- w.Write("uint* __");
- w.Write(p.Parameter.Name ?? "param");
- w.Write("Size, ");
- if (isRefElemBr) { w.Write("void*** "); }
- else
- {
- WriteAbiType(w, TypeSemanticsFactory.Get(brSz.BaseType));
- w.Write("** ");
- }
- Helpers.WriteEscapedIdentifier(w, p.Parameter.Name ?? "param");
- }
- else
- {
- w.Write("uint*, ");
- if (isRefElemBr) { w.Write("void***"); }
- else
- {
- WriteAbiType(w, TypeSemanticsFactory.Get(brSz.BaseType));
- w.Write("**");
- }
- }
- }
- else
- {
- WriteAbiType(w, TypeSemanticsFactory.Get(br.BaseType));
- w.Write("*");
- if (includeParamNames)
- {
- w.Write(" ");
- Helpers.WriteEscapedIdentifier(w, p.Parameter.Name ?? "param");
- }
- }
- }
- else
- {
- WriteAbiType(w, TypeSemanticsFactory.Get(p.Type));
- if (cat is ParamCategory.Out or ParamCategory.Ref) { w.Write("*"); }
- if (includeParamNames)
- {
- w.Write(" ");
- Helpers.WriteEscapedIdentifier(w, p.Parameter.Name ?? "param");
- }
- }
- }
- // Return parameter
- if (sig.ReturnType is not null)
- {
- w.Write(", ");
- string retName = GetReturnParamName(sig);
- string retSizeName = GetReturnSizeParamName(sig);
- // Special handling for SzArray return types: WinRT projects them as a (uint*, T**) pair.
- if (sig.ReturnType is AsmResolver.DotNet.Signatures.SzArrayTypeSignature retSz)
- {
- if (includeParamNames)
- {
- w.Write("uint* ");
- w.Write(retSizeName);
- w.Write(", ");
- WriteAbiType(w, TypeSemanticsFactory.Get(retSz.BaseType));
- w.Write("** ");
- w.Write(retName);
- }
- else
- {
- w.Write("uint*, ");
- WriteAbiType(w, TypeSemanticsFactory.Get(retSz.BaseType));
- w.Write("**");
- }
- }
- else
- {
- WriteAbiType(w, TypeSemanticsFactory.Get(sig.ReturnType));
- w.Write("*");
- if (includeParamNames) { w.Write(' '); w.Write(retName); }
- }
- }
- }
-
- ///
- /// Returns the metadata-derived name for the return parameter, or the C++ default __return_value__.
- /// Mirrors method_signature::return_param_name() in helpers.h.
- ///
- internal static string GetReturnParamName(MethodSig sig)
- {
- string? n = sig.ReturnParam?.Name?.Value;
- if (string.IsNullOrEmpty(n)) { return "__return_value__"; }
- return Helpers.IsKeyword(n) ? "@" + n : n;
- }
-
- ///
- /// Returns the local-variable name for the return parameter on the server side. Mirrors C++
- /// abi_marshaler::get_marshaler_local() which prefixes __ to the param name.
- ///
- internal static string GetReturnLocalName(MethodSig sig)
- {
- return "__" + GetReturnParamName(sig);
- }
-
- /// Returns '__<returnName>Size' (matches C++ '__%Size' convention) — by default '____return_value__Size' for the standard '__return_value__' return param.
- internal static string GetReturnSizeParamName(MethodSig sig)
- {
- // Mirrors C++ 'write_abi_parameter_types_pointer' which writes '__%Size' over the return param name.
- return "__" + GetReturnParamName(sig) + "Size";
- }
-
- /// Mirrors C++ write_interface_vftbl.
- public static void WriteInterfaceVftbl(TypeWriter w, TypeDefinition type)
- {
- if (!EmitImplType(w, type)) { return; }
- if (type.GenericParameters.Count > 0) { return; }
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
-
- w.Write("\n[StructLayout(LayoutKind.Sequential)]\n");
- w.Write("internal unsafe struct ");
- w.Write(nameStripped);
- w.Write("Vftbl\n{\n");
- w.Write("public delegate* unmanaged[MemberFunction] QueryInterface;\n");
- w.Write("public delegate* unmanaged[MemberFunction] AddRef;\n");
- w.Write("public delegate* unmanaged[MemberFunction] Release;\n");
- w.Write("public delegate* unmanaged[MemberFunction] GetIids;\n");
- w.Write("public delegate* unmanaged[MemberFunction] GetRuntimeClassName;\n");
- w.Write("public delegate* unmanaged[MemberFunction] GetTrustLevel;\n");
-
- foreach (MethodDefinition method in type.Methods)
- {
- string vm = GetVMethodName(type, method);
- MethodSig sig = new(method);
- w.Write("public delegate* unmanaged[MemberFunction]<");
- WriteAbiParameterTypesPointer(w, sig);
- w.Write(", int> ");
- w.Write(vm);
- w.Write(";\n");
- }
- w.Write("}\n");
- }
-
- /// Mirrors C++ write_interface_impl (simplified).
- public static void WriteInterfaceImpl(TypeWriter w, TypeDefinition type)
- {
- if (!EmitImplType(w, type)) { return; }
- if (type.GenericParameters.Count > 0) { return; }
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
-
- w.Write("\npublic static unsafe class ");
- w.Write(nameStripped);
- w.Write("Impl\n{\n");
- w.Write("[FixedAddressValueType]\n");
- w.Write("private static readonly ");
- w.Write(nameStripped);
- w.Write("Vftbl Vftbl;\n\n");
-
- w.Write("static ");
- w.Write(nameStripped);
- w.Write("Impl()\n{\n");
- w.Write(" *(IInspectableVftbl*)Unsafe.AsPointer(ref Vftbl) = *(IInspectableVftbl*)IInspectableImpl.Vtable;\n");
- foreach (MethodDefinition method in type.Methods)
- {
- string vm = GetVMethodName(type, method);
- w.Write(" Vftbl.");
- w.Write(vm);
- w.Write(" = &Do_Abi_");
- w.Write(vm);
- w.Write(";\n");
- }
- w.Write("}\n\n");
-
- w.Write("public static ref readonly Guid IID\n{\n [MethodImpl(MethodImplOptions.AggressiveInlining)]\n get => ref ");
- WriteIidGuidReference(w, type);
- w.Write(";\n}\n\n");
-
- w.Write("public static nint Vtable\n{\n [MethodImpl(MethodImplOptions.AggressiveInlining)]\n get => (nint)Unsafe.AsPointer(in Vftbl);\n}\n\n");
-
- // Do_Abi_* implementations: emit real bodies for simple primitive cases,
- // throw null! for everything else (deferred — needs full per-parameter marshalling).
- // Mirror C++: in component mode, exclusive-to interfaces dispatch to the OWNING class
- // type (not the interface) since the authored class IS the implementation. This is what
- // 'write_method_abi_invoke' produces because 'method.Parent()' is treated through
- // 'does_abi_interface_implement_ccw_interface' for authoring scenarios.
- //
- // EXCEPTION: static factory interfaces ([Static] attr on the class) and activation
- // factory interfaces ([Activatable(typeof(IFooFactory))]) are implemented by the
- // generated 'ABI.Impl..'/' types, NOT by the user runtime
- // class. For those, the dispatch target must be 'global::ABI.Impl..'.
- TypeDefinition? exclusiveToOwner = null;
- bool exclusiveIsFactoryOrStatic = false;
- if (w.Settings.Component)
- {
- MetadataCache? cache = GetMetadataCache();
- if (cache is not null)
- {
- exclusiveToOwner = Helpers.GetExclusiveToType(type, cache);
- if (exclusiveToOwner is not null)
- {
- foreach (KeyValuePair kv in AttributedTypes.Get(exclusiveToOwner, cache))
- {
- if (kv.Value.Type == type && (kv.Value.Statics || kv.Value.Activatable))
- {
- exclusiveIsFactoryOrStatic = true;
- break;
- }
- }
- }
- }
- }
-
- string ifaceFullName;
- if (exclusiveToOwner is not null && !exclusiveIsFactoryOrStatic)
- {
- string ownerNs = exclusiveToOwner.Namespace?.Value ?? string.Empty;
- string ownerNm = Helpers.StripBackticks(exclusiveToOwner.Name?.Value ?? string.Empty);
- ifaceFullName = string.IsNullOrEmpty(ownerNs)
- ? "global::" + ownerNm
- : "global::" + ownerNs + "." + ownerNm;
- }
- else if (exclusiveToOwner is not null && exclusiveIsFactoryOrStatic)
- {
- // Factory/static interfaces in authoring mode are implemented by the generated
- // 'global::ABI.Impl..' type that the activation factory CCW exposes.
- string ifaceNs = type.Namespace?.Value ?? string.Empty;
- string ifaceNm = Helpers.StripBackticks(type.Name?.Value ?? string.Empty);
- ifaceFullName = string.IsNullOrEmpty(ifaceNs)
- ? "global::ABI.Impl." + ifaceNm
- : "global::ABI.Impl." + ifaceNs + "." + ifaceNm;
- }
- else
- {
- ifaceFullName = w.WriteTemp("%", new System.Action(_ => WriteTypedefName(w, type, TypedefNameType.Projected, true)));
- if (!ifaceFullName.StartsWith("global::", System.StringComparison.Ordinal)) { ifaceFullName = "global::" + ifaceFullName; }
- }
-
- // Build a map of event add/remove methods to their event so we can emit the table field
- // and the proper Do_Abi_add_*/Do_Abi_remove_* bodies (mirrors C++ write_event_abi_invoke).
- System.Collections.Generic.Dictionary? eventMap = BuildEventMethodMap(type);
-
- // Build sets of property accessors and event accessors so the first loop below can
- // iterate "regular" methods (non-property, non-event) only. C++ emits Do_Abi bodies in
- // this order: methods first, then properties (setter before getter per write_property_abi_invoke
- // at code_writers.h:8245), then events. Mine previously emitted them in pure metadata
- // (slot) order which matched neither truth nor C++.
- System.Collections.Generic.HashSet propertyAccessors = new();
- foreach (PropertyDefinition prop in type.Properties)
- {
- if (prop.GetMethod is MethodDefinition g) { propertyAccessors.Add(g); }
- if (prop.SetMethod is MethodDefinition s) { propertyAccessors.Add(s); }
- }
-
- // Local helper to emit a single Do_Abi method body for a given MethodDefinition.
- void EmitOneDoAbi(MethodDefinition method)
- {
- string vm = GetVMethodName(type, method);
- MethodSig sig = new(method);
- string mname = method.Name?.Value ?? string.Empty;
-
- // If this method is an event add accessor, emit the per-event ConditionalWeakTable
- // before the Do_Abi method (mirrors C++ ordering).
- if (eventMap is not null && eventMap.TryGetValue(method, out EventDefinition? evt) && evt.AddMethod == method)
- {
- EmitEventTableField(w, evt, type, ifaceFullName);
- }
-
- w.Write("[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]\n");
- w.Write("private static unsafe int Do_Abi_");
- w.Write(vm);
- w.Write("(");
- WriteAbiParameterTypesPointer(w, sig, includeParamNames: true);
- w.Write(")");
-
- if (eventMap is not null && eventMap.TryGetValue(method, out EventDefinition? evt2))
- {
- if (evt2.AddMethod == method)
- {
- EmitDoAbiAddEvent(w, evt2, sig, ifaceFullName);
- }
- else
- {
- EmitDoAbiRemoveEvent(w, evt2, sig, ifaceFullName);
- }
- }
- else
- {
- EmitDoAbiBodyIfSimple(w, sig, ifaceFullName, mname);
- }
- }
-
- // 1. Regular methods (non-property, non-event), in metadata order.
- foreach (MethodDefinition method in type.Methods)
- {
- if (propertyAccessors.Contains(method)) { continue; }
- if (eventMap is not null && eventMap.ContainsKey(method)) { continue; }
- EmitOneDoAbi(method);
- }
-
- // 2. Properties, in metadata order. Setter before getter per write_property_abi_invoke.
- foreach (PropertyDefinition prop in type.Properties)
- {
- if (prop.SetMethod is MethodDefinition s) { EmitOneDoAbi(s); }
- if (prop.GetMethod is MethodDefinition g) { EmitOneDoAbi(g); }
- }
-
- // 3. Events, in metadata order. Add then Remove (matches metadata order from BuildEventMethodMap).
- foreach (EventDefinition evt in type.Events)
- {
- if (evt.AddMethod is MethodDefinition a) { EmitOneDoAbi(a); }
- if (evt.RemoveMethod is MethodDefinition r) { EmitOneDoAbi(r); }
- }
- w.Write("}\n");
- }
-
- /// Build a method-to-event map for add/remove accessors of a type.
- private static System.Collections.Generic.Dictionary? BuildEventMethodMap(TypeDefinition type)
- {
- if (type.Events.Count == 0) { return null; }
- System.Collections.Generic.Dictionary map = new();
- foreach (EventDefinition evt in type.Events)
- {
- if (evt.AddMethod is MethodDefinition add) { map[add] = evt; }
- if (evt.RemoveMethod is MethodDefinition rem) { map[rem] = evt; }
- }
- return map;
- }
-
- ///
- /// Emits the per-event ConditionalWeakTable<TInterface, EventRegistrationTokenTable<THandler>>
- /// backing field property. Mirrors the table emission in C++ write_event_abi_invoke.
- /// The is the dispatch target type for the CCW (computed by
- /// the caller in EmitDoAbiBodyIfSimple) — for instance events on authored classes this is
- /// the runtime class type, NOT the ABI.Impl interface.
- ///
- private static void EmitEventTableField(TypeWriter w, EventDefinition evt, TypeDefinition iface, string ifaceFullName)
- {
- string evName = evt.Name?.Value ?? "Event";
- string evtType = w.WriteTemp("%", new System.Action(_ => WriteEventType(w, evt)));
-
- w.Write("\nprivate static ConditionalWeakTable<");
- w.Write(ifaceFullName);
- w.Write(", EventRegistrationTokenTable<");
- w.Write(evtType);
- w.Write(">> _");
- w.Write(evName);
- w.Write("\n{\n");
- w.Write(" [MethodImpl(MethodImplOptions.AggressiveInlining)]\n");
- w.Write(" get\n {\n");
- w.Write(" [MethodImpl(MethodImplOptions.NoInlining)]\n");
- w.Write(" static ConditionalWeakTable<");
- w.Write(ifaceFullName);
- w.Write(", EventRegistrationTokenTable<");
- w.Write(evtType);
- w.Write(">> MakeTable()\n {\n");
- w.Write(" _ = global::System.Threading.Interlocked.CompareExchange(ref field, [], null);\n\n");
- w.Write(" return global::System.Threading.Volatile.Read(in field);\n");
- w.Write(" }\n\n");
- w.Write(" return global::System.Threading.Volatile.Read(in field) ?? MakeTable();\n }\n}\n");
- }
-
- ///
- /// Emits the body of the Do_Abi_add_<EventName>_N method. Mirrors the corresponding
- /// branch in C++ write_event_abi_invoke.
- ///
- private static void EmitDoAbiAddEvent(TypeWriter w, EventDefinition evt, MethodSig sig, string ifaceFullName)
- {
- string evName = evt.Name?.Value ?? "Event";
- // Handler is the (last) input parameter of the add method. The emitted parameter name in the
- // signature comes from WriteAbiParameterTypesPointer which uses the metadata name verbatim.
- string handlerRawName = sig.Params.Count > 0 ? (sig.Params[^1].Parameter.Name ?? "handler") : "handler";
- string handlerRef = Helpers.IsKeyword(handlerRawName) ? "@" + handlerRawName : handlerRawName;
-
- // The cookie/token return parameter takes the metadata return param name (matches truth).
- string cookieName = GetReturnParamName(sig);
-
- AsmResolver.DotNet.Signatures.TypeSignature evtTypeSig = evt.EventType!.ToTypeSignature(false);
- bool isGeneric = evtTypeSig is AsmResolver.DotNet.Signatures.GenericInstanceTypeSignature;
-
- w.Write("\n{\n");
- w.Write(" *");
- w.Write(cookieName);
- w.Write(" = default;\n");
- w.Write(" try\n {\n");
- w.Write(" var __this = ComInterfaceDispatch.GetInstance<");
- w.Write(ifaceFullName);
- w.Write(">((ComInterfaceDispatch*)thisPtr);\n");
-
- if (isGeneric)
- {
- string interopTypeName = EncodeInteropTypeName(evtTypeSig, TypedefNameType.ABI) + ", WinRT.Interop";
- string projectedTypeName = w.WriteTemp("%", new System.Action(_ => WriteProjectedSignature(w, evtTypeSig, false)));
- w.Write(" [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = \"ConvertToManaged\")]\n");
- w.Write(" static extern ");
- w.Write(projectedTypeName);
- w.Write(" ConvertToManaged([UnsafeAccessorType(\"");
- w.Write(interopTypeName);
- w.Write("\")] object _, void* value);\n");
- w.Write(" var __handler = ConvertToManaged(null, ");
- w.Write(handlerRef);
- w.Write(");\n");
- }
- else
- {
- w.Write(" var __handler = ");
- WriteTypeName(w, TypeSemanticsFactory.Get(evtTypeSig), TypedefNameType.ABI, false);
- w.Write("Marshaller.ConvertToManaged(");
- w.Write(handlerRef);
- w.Write(");\n");
- }
-
- w.Write(" *");
- w.Write(cookieName);
- w.Write(" = _");
- w.Write(evName);
- w.Write(".GetOrCreateValue(__this).AddEventHandler(__handler);\n");
- w.Write(" __this.");
- w.Write(evName);
- w.Write(" += __handler;\n");
- w.Write(" return 0;\n }\n");
- w.Write(" catch (Exception __exception__)\n {\n");
- w.Write(" return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(__exception__);\n }\n}\n");
- }
-
- ///
- /// Emits the body of the Do_Abi_remove_<EventName>_N method. Mirrors the corresponding
- /// branch in C++ write_event_abi_invoke.
- ///
- private static void EmitDoAbiRemoveEvent(TypeWriter w, EventDefinition evt, MethodSig sig, string ifaceFullName)
- {
- string evName = evt.Name?.Value ?? "Event";
- string tokenRawName = sig.Params.Count > 0 ? (sig.Params[^1].Parameter.Name ?? "token") : "token";
- string tokenRef = Helpers.IsKeyword(tokenRawName) ? "@" + tokenRawName : tokenRawName;
-
- w.Write("\n{\n");
- w.Write(" try\n {\n");
- w.Write(" var __this = ComInterfaceDispatch.GetInstance<");
- w.Write(ifaceFullName);
- w.Write(">((ComInterfaceDispatch*)thisPtr);\n");
- w.Write(" if(__this is not null && _");
- w.Write(evName);
- w.Write(".TryGetValue(__this, out var __table) && __table.RemoveEventHandler(");
- w.Write(tokenRef);
- w.Write(", out var __handler))\n {\n");
- w.Write(" __this.");
- w.Write(evName);
- w.Write(" -= __handler;\n");
- w.Write(" }\n");
- w.Write(" return 0;\n }\n");
- w.Write(" catch (Exception __exception__)\n {\n");
- w.Write(" return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(__exception__);\n }\n}\n");
- }
-
- ///
- /// Emits a real Do_Abi (CCW) body for the cases we can handle. Mirrors C++
- /// write_abi_method_call_marshalers (code_writers.h:6682) which
- /// unconditionally emits a real body via the abi_marshaler abstraction
- /// for every WinRT-valid signature.
- ///
- private static void EmitDoAbiBodyIfSimple(TypeWriter w, MethodSig sig, string ifaceFullName, string methodName)
- {
- AsmResolver.DotNet.Signatures.TypeSignature? rt = sig.ReturnType;
-
- // String params drive whether we need HString header allocation in the body.
- bool hasStringParams = false;
- foreach (ParamInfo p in sig.Params)
- {
- if (IsString(p.Type)) { hasStringParams = true; break; }
- }
- bool returnIsReceiveArrayDoAbi = rt is AsmResolver.DotNet.Signatures.SzArrayTypeSignature retSzAbi
- && (IsBlittablePrimitive(retSzAbi.BaseType) || IsAnyStruct(retSzAbi.BaseType)
- || IsString(retSzAbi.BaseType) || IsRuntimeClassOrInterface(retSzAbi.BaseType) || IsObject(retSzAbi.BaseType)
- || IsComplexStruct(retSzAbi.BaseType));
- bool returnIsHResultExceptionDoAbi = rt is not null && IsHResultException(rt);
- bool returnIsString = rt is not null && IsString(rt);
- bool returnIsRefType = rt is not null && (IsRuntimeClassOrInterface(rt) || IsObject(rt) || IsGenericInstance(rt));
- bool returnIsGenericInstance = rt is not null && IsGenericInstance(rt);
- bool returnIsBlittableStruct = rt is not null && IsAnyStruct(rt);
-
- bool isGetter = methodName.StartsWith("get_", System.StringComparison.Ordinal);
- bool isSetter = methodName.StartsWith("put_", System.StringComparison.Ordinal);
- bool isAddEvent = methodName.StartsWith("add_", System.StringComparison.Ordinal);
- bool isRemoveEvent = methodName.StartsWith("remove_", System.StringComparison.Ordinal);
-
- if (isAddEvent || isRemoveEvent)
- {
- // Events go through dedicated EmitDoAbiAddEvent / EmitDoAbiRemoveEvent paths
- // upstream (see lines 1153-1159). If we reach here for an event accessor it's a
- // generator bug. Defensive guard against future regressions.
- throw new System.InvalidOperationException(
- $"EmitDoAbiBodyIfSimple: unexpectedly called for event accessor '{methodName}' " +
- $"on '{ifaceFullName}'. Events should dispatch through EmitDoAbiAddEvent / EmitDoAbiRemoveEvent.");
- }
-
- w.Write("\n{\n");
- string retParamName = GetReturnParamName(sig);
- string retSizeParamName = GetReturnSizeParamName(sig);
- // The local name for the unmarshalled return value mirrors C++
- // 'abi_marshaler::get_marshaler_local()' which prefixes '__' to the param name.
- // For the default '__return_value__' param this becomes '____return_value__'.
- string retLocalName = "__" + retParamName;
-
- // Mirror C++ ordering: emit any [UnsafeAccessor] static local function declarations
- // at the TOP of the method body (before local declarations and the try block). The
- // actual call sites later in the body just reference the already-declared accessor.
- // For a generic-instance return type, the accessor is named ConvertToUnmanaged_.
- // Skip Nullable returns: those use Marshaller.BoxToUnmanaged at the call site
- // instead of the generic-instance UnsafeAccessor (V3-M7).
- if (returnIsGenericInstance && !(rt is not null && IsNullableT(rt)))
- {
- string interopTypeName = EncodeInteropTypeName(rt!, TypedefNameType.ABI) + ", WinRT.Interop";
- string projectedTypeName = w.WriteTemp("%", new System.Action(_ => WriteProjectedSignature(w, rt!, false)));
- w.Write(" [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = \"ConvertToUnmanaged\")]\n");
- w.Write(" static extern WindowsRuntimeObjectReferenceValue ConvertToUnmanaged_");
- w.Write(retParamName);
- w.Write("([UnsafeAccessorType(\"");
- w.Write(interopTypeName);
- w.Write("\")] object _, ");
- w.Write(projectedTypeName);
- w.Write(" value);\n\n");
- }
-
- // Hoist [UnsafeAccessor] declarations for Out generic-instance params:
- // ConvertToUnmanaged_ wraps the projected value into a WindowsRuntimeObjectReferenceValue.
- // The body's writeback later references these already-declared accessors.
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.Out) { continue; }
- AsmResolver.DotNet.Signatures.TypeSignature uOut = StripByRefAndCustomModifiers(p.Type);
- if (!IsGenericInstance(uOut)) { continue; }
- string raw = p.Parameter.Name ?? "param";
- string interopTypeName = EncodeInteropTypeName(uOut, TypedefNameType.ABI) + ", WinRT.Interop";
- string projectedTypeName = w.WriteTemp("%", new System.Action(_ => WriteProjectedSignature(w, uOut, false)));
- w.Write(" [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = \"ConvertToUnmanaged\")]\n");
- w.Write(" static extern WindowsRuntimeObjectReferenceValue ConvertToUnmanaged_");
- w.Write(raw);
- w.Write("([UnsafeAccessorType(\"");
- w.Write(interopTypeName);
- w.Write("\")] object _, ");
- w.Write(projectedTypeName);
- w.Write(" value);\n\n");
- }
-
- // Mirror C++ ordering: hoist [UnsafeAccessor] declarations for ReceiveArray (out T[])
- // ConvertToUnmanaged_ and the return-array ConvertToUnmanaged_ to the
- // top of the method body, before locals and the try block. The actual call sites later
- // in the body reference these already-declared accessors.
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.ReceiveArray) { continue; }
- string raw = p.Parameter.Name ?? "param";
- AsmResolver.DotNet.Signatures.SzArrayTypeSignature sza = (AsmResolver.DotNet.Signatures.SzArrayTypeSignature)StripByRefAndCustomModifiers(p.Type);
- string elementProjected = w.WriteTemp("%", new System.Action(_ => WriteProjectionType(w, TypeSemanticsFactory.Get(sza.BaseType))));
- string elementInteropArg = EncodeInteropTypeName(sza.BaseType, TypedefNameType.Projected);
- string marshallerPath = GetArrayMarshallerInteropPath(w, sza.BaseType, elementInteropArg);
- string elementAbi = IsString(sza.BaseType) || IsRuntimeClassOrInterface(sza.BaseType) || IsObject(sza.BaseType)
- ? "void*"
- : IsComplexStruct(sza.BaseType)
- ? GetAbiStructTypeName(w, sza.BaseType)
- : IsAnyStruct(sza.BaseType)
- ? GetBlittableStructAbiType(w, sza.BaseType)
- : GetAbiPrimitiveType(sza.BaseType);
- w.Write(" [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = \"ConvertToUnmanaged\")]\n");
- w.Write(" static extern void ConvertToUnmanaged_");
- w.Write(raw);
- w.Write("([UnsafeAccessorType(\"");
- w.Write(marshallerPath);
- w.Write("\")] object _, ReadOnlySpan<");
- w.Write(elementProjected);
- w.Write("> span, out uint length, out ");
- w.Write(elementAbi);
- w.Write("* data);\n\n");
- }
- if (returnIsReceiveArrayDoAbi && rt is AsmResolver.DotNet.Signatures.SzArrayTypeSignature retSzHoist)
- {
- string elementProjected = w.WriteTemp("%", new System.Action(_ => WriteProjectionType(w, TypeSemanticsFactory.Get(retSzHoist.BaseType))));
- string elementAbi = IsString(retSzHoist.BaseType) || IsRuntimeClassOrInterface(retSzHoist.BaseType) || IsObject(retSzHoist.BaseType)
- ? "void*"
- : IsComplexStruct(retSzHoist.BaseType)
- ? GetAbiStructTypeName(w, retSzHoist.BaseType)
- : IsAnyStruct(retSzHoist.BaseType)
- ? GetBlittableStructAbiType(w, retSzHoist.BaseType)
- : GetAbiPrimitiveType(retSzHoist.BaseType);
- string elementInteropArg = EncodeInteropTypeName(retSzHoist.BaseType, TypedefNameType.Projected);
- string marshallerPath = GetArrayMarshallerInteropPath(w, retSzHoist.BaseType, elementInteropArg);
- w.Write(" [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = \"ConvertToUnmanaged\")]\n");
- w.Write(" static extern void ConvertToUnmanaged_");
- w.Write(retParamName);
- w.Write("([UnsafeAccessorType(\"");
- w.Write(marshallerPath);
- w.Write("\")] object _, ReadOnlySpan<");
- w.Write(elementProjected);
- w.Write("> span, out uint length, out ");
- w.Write(elementAbi);
- w.Write("* data);\n\n");
- }
-
- // Mirror C++ ordering: declare the return local first with default value, then zero
- // the OUT pointer(s). The actual assignment happens inside the try block.
- if (rt is not null)
- {
- if (returnIsString)
- {
- w.Write(" string ");
- w.Write(retLocalName);
- w.Write(" = default;\n");
- }
- else if (returnIsRefType)
- {
- string projected = w.WriteTemp("%", new System.Action(_ => WriteProjectedSignature(w, rt, false)));
- w.Write(" ");
- w.Write(projected);
- w.Write(" ");
- w.Write(retLocalName);
- w.Write(" = default;\n");
- }
- else if (returnIsReceiveArrayDoAbi)
- {
- string projected = w.WriteTemp("%", new System.Action(_ => WriteProjectedSignature(w, rt, false)));
- w.Write(" ");
- w.Write(projected);
- w.Write(" ");
- w.Write(retLocalName);
- w.Write(" = default;\n");
- }
- else
- {
- string projected = w.WriteTemp("%", new System.Action(_ => WriteProjectedSignature(w, rt, false)));
- w.Write(" ");
- w.Write(projected);
- w.Write(" ");
- w.Write(retLocalName);
- w.Write(" = default;\n");
- }
- }
-
- if (rt is not null)
- {
- if (returnIsReceiveArrayDoAbi)
- {
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = default;\n");
- w.Write(" *");
- w.Write(retSizeParamName);
- w.Write(" = default;\n");
- }
- else
- {
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = default;\n");
- }
- }
- // For each out parameter, clear the destination and declare a local.
- // NOTE: Ref params (WinRT 'in T' / 'ref const T') are READ-ONLY inputs from the caller's
- // perspective. Do NOT zero * (it's the input value) and do NOT declare a local
- // (we read directly via *).
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.Out) { continue; }
- string raw = p.Parameter.Name ?? "param";
- string ptr = Helpers.IsKeyword(raw) ? "@" + raw : raw;
- w.Write(" *");
- w.Write(ptr);
- w.Write(" = default;\n");
- }
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.Out) { continue; }
- string raw = p.Parameter.Name ?? "param";
- // Use the projected (non-ABI) type for the local variable.
- // Strip ByRef and CustomModifier wrappers to get the underlying base type.
- AsmResolver.DotNet.Signatures.TypeSignature underlying = StripByRefAndCustomModifiers(p.Type);
- string projected = w.WriteTemp("%", new System.Action(_ => WriteProjectedSignature(w, underlying, false)));
- w.Write(" ");
- w.Write(projected);
- w.Write(" __");
- w.Write(raw);
- w.Write(" = default;\n");
- }
- // For each ReceiveArray parameter (out T[]), zero the destination + size out pointers
- // and declare a managed array local. The managed call passes 'out __' and after
- // the call we copy to the ABI buffer via UnsafeAccessor.
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.ReceiveArray) { continue; }
- string raw = p.Parameter.Name ?? "param";
- string ptr = Helpers.IsKeyword(raw) ? "@" + raw : raw;
- AsmResolver.DotNet.Signatures.SzArrayTypeSignature sza = (AsmResolver.DotNet.Signatures.SzArrayTypeSignature)StripByRefAndCustomModifiers(p.Type);
- string elementProjected = w.WriteTemp("%", new System.Action(_ => WriteProjectionType(w, TypeSemanticsFactory.Get(sza.BaseType))));
- w.Write(" *");
- w.Write(ptr);
- w.Write(" = default;\n");
- w.Write(" *__");
- w.Write(raw);
- w.Write("Size = default;\n");
- w.Write(" ");
- w.Write(elementProjected);
- w.Write("[] __");
- w.Write(raw);
- w.Write(" = default;\n");
- }
- // For each blittable array (PassArray / FillArray) parameter, declare a Span local that
- // wraps the (length, pointer) pair from the ABI signature.
- // For non-blittable element types (string/runtime class/object), declare InlineArray16 +
- // ArrayPool fallback then CopyToManaged via UnsafeAccessor.
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.PassArray && cat != ParamCategory.FillArray) { continue; }
- if (p.Type is not AsmResolver.DotNet.Signatures.SzArrayTypeSignature sz) { continue; }
- string raw = p.Parameter.Name ?? "param";
- string ptr = Helpers.IsKeyword(raw) ? "@" + raw : raw;
- string elementProjected = w.WriteTemp("%", new System.Action(_ => WriteProjectionType(w, TypeSemanticsFactory.Get(sz.BaseType))));
- bool isBlittableElem = IsBlittablePrimitive(sz.BaseType) || IsAnyStruct(sz.BaseType);
- if (isBlittableElem)
- {
- w.Write(" ");
- w.Write(cat == ParamCategory.PassArray ? "ReadOnlySpan<" : "Span<");
- w.Write(elementProjected);
- w.Write("> __");
- w.Write(raw);
- w.Write(" = new(");
- w.Write(ptr);
- w.Write(", (int)__");
- w.Write(raw);
- w.Write("Size);\n");
- }
- else
- {
- // Non-blittable element: InlineArray16 + ArrayPool with size from ABI.
- w.Write("\n Unsafe.SkipInit(out InlineArray16<");
- w.Write(elementProjected);
- w.Write("> __");
- w.Write(raw);
- w.Write("_inlineArray);\n");
- w.Write(" ");
- w.Write(elementProjected);
- w.Write("[] __");
- w.Write(raw);
- w.Write("_arrayFromPool = null;\n");
- w.Write(" Span<");
- w.Write(elementProjected);
- w.Write("> __");
- w.Write(raw);
- w.Write(" = __");
- w.Write(raw);
- w.Write("Size <= 16\n ? __");
- w.Write(raw);
- w.Write("_inlineArray[..(int)__");
- w.Write(raw);
- w.Write("Size]\n : (__");
- w.Write(raw);
- w.Write("_arrayFromPool = global::System.Buffers.ArrayPool<");
- w.Write(elementProjected);
- w.Write(">.Shared.Rent((int)__");
- w.Write(raw);
- w.Write("Size));\n");
- }
- }
- w.Write(" try\n {\n");
-
- // For non-blittable PassArray params (read-only input arrays), emit CopyToManaged_
- // via UnsafeAccessor to convert the native ABI buffer into the managed Span the
- // delegate sees. For FillArray params, the buffer is fresh storage the user delegate
- // fills — the post-call writeback loop handles that. (Mirrors C++ which only emits the
- // pre-call CopyToManaged for PassArray, see code_writers.h:7772 write_copy_to_managed.)
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.PassArray) { continue; }
- if (p.Type is not AsmResolver.DotNet.Signatures.SzArrayTypeSignature szArr) { continue; }
- if (IsBlittablePrimitive(szArr.BaseType) || IsAnyStruct(szArr.BaseType)) { continue; }
- string raw = p.Parameter.Name ?? "param";
- string ptr = Helpers.IsKeyword(raw) ? "@" + raw : raw;
- string elementProjected = w.WriteTemp("%", new System.Action(_ => WriteProjectionType(w, TypeSemanticsFactory.Get(szArr.BaseType))));
- string elementInteropArg = EncodeInteropTypeName(szArr.BaseType, TypedefNameType.Projected);
- // For complex structs, the data param is the ABI struct pointer (e.g. BasicStruct*).
- // The Do_Abi parameter we receive is void* (per V3R3-M8), so the call-site needs an
- // explicit (T*) cast to bridge the type. For ref-types (string/runtime-class/object),
- // the data param is void** and the cast is (void**).
- string dataParamType;
- string dataCastExpr;
- if (IsComplexStruct(szArr.BaseType))
- {
- string abiStructName = GetAbiStructTypeName(w, szArr.BaseType);
- dataParamType = abiStructName + "* data";
- dataCastExpr = "(" + abiStructName + "*)" + ptr;
- }
- else
- {
- dataParamType = "void** data";
- dataCastExpr = "(void**)" + ptr;
- }
- w.Write(" [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = \"CopyToManaged\")]\n");
- w.Write(" static extern void CopyToManaged_");
- w.Write(raw);
- w.Write("([UnsafeAccessorType(\"");
- w.Write(GetArrayMarshallerInteropPath(w, szArr.BaseType, elementInteropArg));
- w.Write("\")] object _, uint length, ");
- w.Write(dataParamType);
- w.Write(", Span<");
- w.Write(elementProjected);
- w.Write("> span);\n");
- w.Write(" CopyToManaged_");
- w.Write(raw);
- w.Write("(null, __");
- w.Write(raw);
- w.Write("Size, ");
- w.Write(dataCastExpr);
- w.Write(", __");
- w.Write(raw);
- w.Write(");\n");
- }
-
- // For generic instance ABI input parameters, emit local UnsafeAccessor delegates and locals
- // first so the call site can reference them.
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- if (IsNullableT(p.Type))
- {
- // Nullable param (server-side): use Marshaller.UnboxToManaged. Mirrors truth pattern.
- string rawName = p.Parameter.Name ?? "param";
- string callName = Helpers.IsKeyword(rawName) ? "@" + rawName : rawName;
- AsmResolver.DotNet.Signatures.TypeSignature inner = GetNullableInnerType(p.Type)!;
- string innerMarshaller = GetNullableInnerMarshallerName(w, inner);
- w.Write(" var __arg_");
- w.Write(rawName);
- w.Write(" = ");
- w.Write(innerMarshaller);
- w.Write(".UnboxToManaged(");
- w.Write(callName);
- w.Write(");\n");
- }
- else if (IsGenericInstance(p.Type))
- {
- string rawName = p.Parameter.Name ?? "param";
- string callName = Helpers.IsKeyword(rawName) ? "@" + rawName : rawName;
- string interopTypeName = EncodeInteropTypeName(p.Type, TypedefNameType.ABI) + ", WinRT.Interop";
- string projectedTypeName = w.WriteTemp("%", new System.Action(_ => WriteProjectedSignature(w, p.Type, false)));
- w.Write(" [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = \"ConvertToManaged\")]\n");
- w.Write(" static extern ");
- w.Write(projectedTypeName);
- w.Write(" ConvertToManaged_arg_");
- w.Write(rawName);
- w.Write("([UnsafeAccessorType(\"");
- w.Write(interopTypeName);
- w.Write("\")] object _, void* value);\n");
- w.Write(" var __arg_");
- w.Write(rawName);
- w.Write(" = ConvertToManaged_arg_");
- w.Write(rawName);
- w.Write("(null, ");
- w.Write(callName);
- w.Write(");\n");
- }
- }
-
- if (returnIsString)
- {
- w.Write(" ");
- w.Write(retLocalName);
- w.Write(" = ");
- }
- else if (returnIsRefType)
- {
- w.Write(" ");
- w.Write(retLocalName);
- w.Write(" = ");
- }
- else if (returnIsReceiveArrayDoAbi)
- {
- // For T[] return: assign to existing local.
- w.Write(" ");
- w.Write(retLocalName);
- w.Write(" = ");
- }
- else if (rt is not null)
- {
- w.Write(" ");
- w.Write(retLocalName);
- w.Write(" = ");
- }
- else
- {
- w.Write(" ");
- }
-
- if (isGetter)
- {
- string propName = methodName.Substring(4);
- w.Write("ComInterfaceDispatch.GetInstance<");
- w.Write(ifaceFullName);
- w.Write(">((ComInterfaceDispatch*)thisPtr).");
- w.Write(propName);
- w.Write(";\n");
- }
- else if (isSetter)
- {
- string propName = methodName.Substring(4);
- w.Write("ComInterfaceDispatch.GetInstance<");
- w.Write(ifaceFullName);
- w.Write(">((ComInterfaceDispatch*)thisPtr).");
- w.Write(propName);
- w.Write(" = ");
- EmitDoAbiParamArgConversion(w, sig.Params[0]);
- w.Write(";\n");
- }
- else
- {
- w.Write("ComInterfaceDispatch.GetInstance<");
- w.Write(ifaceFullName);
- w.Write(">((ComInterfaceDispatch*)thisPtr).");
- w.Write(methodName);
- w.Write("(");
- for (int i = 0; i < sig.Params.Count; i++)
- {
- if (i > 0) { w.Write(",\n "); }
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat == ParamCategory.Out)
- {
- string raw = p.Parameter.Name ?? "param";
- w.Write("out __");
- w.Write(raw);
- }
- else if (cat == ParamCategory.Ref)
- {
- // WinRT 'in T' / 'ref const T' is a read-only by-ref input on the ABI side
- // (pointer to a value the native caller owns). On the C# delegate / interface
- // side it's projected as 'in T'. Read directly from * via the appropriate
- // marshaller — DO NOT zero or write back.
- string raw = p.Parameter.Name ?? "param";
- string ptr = Helpers.IsKeyword(raw) ? "@" + raw : raw;
- AsmResolver.DotNet.Signatures.TypeSignature uRef = StripByRefAndCustomModifiers(p.Type);
- if (IsString(uRef))
- {
- w.Write("HStringMarshaller.ConvertToManaged(*");
- w.Write(ptr);
- w.Write(")");
- }
- else if (IsObject(uRef))
- {
- w.Write("WindowsRuntimeObjectMarshaller.ConvertToManaged(*");
- w.Write(ptr);
- w.Write(")");
- }
- else if (IsRuntimeClassOrInterface(uRef))
- {
- w.Write(GetMarshallerFullName(w, uRef));
- w.Write(".ConvertToManaged(*");
- w.Write(ptr);
- w.Write(")");
- }
- else if (IsMappedAbiValueType(uRef))
- {
- w.Write(GetMappedMarshallerName(uRef));
- w.Write(".ConvertToManaged(*");
- w.Write(ptr);
- w.Write(")");
- }
- else if (IsHResultException(uRef))
- {
- w.Write("global::ABI.System.ExceptionMarshaller.ConvertToManaged(*");
- w.Write(ptr);
- w.Write(")");
- }
- else if (IsComplexStruct(uRef))
- {
- w.Write(GetMarshallerFullName(w, uRef));
- w.Write(".ConvertToManaged(*");
- w.Write(ptr);
- w.Write(")");
- }
- else if (IsAnyStruct(uRef) || IsBlittablePrimitive(uRef) || IsEnumType(uRef))
- {
- // Blittable/almost-blittable: ABI layout matches projected layout.
- w.Write("*");
- w.Write(ptr);
- }
- else
- {
- w.Write("*");
- w.Write(ptr);
- }
- }
- else if (cat == ParamCategory.PassArray || cat == ParamCategory.FillArray)
- {
- string raw = p.Parameter.Name ?? "param";
- w.Write("__");
- w.Write(raw);
- }
- else if (cat == ParamCategory.ReceiveArray)
- {
- string raw = p.Parameter.Name ?? "param";
- w.Write("out __");
- w.Write(raw);
- }
- else
- {
- EmitDoAbiParamArgConversion(w, p);
- }
- }
- w.Write(");\n");
- }
- // After call: write back out params to caller's pointer.
- // NOTE: Ref params (WinRT 'in T') are read-only inputs — never written back.
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.Out) { continue; }
- string raw = p.Parameter.Name ?? "param";
- string ptr = Helpers.IsKeyword(raw) ? "@" + raw : raw;
- AsmResolver.DotNet.Signatures.TypeSignature underlying = StripByRefAndCustomModifiers(p.Type);
- w.Write(" *");
- w.Write(ptr);
- w.Write(" = ");
- // String: HStringMarshaller.ConvertToUnmanaged
- if (IsString(underlying))
- {
- w.Write("HStringMarshaller.ConvertToUnmanaged(__");
- w.Write(raw);
- w.Write(")");
- }
- // Object/runtime class: .ConvertToUnmanaged(...).DetachThisPtrUnsafe()
- else if (IsObject(underlying))
- {
- w.Write("WindowsRuntimeObjectMarshaller.ConvertToUnmanaged(__");
- w.Write(raw);
- w.Write(").DetachThisPtrUnsafe()");
- }
- else if (IsRuntimeClassOrInterface(underlying))
- {
- w.Write(GetMarshallerFullName(w, underlying));
- w.Write(".ConvertToUnmanaged(__");
- w.Write(raw);
- w.Write(").DetachThisPtrUnsafe()");
- }
- // Generic instance (e.g. IEnumerable): use the hoisted UnsafeAccessor
- // 'ConvertToUnmanaged_' declared at the top of the method body.
- else if (IsGenericInstance(underlying))
- {
- w.Write("ConvertToUnmanaged_");
- w.Write(raw);
- w.Write("(null, __");
- w.Write(raw);
- w.Write(").DetachThisPtrUnsafe()");
- }
- // For enums, function pointer signature uses the projected enum type, no cast needed.
- // For bool, cast to byte. For char, cast to ushort.
- else if (IsEnumType(underlying))
- {
- w.Write("__");
- w.Write(raw);
- }
- else if (underlying is AsmResolver.DotNet.Signatures.CorLibTypeSignature corlibBool &&
- corlibBool.ElementType == AsmResolver.PE.DotNet.Metadata.Tables.ElementType.Boolean)
- {
- w.Write("__");
- w.Write(raw);
- }
- else if (underlying is AsmResolver.DotNet.Signatures.CorLibTypeSignature corlibChar &&
- corlibChar.ElementType == AsmResolver.PE.DotNet.Metadata.Tables.ElementType.Char)
- {
- w.Write("__");
- w.Write(raw);
- }
- // Non-blittable struct (e.g. authored BasicStruct with string fields): marshal
- // the local managed value through Marshaller.ConvertToUnmanaged before
- // writing it into the *out ABI struct slot. Mirrors C++ marshaler.write_marshal_from_managed
- // (code_writers.h:7901-7910): "Marshaller.ConvertToUnmanaged(local)".
- else if (IsComplexStruct(underlying))
- {
- w.Write(GetMarshallerFullName(w, underlying));
- w.Write(".ConvertToUnmanaged(__");
- w.Write(raw);
- w.Write(")");
- }
- else
- {
- w.Write("__");
- w.Write(raw);
- }
- w.Write(";\n");
- }
- // After call: for ReceiveArray params, emit ConvertToUnmanaged_ call (the
- // [UnsafeAccessor] declaration was hoisted to the top of the method body).
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.ReceiveArray) { continue; }
- string raw = p.Parameter.Name ?? "param";
- string ptr = Helpers.IsKeyword(raw) ? "@" + raw : raw;
- w.Write(" ConvertToUnmanaged_");
- w.Write(raw);
- w.Write("(null, __");
- w.Write(raw);
- w.Write(", out *__");
- w.Write(raw);
- w.Write("Size, out *");
- w.Write(ptr);
- w.Write(");\n");
- }
- // After call: for non-blittable FillArray params (Span where T is string/runtime
- // class/object/non-blittable struct), copy the managed delegate's writes back into the
- // native ABI buffer. Mirrors C++ write_marshal_from_managed (code_writers.h:7852-7869)
- // which emits 'CopyToUnmanaged_(null, __, __Size, (T*))'.
- // Blittable element types don't need this — the Span wraps the native buffer directly.
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.FillArray) { continue; }
- if (p.Type is not AsmResolver.DotNet.Signatures.SzArrayTypeSignature szFA) { continue; }
- // Blittable element types: Span wraps the native buffer; no copy-back needed.
- if (IsBlittablePrimitive(szFA.BaseType) || IsAnyStruct(szFA.BaseType)) { continue; }
- string raw = p.Parameter.Name ?? "param";
- string ptr = Helpers.IsKeyword(raw) ? "@" + raw : raw;
- string elementProjected = w.WriteTemp("%", new System.Action(_ => WriteProjectionType(w, TypeSemanticsFactory.Get(szFA.BaseType))));
- string elementInteropArg = EncodeInteropTypeName(szFA.BaseType, TypedefNameType.Projected);
- // Determine the ABI element type for the data pointer cast.
- // - Strings / runtime classes / objects: void**
- // - HResult exception: global::ABI.System.Exception*
- // - Mapped value types (DateTime/TimeSpan): global::ABI.System.{DateTimeOffset/TimeSpan}*
- // - Complex structs: *
- string dataParamType;
- string dataCastType;
- if (IsString(szFA.BaseType) || IsRuntimeClassOrInterface(szFA.BaseType) || IsObject(szFA.BaseType))
- {
- dataParamType = "void** data";
- dataCastType = "(void**)";
- }
- else if (IsHResultException(szFA.BaseType))
- {
- dataParamType = "global::ABI.System.Exception* data";
- dataCastType = "(global::ABI.System.Exception*)";
- }
- else if (IsMappedAbiValueType(szFA.BaseType))
- {
- string abiName = GetMappedAbiTypeName(szFA.BaseType);
- dataParamType = abiName + "* data";
- dataCastType = "(" + abiName + "*)";
- }
- else
- {
- string abiStructName = GetAbiStructTypeName(w, szFA.BaseType);
- dataParamType = abiStructName + "* data";
- dataCastType = "(" + abiStructName + "*)";
- }
- w.Write(" [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = \"CopyToUnmanaged\")]\n");
- w.Write(" static extern void CopyToUnmanaged_");
- w.Write(raw);
- w.Write("([UnsafeAccessorType(\"");
- w.Write(GetArrayMarshallerInteropPath(w, szFA.BaseType, elementInteropArg));
- w.Write("\")] object _, ReadOnlySpan<");
- w.Write(elementProjected);
- w.Write("> span, uint length, ");
- w.Write(dataParamType);
- w.Write(");\n");
- w.Write(" CopyToUnmanaged_");
- w.Write(raw);
- w.Write("(null, __");
- w.Write(raw);
- w.Write(", __");
- w.Write(raw);
- w.Write("Size, ");
- w.Write(dataCastType);
- w.Write(ptr);
- w.Write(");\n");
- }
- if (rt is not null)
- {
- if (returnIsHResultExceptionDoAbi)
- {
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = global::ABI.System.ExceptionMarshaller.ConvertToUnmanaged(");
- w.Write(retLocalName);
- w.Write(");\n");
- }
- else if (returnIsString)
- {
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = HStringMarshaller.ConvertToUnmanaged(");
- w.Write(retLocalName);
- w.Write(");\n");
- }
- else if (returnIsRefType)
- {
- if (rt is not null && IsNullableT(rt))
- {
- // Nullable return (server-side): use Marshaller.BoxToUnmanaged.
- AsmResolver.DotNet.Signatures.TypeSignature inner = GetNullableInnerType(rt)!;
- string innerMarshaller = GetNullableInnerMarshallerName(w, inner);
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = ");
- w.Write(innerMarshaller);
- w.Write(".BoxToUnmanaged(");
- w.Write(retLocalName);
- w.Write(").DetachThisPtrUnsafe();\n");
- }
- else if (returnIsGenericInstance)
- {
- // Generic instance return: use the UnsafeAccessor static local function declared at
- // the top of the method body via the M12 hoisting pass; just emit the call here.
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = ConvertToUnmanaged_");
- w.Write(retParamName);
- w.Write("(null, ");
- w.Write(retLocalName);
- w.Write(").DetachThisPtrUnsafe();\n");
- }
- else
- {
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = ");
- EmitMarshallerConvertToUnmanaged(w, rt!, retLocalName);
- w.Write(".DetachThisPtrUnsafe();\n");
- }
- }
- else if (returnIsReceiveArrayDoAbi)
- {
- // Return-receive-array: emit ConvertToUnmanaged_ call (declaration
- // was hoisted to the top of the method body).
- w.Write(" ConvertToUnmanaged_");
- w.Write(retParamName);
- w.Write("(null, ");
- w.Write(retLocalName);
- w.Write(", out *");
- w.Write(retSizeParamName);
- w.Write(", out *");
- w.Write(retParamName);
- w.Write(");\n");
- }
- else if (IsMappedAbiValueType(rt))
- {
- // Mapped value type return (DateTime/TimeSpan): convert via marshaller.
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = ");
- w.Write(GetMappedMarshallerName(rt));
- w.Write(".ConvertToUnmanaged(");
- w.Write(retLocalName);
- w.Write(");\n");
- }
- else if (IsSystemType(rt))
- {
- // System.Type return (server-side): convert managed System.Type to ABI Type struct.
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = global::ABI.System.TypeMarshaller.ConvertToUnmanaged(");
- w.Write(retLocalName);
- w.Write(");\n");
- }
- else if (IsComplexStruct(rt))
- {
- // Complex struct return (server-side): convert managed struct to ABI struct via marshaller.
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = ");
- w.Write(GetMarshallerFullName(w, rt));
- w.Write(".ConvertToUnmanaged(");
- w.Write(retLocalName);
- w.Write(");\n");
- }
- else if (returnIsBlittableStruct)
- {
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = ");
- w.Write(retLocalName);
- w.Write(";\n");
- }
- else
- {
- string abiType = GetAbiPrimitiveType(rt);
- w.Write(" *");
- w.Write(retParamName);
- w.Write(" = ");
- if (rt is AsmResolver.DotNet.Signatures.CorLibTypeSignature corlib &&
- corlib.ElementType == AsmResolver.PE.DotNet.Metadata.Tables.ElementType.Boolean)
- {
- w.Write(retLocalName);
- w.Write(";\n");
- }
- else if (rt is AsmResolver.DotNet.Signatures.CorLibTypeSignature corlib2 &&
- corlib2.ElementType == AsmResolver.PE.DotNet.Metadata.Tables.ElementType.Char)
- {
- w.Write(retLocalName);
- w.Write(";\n");
- }
- else if (IsEnumType(rt))
- {
- // Enum: function pointer signature uses the projected enum type, no cast needed.
- w.Write(retLocalName);
- w.Write(";\n");
- }
- else
- {
- w.Write(retLocalName);
- w.Write(";\n");
- }
- }
- }
- w.Write(" return 0;\n }\n");
- w.Write(" catch (Exception __exception__)\n {\n");
- w.Write(" return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(__exception__);\n }\n");
-
- // For non-blittable PassArray params, emit finally block with ArrayPool.Shared.Return.
- bool hasNonBlittableArrayDoAbi = false;
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.PassArray && cat != ParamCategory.FillArray) { continue; }
- if (p.Type is not AsmResolver.DotNet.Signatures.SzArrayTypeSignature szArr) { continue; }
- if (IsBlittablePrimitive(szArr.BaseType) || IsAnyStruct(szArr.BaseType)) { continue; }
- hasNonBlittableArrayDoAbi = true;
- break;
- }
- if (hasNonBlittableArrayDoAbi)
- {
- w.Write(" finally\n {\n");
- for (int i = 0; i < sig.Params.Count; i++)
- {
- ParamInfo p = sig.Params[i];
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat != ParamCategory.PassArray && cat != ParamCategory.FillArray) { continue; }
- if (p.Type is not AsmResolver.DotNet.Signatures.SzArrayTypeSignature szArr) { continue; }
- if (IsBlittablePrimitive(szArr.BaseType) || IsAnyStruct(szArr.BaseType)) { continue; }
- string raw = p.Parameter.Name ?? "param";
- string elementProjected = w.WriteTemp("%", new System.Action(_ => WriteProjectionType(w, TypeSemanticsFactory.Get(szArr.BaseType))));
- w.Write("\n if (__");
- w.Write(raw);
- w.Write("_arrayFromPool is not null)\n {\n");
- w.Write(" global::System.Buffers.ArrayPool<");
- w.Write(elementProjected);
- w.Write(">.Shared.Return(__");
- w.Write(raw);
- w.Write("_arrayFromPool);\n }\n");
- }
- w.Write(" }\n");
- }
-
- w.Write("}\n\n");
- _ = hasStringParams;
- }
-
- /// Converts an ABI parameter to its projected (managed) form for the Do_Abi call.
- private static void EmitDoAbiParamArgConversion(TypeWriter w, ParamInfo p)
- {
- string rawName = p.Parameter.Name ?? "param";
- string pname = Helpers.IsKeyword(rawName) ? "@" + rawName : rawName;
- if (p.Type is AsmResolver.DotNet.Signatures.CorLibTypeSignature corlib &&
- corlib.ElementType == AsmResolver.PE.DotNet.Metadata.Tables.ElementType.Boolean)
- {
- w.Write(pname);
- }
- else if (p.Type is AsmResolver.DotNet.Signatures.CorLibTypeSignature corlib2 &&
- corlib2.ElementType == AsmResolver.PE.DotNet.Metadata.Tables.ElementType.Char)
- {
- w.Write(pname);
- }
- else if (p.Type is AsmResolver.DotNet.Signatures.CorLibTypeSignature corlibStr &&
- corlibStr.ElementType == AsmResolver.PE.DotNet.Metadata.Tables.ElementType.String)
- {
- w.Write("HStringMarshaller.ConvertToManaged(");
- w.Write(pname);
- w.Write(")");
- }
- else if (IsGenericInstance(p.Type))
- {
- // Generic instance ABI parameter: caller already declared a local UnsafeAccessor +
- // local var __arg_ that holds the converted value.
- w.Write("__arg_");
- w.Write(rawName);
- }
- else if (IsRuntimeClassOrInterface(p.Type) || IsObject(p.Type))
- {
- EmitMarshallerConvertToManaged(w, p.Type, pname);
- }
- else if (IsMappedAbiValueType(p.Type))
- {
- // Mapped value type input (DateTime/TimeSpan): the parameter is the ABI type;
- // convert to the projected managed type via the marshaller.
- w.Write(GetMappedMarshallerName(p.Type));
- w.Write(".ConvertToManaged(");
- w.Write(pname);
- w.Write(")");
- }
- else if (IsSystemType(p.Type))
- {
- // System.Type input (server-side): convert ABI Type struct to System.Type.
- w.Write("global::ABI.System.TypeMarshaller.ConvertToManaged(");
- w.Write(pname);
- w.Write(")");
- }
- else if (IsComplexStruct(p.Type))
- {
- // Complex struct input (server-side): convert ABI struct to managed via marshaller.
- w.Write(GetMarshallerFullName(w, p.Type));
- w.Write(".ConvertToManaged(");
- w.Write(pname);
- w.Write(")");
- }
- else if (IsAnyStruct(p.Type))
- {
- // Blittable / almost-blittable struct: pass directly (projected type == ABI type).
- w.Write(pname);
- }
- else if (IsEnumType(p.Type))
- {
- // Enum: param signature is already the projected enum type, no cast needed.
- w.Write(pname);
- }
- else
- {
- w.Write(pname);
- }
- }
-
- /// Mirrors C++ write_interface_idic_impl.
- public static void WriteInterfaceIdicImpl(TypeWriter w, TypeDefinition type)
- {
- if (TypeCategorization.IsExclusiveTo(type) && !w.Settings.IdicExclusiveTo) { return; }
- if (type.GenericParameters.Count > 0) { return; }
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
-
- w.Write("\n[DynamicInterfaceCastableImplementation]\n");
- WriteGuidAttribute(w, type);
- w.Write("\n");
- w.Write("file interface ");
- w.Write(nameStripped);
- w.Write(" : ");
- WriteTypedefName(w, type, TypedefNameType.Projected, false);
- WriteTypeParams(w, type);
- w.Write("\n{\n");
- // Emit DIM bodies that dispatch through the static ABI Methods class.
- WriteInterfaceIdicImplMembers(w, type);
- w.Write("\n}\n");
- }
-
- ///
- /// Emits explicit-interface DIM (default interface method) implementations for the IDIC
- /// file interface. Mirrors C++ write_interface_members.
- ///
- private static void WriteInterfaceIdicImplMembers(TypeWriter w, TypeDefinition type)
- {
- HashSet visited = new();
- WriteInterfaceIdicImplMembersForInterface(w, type, visited);
-
- // Also walk required (inherited) interfaces and emit members for each one.
- // Mirrors C++ write_required_interface_members_for_abi_type.
- WriteInterfaceIdicImplMembersForRequiredInterfaces(w, type, visited);
- }
-
- private static void WriteInterfaceIdicImplMembersForRequiredInterfaces(
- TypeWriter w, TypeDefinition type, HashSet visited)
- {
- foreach (InterfaceImplementation impl in type.Interfaces)
- {
- if (impl.Interface is null) { continue; }
- TypeDefinition? required = ResolveInterfaceTypeDef(impl.Interface);
- if (required is null) { continue; }
- if (!visited.Add(required)) { continue; }
- string rNs = required.Namespace?.Value ?? string.Empty;
- string rName = required.Name?.Value ?? string.Empty;
- MappedType? mapped = MappedTypes.Get(rNs, rName);
- if (mapped is not null && mapped.HasCustomMembersOutput)
- {
- // Mapped to a BCL interface (IBindableVector -> IList, IBindableIterable -> IEnumerable, etc.).
- // Emit explicit-interface DIM forwarders for the BCL members so the DIC shim
- // satisfies them when queried via casts like '((IList)(WindowsRuntimeObject)this)'.
- EmitDicShimMappedBclForwarders(w, rName);
- // IBindableVector's IList forwarders already include the IEnumerable.GetEnumerator
- // forwarder (since IList : IEnumerable). Pre-add IBindableIterable to the visited
- // set so we don't emit a second GetEnumerator forwarder for it. We also walk the
- // required interfaces so any other (deeper) inherited mapped interface is covered.
- if (rName == "IBindableVector")
- {
- foreach (InterfaceImplementation impl2 in required.Interfaces)
- {
- if (impl2.Interface is null) { continue; }
- TypeDefinition? r2 = ResolveInterfaceTypeDef(impl2.Interface);
- if (r2 is not null) { visited.Add(r2); }
- }
- }
- continue;
- }
- // Special case: IObservableMap`2 and IObservableVector`1 are NOT mapped to BCL
- // interfaces (they retain WinRT names) but they DO need to forward their inherited
- // IDictionary/IList members for cast-based dispatch. Mirrors C++ which uses
- // write_dictionary_members_using_idic / write_list_members_using_idic when walking
- // these interfaces in write_required_interface_members_for_abi_type.
- if (rNs == "Windows.Foundation.Collections" && rName == "IObservableMap`2")
- {
- if (impl.Interface is TypeSpecification tsMap && tsMap.Signature is AsmResolver.DotNet.Signatures.GenericInstanceTypeSignature giMap && giMap.TypeArguments.Count == 2)
- {
- string keyText = w.WriteTemp("%", new System.Action(_ => WriteTypeName(w, TypeSemanticsFactory.Get(giMap.TypeArguments[0]), TypedefNameType.Projected, true)));
- string valueText = w.WriteTemp("%", new System.Action(_ => WriteTypeName(w, TypeSemanticsFactory.Get(giMap.TypeArguments[1]), TypedefNameType.Projected, true)));
- EmitDicShimIObservableMapForwarders(w, keyText, valueText);
- // Mark the inherited IMap`2 / IIterable`1 as visited so they aren't re-emitted.
- foreach (InterfaceImplementation impl2 in required.Interfaces)
- {
- if (impl2.Interface is null) { continue; }
- TypeDefinition? r2 = ResolveInterfaceTypeDef(impl2.Interface);
- if (r2 is not null) { visited.Add(r2); }
- }
- }
- continue;
- }
- if (rNs == "Windows.Foundation.Collections" && rName == "IObservableVector`1")
- {
- if (impl.Interface is TypeSpecification tsVec && tsVec.Signature is AsmResolver.DotNet.Signatures.GenericInstanceTypeSignature giVec && giVec.TypeArguments.Count == 1)
- {
- string elementText = w.WriteTemp("%", new System.Action(_ => WriteTypeName(w, TypeSemanticsFactory.Get(giVec.TypeArguments[0]), TypedefNameType.Projected, true)));
- EmitDicShimIObservableVectorForwarders(w, elementText);
- foreach (InterfaceImplementation impl2 in required.Interfaces)
- {
- if (impl2.Interface is null) { continue; }
- TypeDefinition? r2 = ResolveInterfaceTypeDef(impl2.Interface);
- if (r2 is not null) { visited.Add(r2); }
- }
- }
- continue;
- }
- // Skip generic interfaces with unbound params (we can't substitute T at this layer).
- if (required.GenericParameters.Count > 0) { continue; }
- // Recurse first so deepest-base is emitted before nearer-base (matches deduplication).
- WriteInterfaceIdicImplMembersForRequiredInterfaces(w, required, visited);
- WriteInterfaceIdicImplMembersForInheritedInterface(w, required);
- }
- }
-
- ///
- /// Emits IDictionary<K,V> / ICollection<KVP> / IEnumerable<KVP> +
- /// IObservableMap<K,V>.MapChanged forwarders for a DIC file interface that inherits
- /// from Windows.Foundation.Collections.IObservableMap<K,V>. Mirrors C++
- /// write_dictionary_members_using_idic(true) + the IObservableMap event forwarder.
- ///
- private static void EmitDicShimIObservableMapForwarders(TypeWriter w, string keyText, string valueText)
- {
- string target = $"((global::System.Collections.Generic.IDictionary<{keyText}, {valueText}>)(WindowsRuntimeObject)this)";
- string self = $"global::System.Collections.Generic.IDictionary<{keyText}, {valueText}>.";
- string icoll = $"global::System.Collections.Generic.ICollection>.";
- w.Write("\n");
- w.Write($"ICollection<{keyText}> {self}Keys => {target}.Keys;\n");
- w.Write($"ICollection<{valueText}> {self}Values => {target}.Values;\n");
- w.Write($"int {icoll}Count => {target}.Count;\n");
- w.Write($"bool {icoll}IsReadOnly => {target}.IsReadOnly;\n");
- w.Write($"{valueText} {self}this[{keyText} key] \n");
- w.Write("{\n");
- w.Write($"get => {target}[key];\n");
- w.Write($"set => {target}[key] = value;\n");
- w.Write("}\n");
- w.Write($"void {self}Add({keyText} key, {valueText} value) => {target}.Add(key, value);\n");
- w.Write($"bool {self}ContainsKey({keyText} key) => {target}.ContainsKey(key);\n");
- w.Write($"bool {self}Remove({keyText} key) => {target}.Remove(key);\n");
- w.Write($"bool {self}TryGetValue({keyText} key, out {valueText} value) => {target}.TryGetValue(key, out value);\n");
- w.Write($"void {icoll}Add(KeyValuePair<{keyText}, {valueText}> item) => {target}.Add(item);\n");
- w.Write($"void {icoll}Clear() => {target}.Clear();\n");
- w.Write($"bool {icoll}Contains(KeyValuePair<{keyText}, {valueText}> item) => {target}.Contains(item);\n");
- w.Write($"void {icoll}CopyTo(KeyValuePair<{keyText}, {valueText}>[] array, int arrayIndex) => {target}.CopyTo(array, arrayIndex);\n");
- w.Write($"bool ICollection>.Remove(KeyValuePair<{keyText}, {valueText}> item) => {target}.Remove(item);\n");
- // Enumerable forwarders.
- w.Write("\n");
- w.Write($"IEnumerator> IEnumerable>.GetEnumerator() => {target}.GetEnumerator();\n");
- w.Write("IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();\n");
- // IObservableMap.MapChanged event forwarder.
- string obsTarget = $"((global::Windows.Foundation.Collections.IObservableMap<{keyText}, {valueText}>)(WindowsRuntimeObject)this)";
- string obsSelf = $"global::Windows.Foundation.Collections.IObservableMap<{keyText}, {valueText}>.";
- w.Write("\n");
- w.Write($"event global::Windows.Foundation.Collections.MapChangedEventHandler<{keyText}, {valueText}> {obsSelf}MapChanged\n");
- w.Write("{\n");
- w.Write($"add => {obsTarget}.MapChanged += value;\n");
- w.Write($"remove => {obsTarget}.MapChanged -= value;\n");
- w.Write("}\n");
- }
-
- ///
- /// Emits IList<T> / ICollection<T> / IEnumerable<T> +
- /// IObservableVector<T>.VectorChanged forwarders for a DIC file interface that inherits
- /// from Windows.Foundation.Collections.IObservableVector<T>. Mirrors C++
- /// write_list_members_using_idic(true) + the IObservableVector event forwarder.
- ///
- private static void EmitDicShimIObservableVectorForwarders(TypeWriter w, string elementText)
- {
- string target = $"((global::System.Collections.Generic.IList<{elementText}>)(WindowsRuntimeObject)this)";
- string self = $"global::System.Collections.Generic.IList<{elementText}>.";
- string icoll = $"global::System.Collections.Generic.ICollection<{elementText}>.";
- w.Write("\n");
- w.Write($"int {icoll}Count => {target}.Count;\n");
- w.Write($"bool {icoll}IsReadOnly => {target}.IsReadOnly;\n");
- w.Write($"{elementText} {self}this[int index]\n");
- w.Write("{\n");
- w.Write($"get => {target}[index];\n");
- w.Write($"set => {target}[index] = value;\n");
- w.Write("}\n");
- w.Write($"int {self}IndexOf({elementText} item) => {target}.IndexOf(item);\n");
- w.Write($"void {self}Insert(int index, {elementText} item) => {target}.Insert(index, item);\n");
- w.Write($"void {self}RemoveAt(int index) => {target}.RemoveAt(index);\n");
- w.Write($"void {icoll}Add({elementText} item) => {target}.Add(item);\n");
- w.Write($"void {icoll}Clear() => {target}.Clear();\n");
- w.Write($"bool {icoll}Contains({elementText} item) => {target}.Contains(item);\n");
- w.Write($"void {icoll}CopyTo({elementText}[] array, int arrayIndex) => {target}.CopyTo(array, arrayIndex);\n");
- w.Write($"bool {icoll}Remove({elementText} item) => {target}.Remove(item);\n");
- w.Write("\n");
- w.Write($"IEnumerator<{elementText}> IEnumerable<{elementText}>.GetEnumerator() => {target}.GetEnumerator();\n");
- w.Write("IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();\n");
- // IObservableVector.VectorChanged event forwarder.
- string obsTarget = $"((global::Windows.Foundation.Collections.IObservableVector<{elementText}>)(WindowsRuntimeObject)this)";
- string obsSelf = $"global::Windows.Foundation.Collections.IObservableVector<{elementText}>.";
- w.Write("\n");
- w.Write($"event global::Windows.Foundation.Collections.VectorChangedEventHandler<{elementText}> {obsSelf}VectorChanged\n");
- w.Write("{\n");
- w.Write($"add => {obsTarget}.VectorChanged += value;\n");
- w.Write($"remove => {obsTarget}.VectorChanged -= value;\n");
- w.Write("}\n");
- }
-
- ///
- /// Emits explicit-interface DIM thunks for an *inherited* (required) interface on a DIC
- /// file interface shim. Each member becomes a thin
- /// => ((IParent)(WindowsRuntimeObject)this).Member delegating thunk so that DIC
- /// re-dispatches through the parent's own DIC shim. Mirrors the C++ tool's emission for
- /// inherited-interface members in DIC shims.
- ///
- private static void WriteInterfaceIdicImplMembersForInheritedInterface(TypeWriter w, TypeDefinition type)
- {
- // The CCW interface name (the projected interface name with global:: prefix). For the
- // delegating thunks we cast through this same projected interface type.
- string ccwIfaceName = w.WriteTemp("%", new System.Action(_ => WriteTypedefName(w, type, TypedefNameType.Projected, true)));
- if (!ccwIfaceName.StartsWith("global::", System.StringComparison.Ordinal)) { ccwIfaceName = "global::" + ccwIfaceName; }
-
- foreach (MethodDefinition method in type.Methods)
- {
- if (Helpers.IsSpecial(method)) { continue; }
- MethodSig sig = new(method);
- string mname = method.Name?.Value ?? string.Empty;
-
- w.Write("\n");
- WriteProjectionReturnType(w, sig);
- w.Write(" ");
- w.Write(ccwIfaceName);
- w.Write(".");
- w.Write(mname);
- w.Write("(");
- WriteParameterList(w, sig);
- w.Write(") => ((");
- w.Write(ccwIfaceName);
- w.Write(")(WindowsRuntimeObject)this).");
- w.Write(mname);
- w.Write("(");
- for (int i = 0; i < sig.Params.Count; i++)
- {
- if (i > 0) { w.Write(", "); }
- WriteParameterNameWithModifier(w, sig.Params[i]);
- }
- w.Write(");\n");
- }
-
- foreach (PropertyDefinition prop in type.Properties)
- {
- (MethodDefinition? getter, MethodDefinition? setter) = Helpers.GetPropertyMethods(prop);
- string pname = prop.Name?.Value ?? string.Empty;
- string propType = WritePropType(w, prop);
-
- w.Write("\n");
- w.Write(propType);
- w.Write(" ");
- w.Write(ccwIfaceName);
- w.Write(".");
- w.Write(pname);
- if (getter is not null && setter is null)
- {
- // Read-only: single-line expression body.
- w.Write(" => ((");
- w.Write(ccwIfaceName);
- w.Write(")(WindowsRuntimeObject)this).");
- w.Write(pname);
- w.Write(";\n");
- }
- else
- {
- w.Write("\n{\n");
- if (getter is not null)
- {
- w.Write(" get => ((");
- w.Write(ccwIfaceName);
- w.Write(")(WindowsRuntimeObject)this).");
- w.Write(pname);
- w.Write(";\n");
- }
- if (setter is not null)
- {
- w.Write(" set => ((");
- w.Write(ccwIfaceName);
- w.Write(")(WindowsRuntimeObject)this).");
- w.Write(pname);
- w.Write(" = value;\n");
- }
- w.Write("}\n");
- }
- }
-
- foreach (EventDefinition evt in type.Events)
- {
- string evtName = evt.Name?.Value ?? string.Empty;
- w.Write("\nevent ");
- WriteEventType(w, evt);
- w.Write(" ");
- w.Write(ccwIfaceName);
- w.Write(".");
- w.Write(evtName);
- w.Write("\n{\n");
- w.Write(" add => ((");
- w.Write(ccwIfaceName);
- w.Write(")(WindowsRuntimeObject)this).");
- w.Write(evtName);
- w.Write(" += value;\n");
- w.Write(" remove => ((");
- w.Write(ccwIfaceName);
- w.Write(")(WindowsRuntimeObject)this).");
- w.Write(evtName);
- w.Write(" -= value;\n");
- w.Write("}\n");
- }
- }
-
- ///
- /// Emits explicit-interface DIM forwarders on a DIC file interface shim for the BCL
- /// members that come from a system-collection-mapped required WinRT interface
- /// (e.g. IBindableVector maps to IList, so we must satisfy IList,
- /// ICollection, and IEnumerable members on the shim). The forwarders all
- /// re-cast through (WindowsRuntimeObject)this so the DIC machinery can re-dispatch
- /// to the real BCL adapter shim.
- ///
- private static void EmitDicShimMappedBclForwarders(TypeWriter w, string mappedWinRTInterfaceName)
- {
- switch (mappedWinRTInterfaceName)
- {
- case "IClosable":
- // IClosable maps to IDisposable. Forward Dispose() to the
- // WindowsRuntimeObject base which has the actual implementation.
- w.Write("\nvoid global::System.IDisposable.Dispose() => ((global::System.IDisposable)(WindowsRuntimeObject)this).Dispose();\n");
- break;
- case "IBindableVector":
- // IList covers IList, ICollection, and IEnumerable members.
- w.Write("\n");
- w.Write("int global::System.Collections.ICollection.Count => ((global::System.Collections.IList)(WindowsRuntimeObject)this).Count;\n");
- w.Write("bool global::System.Collections.ICollection.IsSynchronized => ((global::System.Collections.IList)(WindowsRuntimeObject)this).IsSynchronized;\n");
- w.Write("object global::System.Collections.ICollection.SyncRoot => ((global::System.Collections.IList)(WindowsRuntimeObject)this).SyncRoot;\n");
- w.Write("void global::System.Collections.ICollection.CopyTo(Array array, int index) => ((global::System.Collections.IList)(WindowsRuntimeObject)this).CopyTo(array, index);\n\n");
- w.Write("object global::System.Collections.IList.this[int index]\n{\n");
- w.Write("get => ((global::System.Collections.IList)(WindowsRuntimeObject)this)[index];\n");
- w.Write("set => ((global::System.Collections.IList)(WindowsRuntimeObject)this)[index] = value;\n}\n");
- w.Write("bool global::System.Collections.IList.IsFixedSize => ((global::System.Collections.IList)(WindowsRuntimeObject)this).IsFixedSize;\n");
- w.Write("bool global::System.Collections.IList.IsReadOnly => ((global::System.Collections.IList)(WindowsRuntimeObject)this).IsReadOnly;\n");
- w.Write("int global::System.Collections.IList.Add(object value) => ((global::System.Collections.IList)(WindowsRuntimeObject)this).Add(value);\n");
- w.Write("void global::System.Collections.IList.Clear() => ((global::System.Collections.IList)(WindowsRuntimeObject)this).Clear();\n");
- w.Write("bool global::System.Collections.IList.Contains(object value) => ((global::System.Collections.IList)(WindowsRuntimeObject)this).Contains(value);\n");
- w.Write("int global::System.Collections.IList.IndexOf(object value) => ((global::System.Collections.IList)(WindowsRuntimeObject)this).IndexOf(value);\n");
- w.Write("void global::System.Collections.IList.Insert(int index, object value) => ((global::System.Collections.IList)(WindowsRuntimeObject)this).Insert(index, value);\n");
- w.Write("void global::System.Collections.IList.Remove(object value) => ((global::System.Collections.IList)(WindowsRuntimeObject)this).Remove(value);\n");
- w.Write("void global::System.Collections.IList.RemoveAt(int index) => ((global::System.Collections.IList)(WindowsRuntimeObject)this).RemoveAt(index);\n\n");
- w.Write("IEnumerator IEnumerable.GetEnumerator() => ((global::System.Collections.IList)(WindowsRuntimeObject)this).GetEnumerator();\n");
- break;
- case "IBindableIterable":
- w.Write("\n");
- w.Write("IEnumerator IEnumerable.GetEnumerator() => ((global::System.Collections.IEnumerable)(WindowsRuntimeObject)this).GetEnumerator();\n");
- break;
- }
- }
-
- private static void WriteInterfaceIdicImplMembersForInterface(TypeWriter w, TypeDefinition type, HashSet visited)
- {
- // The CCW interface name (the projected interface name with global:: prefix).
- string ccwIfaceName = w.WriteTemp("%", new System.Action(_ => WriteTypedefName(w, type, TypedefNameType.Projected, true)));
- if (!ccwIfaceName.StartsWith("global::", System.StringComparison.Ordinal)) { ccwIfaceName = "global::" + ccwIfaceName; }
- // The static ABI Methods class name.
- string abiClass = w.WriteTemp("%", new System.Action(_ => WriteTypedefName(w, type, TypedefNameType.StaticAbiClass, true)));
- if (!abiClass.StartsWith("global::", System.StringComparison.Ordinal)) { abiClass = "global::" + abiClass; }
-
- foreach (MethodDefinition method in type.Methods)
- {
- if (Helpers.IsSpecial(method)) { continue; }
- MethodSig sig = new(method);
- string mname = method.Name?.Value ?? string.Empty;
-
- w.Write("\nunsafe ");
- WriteProjectionReturnType(w, sig);
- w.Write(" ");
- w.Write(ccwIfaceName);
- w.Write(".");
- w.Write(mname);
- w.Write("(");
- WriteParameterList(w, sig);
- w.Write(")\n{\n");
- w.Write(" var _obj = ((WindowsRuntimeObject)this).GetObjectReferenceForInterface(typeof(");
- w.Write(ccwIfaceName);
- w.Write(").TypeHandle);\n ");
- if (sig.ReturnType is not null) { w.Write("return "); }
- w.Write(abiClass);
- w.Write(".");
- w.Write(mname);
- w.Write("(_obj");
- for (int i = 0; i < sig.Params.Count; i++)
- {
- w.Write(", ");
- WriteParameterNameWithModifier(w, sig.Params[i]);
- }
- w.Write(");\n}\n");
- }
-
- foreach (PropertyDefinition prop in type.Properties)
- {
- (MethodDefinition? getter, MethodDefinition? setter) = Helpers.GetPropertyMethods(prop);
- string pname = prop.Name?.Value ?? string.Empty;
- string propType = WritePropType(w, prop);
-
- w.Write("\nunsafe ");
- w.Write(propType);
- w.Write(" ");
- w.Write(ccwIfaceName);
- w.Write(".");
- w.Write(pname);
- w.Write("\n{\n");
- if (getter is not null)
- {
- w.Write(" get\n {\n");
- w.Write(" var _obj = ((WindowsRuntimeObject)this).GetObjectReferenceForInterface(typeof(");
- w.Write(ccwIfaceName);
- w.Write(").TypeHandle);\n");
- w.Write(" return ");
- w.Write(abiClass);
- w.Write(".");
- w.Write(pname);
- w.Write("(_obj);\n }\n");
- }
- if (setter is not null)
- {
- // If the property has only a setter on this interface BUT a base interface declares
- // the getter (so the C# interface decl emits 'get; set;'), C# requires an explicit
- // interface impl to provide both accessors. Emit a synthetic getter that delegates
- // to the base interface where the getter actually lives. Mirrors C++
- // code_writers.h:7052-7062.
- if (getter is null)
- {
- TypeDefinition? baseIfaceWithGetter = FindPropertyInterfaceInBases(type, pname);
- if (baseIfaceWithGetter is not null)
- {
- w.Write(" get { return ((");
- WriteInterfaceTypeNameForCcw(w, baseIfaceWithGetter);
- w.Write(")(WindowsRuntimeObject)this).");
- w.Write(pname);
- w.Write("; }\n");
- }
- }
- w.Write(" set\n {\n");
- w.Write(" var _obj = ((WindowsRuntimeObject)this).GetObjectReferenceForInterface(typeof(");
- w.Write(ccwIfaceName);
- w.Write(").TypeHandle);\n");
- w.Write(" ");
- w.Write(abiClass);
- w.Write(".");
- w.Write(pname);
- w.Write("(_obj, value);\n }\n");
- }
- w.Write("}\n");
- }
-
- // Events: emit explicit interface event implementations on the IDIC interface that
- // dispatch through the static ABI Methods class's event accessor (returns an EventSource).
- // Mirrors C++ write_interface_members event handling (calls EventName(thisRef, _obj).Subscribe/Unsubscribe).
- foreach (EventDefinition evt in type.Events)
- {
- string evtName = evt.Name?.Value ?? string.Empty;
- w.Write("\nevent ");
- WriteEventType(w, evt);
- w.Write(" ");
- w.Write(ccwIfaceName);
- w.Write(".");
- w.Write(evtName);
- w.Write("\n{\n");
- // add accessor
- w.Write(" add\n {\n");
- w.Write(" var _obj = ((WindowsRuntimeObject)this).GetObjectReferenceForInterface(typeof(");
- w.Write(ccwIfaceName);
- w.Write(").TypeHandle);\n ");
- w.Write(abiClass);
- w.Write(".");
- w.Write(evtName);
- w.Write("((WindowsRuntimeObject)this, _obj).Subscribe(value);\n }\n");
- // remove accessor
- w.Write(" remove\n {\n");
- w.Write(" var _obj = ((WindowsRuntimeObject)this).GetObjectReferenceForInterface(typeof(");
- w.Write(ccwIfaceName);
- w.Write(").TypeHandle);\n ");
- w.Write(abiClass);
- w.Write(".");
- w.Write(evtName);
- w.Write("((WindowsRuntimeObject)this, _obj).Unsubscribe(value);\n }\n");
- w.Write("}\n");
- }
- }
-
- /// Mirrors C++ write_interface_marshaller.
- public static void WriteInterfaceMarshaller(TypeWriter w, TypeDefinition type)
- {
- if (TypeCategorization.IsExclusiveTo(type)) { return; }
- if (type.GenericParameters.Count > 0) { return; }
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
-
- w.Write("\n#nullable enable\n");
- w.Write("public static unsafe class ");
- w.Write(nameStripped);
- w.Write("Marshaller\n{\n");
- w.Write(" public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged(");
- WriteTypedefName(w, type, TypedefNameType.Projected, false);
- WriteTypeParams(w, type);
- w.Write(" value)\n {\n");
- w.Write(" return WindowsRuntimeInterfaceMarshaller<");
- WriteTypedefName(w, type, TypedefNameType.Projected, false);
- WriteTypeParams(w, type);
- w.Write(">.ConvertToUnmanaged(value, ");
- WriteIidGuidReference(w, type);
- w.Write(");\n }\n\n");
- w.Write(" public static ");
- WriteTypedefName(w, type, TypedefNameType.Projected, false);
- WriteTypeParams(w, type);
- w.Write("? ConvertToManaged(void* value)\n {\n");
- w.Write(" return (");
- WriteTypedefName(w, type, TypedefNameType.Projected, false);
- WriteTypeParams(w, type);
- w.Write("?) WindowsRuntimeObjectMarshaller.ConvertToManaged(value);\n }\n}\n");
- w.Write("#nullable disable\n");
- }
-
- /// Mirrors C++ write_iid_guid for use by ABI helpers.
- public static void WriteIidGuidReference(TypeWriter w, TypeDefinition type)
- {
- if (type.GenericParameters.Count != 0)
- {
- // Generic interface IID - call the unsafe accessor
- WriteIidGuidPropertyName(w, type);
- w.Write("(null)");
- return;
- }
- string ns = type.Namespace?.Value ?? string.Empty;
- string nm = type.Name?.Value ?? string.Empty;
- if (MappedTypes.Get(ns, nm) is { } m && m.MappedName == "IStringable")
- {
- w.Write("global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IStringable");
- return;
- }
- w.Write("global::ABI.InterfaceIIDs.");
- WriteIidGuidPropertyName(w, type);
- }
-
- ///
- /// Writes a marshaller class for a struct or enum (mirrors C++ write_struct_and_enum_marshaller_class).
- ///
- private static void WriteStructEnumMarshallerClass(TypeWriter w, TypeDefinition type)
- {
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
- TypeCategory cat = TypeCategorization.GetCategory(type);
- bool blittable = IsTypeBlittable(type);
- // "Almost-blittable" includes blittable + bool/char fields. Excludes string/object fields.
- // Use the same predicate as IsAnyStruct (which is now scoped to almost-blittable).
- AsmResolver.DotNet.Signatures.TypeDefOrRefSignature sig = type.ToTypeSignature(false) is AsmResolver.DotNet.Signatures.TypeDefOrRefSignature td2 ? td2 : null!;
- bool almostBlittable = cat == TypeCategory.Struct && (sig is null || IsAnyStruct(sig));
- bool isEnum = cat == TypeCategory.Enum;
- // Complex structs are non-almost-blittable structs with reference fields (string, object, etc.).
- bool isComplexStruct = cat == TypeCategory.Struct && !almostBlittable;
- // Detect Nullable reference fields to determine whether the struct's BoxToUnmanaged
- // call needs CreateComInterfaceFlags.TrackerSupport (mirrors C++ use_tracker_object_support
- // which returns true for IReference`1 generic instances).
- bool hasReferenceFields = false;
- if (isComplexStruct)
- {
- foreach (FieldDefinition field in type.Fields)
- {
- if (field.IsStatic || field.Signature is null) { continue; }
- AsmResolver.DotNet.Signatures.TypeSignature ft = field.Signature.FieldType;
- if (TryGetNullablePrimitiveMarshallerName(ft, out _)) { hasReferenceFields = true; }
- }
- }
-
- // For structs that are mapped (e.g. Duration, KeyTime, RepeatBehavior — they have
- // EmitAbi=true and an addition file that completely replaces the public struct), skip
- // the per-field ConvertToUnmanaged/ConvertToManaged because the projected struct's
- // public fields don't match the WinMD field layout. The truth marshaller for these
- // contains only BoxToUnmanaged/UnboxToManaged.
- string typeNs = type.Namespace?.Value ?? string.Empty;
- string typeNm = type.Name?.Value ?? string.Empty;
- bool isMappedStruct = isComplexStruct && MappedTypes.Get(typeNs, typeNm) is not null;
- if (isMappedStruct) { isComplexStruct = false; }
-
- w.Write("public static unsafe class ");
- w.Write(nameStripped);
- w.Write("Marshaller\n{\n");
-
- if (isComplexStruct)
- {
- // ConvertToUnmanaged: build ABI struct from projected struct via per-field marshalling.
- w.Write(" public static ");
- WriteTypedefName(w, type, TypedefNameType.ABI, false);
- w.Write(" ConvertToUnmanaged(");
- WriteTypedefName(w, type, TypedefNameType.Projected, true);
- w.Write(" value)\n {\n");
- w.Write(" return new() {\n");
- bool first = true;
- foreach (FieldDefinition field in type.Fields)
- {
- if (field.IsStatic || field.Signature is null) { continue; }
- string fname = field.Name?.Value ?? "";
- AsmResolver.DotNet.Signatures.TypeSignature ft = field.Signature.FieldType;
- if (!first) { w.Write(",\n"); }
- first = false;
- w.Write(" ");
- w.Write(fname);
- w.Write(" = ");
- if (IsString(ft))
- {
- w.Write("HStringMarshaller.ConvertToUnmanaged(value.");
- w.Write(fname);
- w.Write(")");
- }
- else if (IsMappedAbiValueType(ft))
- {
- w.Write(GetMappedMarshallerName(ft));
- w.Write(".ConvertToUnmanaged(value.");
- w.Write(fname);
- w.Write(")");
- }
- else if (IsHResultException(ft))
- {
- // Mapped value type 'HResult' (excluded from IsMappedAbiValueType because
- // it's "treated specially in many places", but for nested struct fields the
- // marshalling is identical: use ABI.System.ExceptionMarshaller).
- w.Write("global::ABI.System.ExceptionMarshaller.ConvertToUnmanaged(value.");
- w.Write(fname);
- w.Write(")");
- }
- else if (ft is AsmResolver.DotNet.Signatures.TypeDefOrRefSignature ftd
- && TryResolveStructTypeDef(ftd) is TypeDefinition fieldStructTd
- && TypeCategorization.GetCategory(fieldStructTd) == TypeCategory.Struct
- && !IsTypeBlittable(fieldStructTd))
- {
- // Nested non-blittable struct: marshal via its Marshaller.
- w.Write(Helpers.StripBackticks(fieldStructTd.Name?.Value ?? string.Empty));
- w.Write("Marshaller.ConvertToUnmanaged(value.");
- w.Write(fname);
- w.Write(")");
- }
- else if (TryGetNullablePrimitiveMarshallerName(ft, out string? nullableMarshaller))
- {
- w.Write(nullableMarshaller!);
- w.Write(".BoxToUnmanaged(value.");
- w.Write(fname);
- w.Write(").DetachThisPtrUnsafe()");
- }
- else
- {
- w.Write("value.");
- w.Write(fname);
- }
- }
- w.Write("\n };\n }\n");
-
- // ConvertToManaged: construct projected struct via constructor accepting the marshalled fields.
- w.Write(" public static ");
- WriteTypedefName(w, type, TypedefNameType.Projected, true);
- w.Write(" ConvertToManaged(");
- WriteTypedefName(w, type, TypedefNameType.ABI, false);
- // Mirror C++ write_convert_to_managed_method_struct (code_writers.h:4536-4540):
- // - In component mode: emit object initializer with named field assignments
- // (positional ctor not always available on authored types).
- // - In non-component mode: emit positional constructor (matches the auto-generated
- // primary constructor on projected struct types).
- bool useObjectInitializer = w.Settings.Component;
- w.Write(" value)\n {\n");
- w.Write(" return new ");
- WriteTypedefName(w, type, TypedefNameType.Projected, true);
- w.Write(useObjectInitializer ? "(){\n" : "(\n");
- first = true;
- foreach (FieldDefinition field in type.Fields)
- {
- if (field.IsStatic || field.Signature is null) { continue; }
- string fname = field.Name?.Value ?? "";
- AsmResolver.DotNet.Signatures.TypeSignature ft = field.Signature.FieldType;
- if (!first) { w.Write(",\n"); }
- first = false;
- w.Write(" ");
- if (useObjectInitializer)
- {
- w.Write(fname);
- w.Write(" = ");
- }
- if (IsString(ft))
- {
- w.Write("HStringMarshaller.ConvertToManaged(value.");
- w.Write(fname);
- w.Write(")");
- }
- else if (IsMappedAbiValueType(ft))
- {
- w.Write(GetMappedMarshallerName(ft));
- w.Write(".ConvertToManaged(value.");
- w.Write(fname);
- w.Write(")");
- }
- else if (IsHResultException(ft))
- {
- // Mapped value type 'HResult' (excluded from IsMappedAbiValueType because
- // it's "treated specially in many places", but for nested struct fields the
- // marshalling is identical: use ABI.System.ExceptionMarshaller).
- w.Write("global::ABI.System.ExceptionMarshaller.ConvertToManaged(value.");
- w.Write(fname);
- w.Write(")");
- }
- else if (ft is AsmResolver.DotNet.Signatures.TypeDefOrRefSignature ftd2
- && TryResolveStructTypeDef(ftd2) is TypeDefinition fieldStructTd2
- && TypeCategorization.GetCategory(fieldStructTd2) == TypeCategory.Struct
- && !IsTypeBlittable(fieldStructTd2))
- {
- // Nested non-blittable struct: convert via its Marshaller.
- w.Write(Helpers.StripBackticks(fieldStructTd2.Name?.Value ?? string.Empty));
- w.Write("Marshaller.ConvertToManaged(value.");
- w.Write(fname);
- w.Write(")");
- }
- else if (TryGetNullablePrimitiveMarshallerName(ft, out string? nullableMarshaller))
- {
- w.Write(nullableMarshaller!);
- w.Write(".UnboxToManaged(value.");
- w.Write(fname);
- w.Write(")");
- }
- else
- {
- w.Write("value.");
- w.Write(fname);
- }
- }
- w.Write(useObjectInitializer ? "\n };\n }\n" : "\n );\n }\n");
-
- // Dispose: free non-blittable fields.
- w.Write(" public static void Dispose(");
- WriteTypedefName(w, type, TypedefNameType.ABI, false);
- w.Write(" value)\n {\n");
- foreach (FieldDefinition field in type.Fields)
- {
- if (field.IsStatic || field.Signature is null) { continue; }
- string fname = field.Name?.Value ?? "";
- AsmResolver.DotNet.Signatures.TypeSignature ft = field.Signature.FieldType;
- if (IsString(ft))
- {
- w.Write(" HStringMarshaller.Free(value.");
- w.Write(fname);
- w.Write(");\n");
- }
- else if (IsHResultException(ft))
- {
- // HResult/Exception field has no per-value resources to release
- // (the ABI representation is just an int HRESULT). Skip Dispose entirely.
- continue;
- }
- else if (IsMappedAbiValueType(ft))
- {
- // Mapped value types (DateTime/TimeSpan) have no per-value resources to
- // release — the ABI representation is just an int64. Mirror C++
- // set_skip_disposer_if_needed (code_writers.h:6431-6440) which explicitly
- // skips the disposer for global::ABI.System.{DateTimeOffset,TimeSpan,Exception}.
- continue;
- }
- else if (ft is AsmResolver.DotNet.Signatures.TypeDefOrRefSignature ftd3
- && TryResolveStructTypeDef(ftd3) is TypeDefinition fieldStructTd3
- && TypeCategorization.GetCategory(fieldStructTd3) == TypeCategory.Struct
- && !IsTypeBlittable(fieldStructTd3))
- {
- // Nested non-blittable struct: dispose via its Marshaller.
- // Mirror C++: this site always uses the fully-qualified marshaller name.
- string nestedNs = fieldStructTd3.Namespace?.Value ?? string.Empty;
- string nestedNm = Helpers.StripBackticks(fieldStructTd3.Name?.Value ?? string.Empty);
- w.Write(" global::ABI.");
- w.Write(nestedNs);
- w.Write(".");
- w.Write(nestedNm);
- w.Write("Marshaller.Dispose(value.");
- w.Write(fname);
- w.Write(");\n");
- }
- else if (TryGetNullablePrimitiveMarshallerName(ft, out _))
- {
- w.Write(" WindowsRuntimeUnknownMarshaller.Free(value.");
- w.Write(fname);
- w.Write(");\n");
- }
- }
- w.Write(" }\n");
- }
-
- // BoxToUnmanaged: same pattern for all (enum, almost-blittable, complex).
- // Truth uses CreateComInterfaceFlags.TrackerSupport when the struct has reference type
- // fields (Nullable, etc.) to avoid GC issues with the boxed managed object reference.
- w.Write(" public static WindowsRuntimeObjectReferenceValue BoxToUnmanaged(");
- WriteTypedefName(w, type, TypedefNameType.Projected, true);
- if (isEnum || almostBlittable || isComplexStruct)
- {
- w.Write("? value)\n {\n");
- w.Write(" return WindowsRuntimeValueTypeMarshaller.BoxToUnmanaged(value, CreateComInterfaceFlags.");
- w.Write(hasReferenceFields ? "TrackerSupport" : "None");
- w.Write(", in ");
- WriteIidReferenceExpression(w, type);
- w.Write(");\n }\n");
- }
- else
- {
- // Mapped struct (Duration/KeyTime/etc.): BoxToUnmanaged is still required because the
- // public projected type still routes through this marshaller (it just lacks per-field
- // ConvertToUnmanaged/ConvertToManaged because the field layout doesn't match).
- w.Write("? value)\n {\n");
- w.Write(" return WindowsRuntimeValueTypeMarshaller.BoxToUnmanaged(value, CreateComInterfaceFlags.None, in ");
- WriteIidReferenceExpression(w, type);
- w.Write(");\n }\n");
- }
-
- // UnboxToManaged: simple for almost-blittable; for complex, unbox to ABI struct then ConvertToManaged.
- w.Write(" public static ");
- WriteTypedefName(w, type, TypedefNameType.Projected, true);
- if (isEnum || almostBlittable)
- {
- w.Write("? UnboxToManaged(void* value)\n {\n");
- w.Write(" return WindowsRuntimeValueTypeMarshaller.UnboxToManaged<");
- WriteTypedefName(w, type, TypedefNameType.Projected, true);
- w.Write(">(value);\n }\n");
- }
- else if (isComplexStruct)
- {
- w.Write("? UnboxToManaged(void* value)\n {\n");
- w.Write(" ");
- WriteTypedefName(w, type, TypedefNameType.ABI, false);
- w.Write("? abi = WindowsRuntimeValueTypeMarshaller.UnboxToManaged<");
- WriteTypedefName(w, type, TypedefNameType.ABI, false);
- w.Write(">(value);\n");
- w.Write(" return abi.HasValue ? ConvertToManaged(abi.GetValueOrDefault()) : null;\n }\n");
- }
- else
- {
- // Mapped struct: unbox directly to projected type (no per-field ConvertToManaged needed
- // because the projected struct's field layout matches the WinMD struct layout).
- w.Write("? UnboxToManaged(void* value)\n {\n");
- w.Write(" return WindowsRuntimeValueTypeMarshaller.UnboxToManaged<");
- WriteTypedefName(w, type, TypedefNameType.Projected, true);
- w.Write(">(value);\n }\n");
- }
-
- w.Write("}\n\n");
-
- // Emit the InterfaceEntriesImpl static class and the proper ComWrappersMarshallerAttribute
- // class derived from WindowsRuntimeComWrappersMarshallerAttribute (matches truth).
- // For enums and almost-blittable structs, GetOrCreateComInterfaceForObject uses None.
- // For complex structs (with reference fields), it uses TrackerSupport.
- // For complex structs, CreateObject converts via the *Marshaller.ConvertToManaged after
- // unboxing to the ABI struct.
- if (isEnum || almostBlittable || isComplexStruct)
- {
- string iidRefExpr = w.WriteTemp("%", new System.Action(_ => WriteIidReferenceExpression(w, type)));
-
- // InterfaceEntriesImpl
- w.Write("file static class ");
- w.Write(nameStripped);
- w.Write("InterfaceEntriesImpl\n{\n");
- w.Write(" [FixedAddressValueType]\n");
- w.Write(" public static readonly ReferenceInterfaceEntries Entries;\n\n");
- w.Write(" static ");
- w.Write(nameStripped);
- w.Write("InterfaceEntriesImpl()\n {\n");
- w.Write(" Entries.IReferenceValue.IID = ");
- w.Write(iidRefExpr);
- w.Write(";\n");
- w.Write(" Entries.IReferenceValue.Vtable = ");
- w.Write(nameStripped);
- w.Write("ReferenceImpl.Vtable;\n");
- w.Write(" Entries.IPropertyValue.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IPropertyValue;\n");
- w.Write(" Entries.IPropertyValue.Vtable = global::WindowsRuntime.InteropServices.IPropertyValueImpl.OtherTypeVtable;\n");
- w.Write(" Entries.IStringable.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IStringable;\n");
- w.Write(" Entries.IStringable.Vtable = global::WindowsRuntime.InteropServices.IStringableImpl.Vtable;\n");
- w.Write(" Entries.IWeakReferenceSource.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IWeakReferenceSource;\n");
- w.Write(" Entries.IWeakReferenceSource.Vtable = global::WindowsRuntime.InteropServices.IWeakReferenceSourceImpl.Vtable;\n");
- w.Write(" Entries.IMarshal.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IMarshal;\n");
- w.Write(" Entries.IMarshal.Vtable = global::WindowsRuntime.InteropServices.IMarshalImpl.Vtable;\n");
- w.Write(" Entries.IAgileObject.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IAgileObject;\n");
- w.Write(" Entries.IAgileObject.Vtable = global::WindowsRuntime.InteropServices.IAgileObjectImpl.Vtable;\n");
- w.Write(" Entries.IInspectable.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IInspectable;\n");
- w.Write(" Entries.IInspectable.Vtable = global::WindowsRuntime.InteropServices.IInspectableImpl.Vtable;\n");
- w.Write(" Entries.IUnknown.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IUnknown;\n");
- w.Write(" Entries.IUnknown.Vtable = global::WindowsRuntime.InteropServices.IUnknownImpl.Vtable;\n");
- w.Write(" }\n}\n\n");
-
- // Mirror C++ write_abi_struct: in component mode the ComWrappers marshaller attribute
- // is NOT emitted for STRUCTS (the attribute is supplied by cswinrtgen instead). Enums
- // and other types still emit it from write_abi_enum/etc.
- if (w.Settings.Component && cat == TypeCategory.Struct) { return; }
-
- // ComWrappersMarshallerAttribute (full body)
- w.Write("internal sealed unsafe class ");
- w.Write(nameStripped);
- w.Write("ComWrappersMarshallerAttribute : WindowsRuntimeComWrappersMarshallerAttribute\n{\n");
- w.Write(" public override void* GetOrCreateComInterfaceForObject(object value)\n {\n");
- w.Write(" return WindowsRuntimeComWrappersMarshal.GetOrCreateComInterfaceForObject(value, CreateComInterfaceFlags.");
- w.Write(hasReferenceFields ? "TrackerSupport" : "None");
- w.Write(");\n }\n\n");
- w.Write(" public override ComInterfaceEntry* ComputeVtables(out int count)\n {\n");
- w.Write(" count = sizeof(ReferenceInterfaceEntries) / sizeof(ComInterfaceEntry);\n");
- w.Write(" return (ComInterfaceEntry*)Unsafe.AsPointer(in ");
- w.Write(nameStripped);
- w.Write("InterfaceEntriesImpl.Entries);\n }\n\n");
- w.Write(" public override object CreateObject(void* value, out CreatedWrapperFlags wrapperFlags)\n {\n");
- w.Write(" wrapperFlags = CreatedWrapperFlags.NonWrapping;\n");
- if (isComplexStruct)
- {
- w.Write(" return ");
- w.Write(nameStripped);
- w.Write("Marshaller.ConvertToManaged(WindowsRuntimeValueTypeMarshaller.UnboxToManagedUnsafe<");
- WriteTypedefName(w, type, TypedefNameType.ABI, true);
- w.Write(">(value, in ");
- w.Write(iidRefExpr);
- w.Write("));\n");
- }
- else
- {
- w.Write(" return WindowsRuntimeValueTypeMarshaller.UnboxToManagedUnsafe<");
- WriteTypedefName(w, type, TypedefNameType.Projected, true);
- w.Write(">(value, in ");
- w.Write(iidRefExpr);
- w.Write(");\n");
- }
- w.Write(" }\n}\n");
- }
- else
- {
- // Fallback: keep the placeholder class so consumer attribute references resolve.
- w.Write("internal sealed class ");
- w.Write(nameStripped);
- w.Write("ComWrappersMarshallerAttribute : global::System.Attribute\n{\n}\n");
- }
- }
-
- ///
- /// Writes a marshaller stub for a delegate.
- ///
- ///
- /// Emits just the <Name>Marshaller class for a delegate. Mirrors C++
- /// write_delegate_marshaller.
- ///
- private static void WriteDelegateMarshallerOnly(TypeWriter w, TypeDefinition type)
- {
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
- string typeNs = type.Namespace?.Value ?? string.Empty;
- string fullProjected = $"global::{typeNs}.{nameStripped}";
- string iidExpr = w.WriteTemp("%", new System.Action(_ => WriteIidExpression(w, type)));
-
- w.Write("\npublic static unsafe class ");
- w.Write(nameStripped);
- w.Write("Marshaller\n{\n");
- w.Write(" public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged(");
- w.Write(fullProjected);
- w.Write(" value)\n {\n");
- w.Write(" return WindowsRuntimeDelegateMarshaller.ConvertToUnmanaged(value, in ");
- w.Write(iidExpr);
- w.Write(");\n }\n\n");
- w.Write("#nullable enable\n");
- w.Write(" public static ");
- w.Write(fullProjected);
- w.Write("? ConvertToManaged(void* value)\n {\n");
- w.Write(" return (");
- w.Write(fullProjected);
- w.Write("?)WindowsRuntimeDelegateMarshaller.ConvertToManaged<");
- w.Write(nameStripped);
- w.Write("ComWrappersCallback>(value);\n }\n");
- w.Write("#nullable disable\n");
- w.Write("}\n");
- }
-
- ///
- /// Emits the <Name>ComWrappersCallback file-scoped class for a delegate.
- /// Mirrors C++ write_delegate_comwrappers_callback. Generic delegates are not emitted
- /// here at all — the higher-level dispatch in ProjectionGenerator filters out generic
- /// types from ABI emission (mirrors C++ main.cpp:412:
- /// if (distance(type.GenericParam()) != 0) { continue; }). Open generic delegates
- /// can't compile this body anyway because the projected type would have unbound generic
- /// parameters.
- ///
- private static void WriteDelegateComWrappersCallback(TypeWriter w, TypeDefinition type)
- {
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
- string typeNs = type.Namespace?.Value ?? string.Empty;
- string fullProjected = $"global::{typeNs}.{nameStripped}";
- string iidExpr = w.WriteTemp("%", new System.Action(_ => WriteIidExpression(w, type)));
-
- MethodDefinition? invoke = Helpers.GetDelegateInvoke(type);
- bool nativeSupported = invoke is not null && IsDelegateInvokeNativeSupported(new MethodSig(invoke));
-
- w.Write("\nfile abstract unsafe class ");
- w.Write(nameStripped);
- w.Write("ComWrappersCallback : IWindowsRuntimeObjectComWrappersCallback\n{\n");
- w.Write(" /// \n");
- w.Write(" public static object CreateObject(void* value, out CreatedWrapperFlags wrapperFlags)\n {\n");
- w.Write(" WindowsRuntimeObjectReference valueReference = WindowsRuntimeComWrappersMarshal.CreateObjectReferenceUnsafe(\n");
- w.Write(" externalComObject: value,\n");
- w.Write(" iid: in ");
- w.Write(iidExpr);
- w.Write(",\n wrapperFlags: out wrapperFlags);\n\n");
- // Always emit the body. The 'valueReference.Invoke' extension method always
- // exists (in NativeDelegate); even when its body is itself a stub, this path compiles
- // and matches the truth, which never emits 'throw null!' for CreateObject.
- w.Write(" return new ");
- w.Write(fullProjected);
- w.Write("(valueReference.");
- w.Write(nameStripped);
- w.Write("Invoke);\n");
- _ = nativeSupported;
- w.Write(" }\n}\n");
- }
-
- ///
- /// Emits the <Name>ComWrappersMarshallerAttribute class. Mirrors C++
- /// write_delegate_com_wrappers_marshaller_attribute_impl. Generic delegates are not
- /// emitted here at all (filtered out in ProjectionGenerator).
- ///
- private static void WriteDelegateComWrappersMarshallerAttribute(TypeWriter w, TypeDefinition type)
- {
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
- string iidRefExpr = w.WriteTemp("%", new System.Action(_ => WriteIidReferenceExpression(w, type)));
-
- w.Write("\ninternal sealed unsafe class ");
- w.Write(nameStripped);
- w.Write("ComWrappersMarshallerAttribute : WindowsRuntimeComWrappersMarshallerAttribute\n{\n");
- w.Write(" /// \n");
- w.Write(" public override void* GetOrCreateComInterfaceForObject(object value)\n {\n");
- w.Write(" return WindowsRuntimeComWrappersMarshal.GetOrCreateComInterfaceForObject(value, CreateComInterfaceFlags.TrackerSupport);\n");
- w.Write(" }\n\n");
- w.Write(" /// \n");
- w.Write(" public override ComInterfaceEntry* ComputeVtables(out int count)\n {\n");
- w.Write(" count = sizeof(DelegateReferenceInterfaceEntries) / sizeof(ComInterfaceEntry);\n\n");
- w.Write(" return (ComInterfaceEntry*)Unsafe.AsPointer(in ");
- w.Write(nameStripped);
- w.Write("InterfaceEntriesImpl.Entries);\n }\n\n");
- w.Write(" /// \n");
- w.Write(" public override object CreateObject(void* value, out CreatedWrapperFlags wrapperFlags)\n {\n");
- w.Write(" wrapperFlags = CreatedWrapperFlags.NonWrapping;\n");
- w.Write(" return WindowsRuntimeDelegateMarshaller.UnboxToManaged<");
- w.Write(nameStripped);
- w.Write("ComWrappersCallback>(value, in ");
- w.Write(iidRefExpr);
- w.Write(")!;\n }\n");
- w.Write("}\n");
- }
-
- /// True if EmitNativeDelegateBody can emit a real (non-throw) body for this signature.
- private static bool IsDelegateInvokeNativeSupported(MethodSig sig)
- {
- AsmResolver.DotNet.Signatures.TypeSignature? rt = sig.ReturnType;
- if (rt is not null)
- {
- if (IsHResultException(rt)) { return false; }
- if (!(IsBlittablePrimitive(rt) || IsAnyStruct(rt) || IsString(rt) || IsRuntimeClassOrInterface(rt) || IsObject(rt) || IsGenericInstance(rt) || IsComplexStruct(rt))) { return false; }
- }
- foreach (ParamInfo p in sig.Params)
- {
- ParamCategory cat = ParamHelpers.GetParamCategory(p);
- if (cat == ParamCategory.PassArray || cat == ParamCategory.FillArray)
- {
- if (p.Type is AsmResolver.DotNet.Signatures.SzArrayTypeSignature szP)
- {
- if (IsBlittablePrimitive(szP.BaseType)) { continue; }
- if (IsAnyStruct(szP.BaseType)) { continue; }
- }
- return false;
- }
- if (cat != ParamCategory.In) { return false; }
- if (IsHResultException(p.Type)) { return false; }
- if (IsBlittablePrimitive(p.Type)) { continue; }
- if (IsAnyStruct(p.Type)) { continue; }
- if (IsString(p.Type)) { continue; }
- if (IsRuntimeClassOrInterface(p.Type)) { continue; }
- if (IsObject(p.Type)) { continue; }
- if (IsGenericInstance(p.Type)) { continue; }
- if (IsComplexStruct(p.Type)) { continue; }
- return false;
- }
- return true;
- }
-
- ///
- /// Writes the marshaller infrastructure for a runtime class:
- /// * Public *Marshaller class with real ConvertToUnmanaged/ConvertToManaged bodies
- /// * file-scoped *ComWrappersMarshallerAttribute (CreateObject implementation)
- /// * file-scoped *ComWrappersCallback (IWindowsRuntimeObjectComWrappersCallback for sealed,
- /// IWindowsRuntimeUnsealedObjectComWrappersCallback for unsealed)
- /// Mirrors C++ write_class_marshaller, write_class_comwrappers_marshaller_attribute,
- /// and write_class_comwrappers_callback.
- ///
- private static void WriteClassMarshallerStub(TypeWriter w, TypeDefinition type)
- {
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
- string typeNs = type.Namespace?.Value ?? string.Empty;
- string fullProjected = $"global::{typeNs}.{nameStripped}";
-
- // Get the IID expression for the default interface (used by CreateObject).
- ITypeDefOrRef? defaultIface = Helpers.GetDefaultInterface(type);
- string defaultIfaceIid = defaultIface is not null
- ? w.WriteTemp("%", new System.Action(_ => WriteIidExpression(w, defaultIface)))
- : "default(global::System.Guid)";
-
- // Determine the marshalingType expression from the class's [MarshalingBehaviorAttribute]
- // (mirrors C++ get_marshaling_type_name). This is used by both the marshaller attribute and the
- // callback (the C++ code uses the same value for both).
- string marshalingType = GetMarshalingTypeName(type);
-
- bool isSealed = type.IsSealed;
-
- // For unsealed classes, the ConvertToUnmanaged path needs to know whether the default interface is
- // exclusive-to (mirrors C++ logic).
- TypeDefinition? defaultIfaceTd = defaultIface is null ? null : ResolveInterfaceTypeDef(defaultIface);
- bool defaultIfaceIsExclusive = defaultIfaceTd is not null && TypeCategorization.IsExclusiveTo(defaultIfaceTd);
-
- // Public *Marshaller class
- w.Write("public static unsafe class ");
- w.Write(nameStripped);
- w.Write("Marshaller\n{\n");
- w.Write(" public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged(");
- w.Write(fullProjected);
- w.Write(" value)\n {\n");
- if (isSealed)
- {
- // For projected sealed runtime classes, the RCW type is always unwrappable.
- w.Write(" if (value is not null)\n {\n");
- w.Write(" return WindowsRuntimeComWrappersMarshal.UnwrapObjectReferenceUnsafe(value).AsValue();\n");
- w.Write(" }\n");
- }
- else if (!defaultIfaceIsExclusive && defaultIface is not null)
- {
- string defIfaceTypeName = w.WriteTemp("%", new System.Action(_ => WriteTypeName(w, TypeSemanticsFactory.Get(defaultIface.ToTypeSignature(false)), TypedefNameType.Projected, false)));
- w.Write(" if (value is IWindowsRuntimeInterface<");
- w.Write(defIfaceTypeName);
- w.Write("> windowsRuntimeInterface)\n {\n");
- w.Write(" return windowsRuntimeInterface.GetInterface();\n");
- w.Write(" }\n");
- }
- else
- {
- w.Write(" if (value is not null)\n {\n");
- w.Write(" return value.GetDefaultInterface();\n");
- w.Write(" }\n");
- }
- w.Write(" return default;\n }\n\n");
- w.Write(" public static ");
- w.Write(fullProjected);
- w.Write("? ConvertToManaged(void* value)\n {\n");
- w.Write(" return (");
- w.Write(fullProjected);
- w.Write("?)");
- w.Write(isSealed ? "WindowsRuntimeObjectMarshaller" : "WindowsRuntimeUnsealedObjectMarshaller");
- w.Write(".ConvertToManaged<");
- w.Write(nameStripped);
- w.Write("ComWrappersCallback>(value);\n }\n}\n\n");
-
- // file-scoped *ComWrappersMarshallerAttribute - implements WindowsRuntimeComWrappersMarshallerAttribute.CreateObject
- w.Write("file sealed unsafe class ");
- w.Write(nameStripped);
- w.Write("ComWrappersMarshallerAttribute : WindowsRuntimeComWrappersMarshallerAttribute\n{\n");
- EmitUnsafeAccessorForDefaultIfaceIfGeneric(w, defaultIface);
- w.Write(" public override object CreateObject(void* value, out CreatedWrapperFlags wrapperFlags)\n {\n");
- w.Write(" WindowsRuntimeObjectReference valueReference = WindowsRuntimeComWrappersMarshal.CreateObjectReference(\n");
- w.Write(" externalComObject: value,\n");
- w.Write(" iid: ");
- w.Write(defaultIfaceIid);
- w.Write(",\n");
- w.Write(" marshalingType: ");
- w.Write(marshalingType);
- w.Write(",\n");
- w.Write(" wrapperFlags: out wrapperFlags);\n\n");
- w.Write(" return new ");
- w.Write(fullProjected);
- w.Write("(valueReference);\n }\n}\n\n");
-
- if (isSealed)
- {
- // file-scoped *ComWrappersCallback - implements IWindowsRuntimeObjectComWrappersCallback
- w.Write("file sealed unsafe class ");
- w.Write(nameStripped);
- w.Write("ComWrappersCallback : IWindowsRuntimeObjectComWrappersCallback\n{\n");
- EmitUnsafeAccessorForDefaultIfaceIfGeneric(w, defaultIface);
- w.Write(" public static object CreateObject(void* value, out CreatedWrapperFlags wrapperFlags)\n {\n");
- w.Write(" WindowsRuntimeObjectReference valueReference = WindowsRuntimeComWrappersMarshal.CreateObjectReferenceUnsafe(\n");
- w.Write(" externalComObject: value,\n");
- w.Write(" iid: ");
- w.Write(defaultIfaceIid);
- w.Write(",\n");
- w.Write(" marshalingType: ");
- w.Write(marshalingType);
- w.Write(",\n");
- w.Write(" wrapperFlags: out wrapperFlags);\n\n");
- w.Write(" return new ");
- w.Write(fullProjected);
- w.Write("(valueReference);\n }\n}\n");
- }
- else
- {
- // file-scoped *ComWrappersCallback - implements IWindowsRuntimeUnsealedObjectComWrappersCallback
- string nonProjectedRcn = $"{typeNs}.{nameStripped}";
- w.Write("file sealed unsafe class ");
- w.Write(nameStripped);
- w.Write("ComWrappersCallback : IWindowsRuntimeUnsealedObjectComWrappersCallback\n{\n");
- EmitUnsafeAccessorForDefaultIfaceIfGeneric(w, defaultIface);
-
- // TryCreateObject (non-projected runtime class name match)
- w.Write(" public static unsafe bool TryCreateObject(\n");
- w.Write(" void* value,\n");
- w.Write(" ReadOnlySpan runtimeClassName,\n");
- w.Write(" [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out object? wrapperObject,\n");
- w.Write(" out CreatedWrapperFlags wrapperFlags)\n {\n");
- w.Write(" if (runtimeClassName.SequenceEqual(\"");
- w.Write(nonProjectedRcn);
- w.Write("\".AsSpan()))\n {\n");
- w.Write(" WindowsRuntimeObjectReference valueReference = WindowsRuntimeComWrappersMarshal.CreateObjectReferenceUnsafe(\n");
- w.Write(" externalComObject: value,\n");
- w.Write(" iid: ");
- w.Write(defaultIfaceIid);
- w.Write(",\n");
- w.Write(" marshalingType: ");
- w.Write(marshalingType);
- w.Write(",\n");
- w.Write(" wrapperFlags: out wrapperFlags);\n\n");
- w.Write(" wrapperObject = new ");
- w.Write(fullProjected);
- w.Write("(valueReference);\n return true;\n }\n\n");
- w.Write(" wrapperObject = null;\n wrapperFlags = CreatedWrapperFlags.None;\n return false;\n }\n\n");
-
- // CreateObject (fallback)
- w.Write(" public static unsafe object CreateObject(void* value, out CreatedWrapperFlags wrapperFlags)\n {\n");
- w.Write(" WindowsRuntimeObjectReference valueReference = WindowsRuntimeComWrappersMarshal.CreateObjectReferenceUnsafe(\n");
- w.Write(" externalComObject: value,\n");
- w.Write(" iid: ");
- w.Write(defaultIfaceIid);
- w.Write(",\n");
- w.Write(" marshalingType: ");
- w.Write(marshalingType);
- w.Write(",\n");
- w.Write(" wrapperFlags: out wrapperFlags);\n\n");
- w.Write(" return new ");
- w.Write(fullProjected);
- w.Write("(valueReference);\n }\n}\n");
- }
- }
-
- ///
- /// Emits the [UnsafeAccessor] declaration for the default interface IID inside a file-scoped
- /// ComWrappers class. Only emits if the default interface is a generic instantiation.
- /// Mirrors C++ write_class_comwrappers_marshaller_attribute / write_class_comwrappers_callback
- /// behavior of inserting write_unsafe_accessor_for_iid at the top of the class body.
- ///
- private static void EmitUnsafeAccessorForDefaultIfaceIfGeneric(TypeWriter w, ITypeDefOrRef? defaultIface)
- {
- if (defaultIface is TypeSpecification ts && ts.Signature is GenericInstanceTypeSignature gi)
- {
- EmitUnsafeAccessorForIid(w, gi);
- }
- }
-
- ///
- /// Writes a minimal interface 'Methods' static class with method body emission.
- /// Mirrors C++ write_static_abi_methods: void/no-args methods and
- /// blittable-primitive-return/no-args methods get real implementations; everything else
- /// remains as 'throw null!' stubs (deferred — needs full per-parameter marshalling).
- ///
- private static void WriteInterfaceMarshallerStub(TypeWriter w, TypeDefinition type)
- {
- string name = type.Name?.Value ?? string.Empty;
- string nameStripped = Helpers.StripBackticks(name);
- // Mirrors C++ write_static_abi_classes: visibility is internal if the interface is
- // exclusive to a class (and not opted into PublicExclusiveTo) or if it's marked
- // [ProjectionInternal]; public otherwise.
- bool useInternal = (TypeCategorization.IsExclusiveTo(type) && !w.Settings.PublicExclusiveTo)
- || TypeCategorization.IsProjectionInternal(type);
-
- // Fast ABI: if this interface is a non-default exclusive-to interface of a fast-abi
- // class, skip emitting it entirely — its members are merged into the default
- // interface's Methods class. Mirrors C++ code_writers.h:9082-9089
- // (write_static_abi_classes early return on contains_other_interface(iface)).
- if (IsFastAbiOtherInterface(type)) { return; }
-
- // If the interface is exclusive-to a class that's been excluded from the projection,
- // skip emitting the entire *Methods class — it would be dead code (the owning class
- // is manually projected in WinRT.Runtime, e.g. IColorHelperStatics for ColorHelper,
- // IColorsStatics for Colors, IFontWeightsStatics for FontWeights). The C++ tool also
- // omits these because their owning class is not projected.
- if (TypeCategorization.IsExclusiveTo(type))
- {
- TypeDefinition? owningClass = GetExclusiveToType(type);
- if (owningClass is not null && !w.Settings.Filter.Includes(owningClass))
- {
- return;
- }
- }
-
- // Mirrors C++ skip_exclusive_events: events on exclusive interfaces (used by the class)
- // are inlined in the RCW class, so we skip emitting them in the Methods type.
- bool skipExclusiveEvents = false;
- if (TypeCategorization.IsExclusiveTo(type) && !w.Settings.PublicExclusiveTo)
- {
- TypeDefinition? classType = GetExclusiveToType(type);
- if (classType is not null)
- {
- foreach (InterfaceImplementation impl in classType.Interfaces)
- {
- TypeDefinition? implDef = ResolveInterfaceTypeDef(impl.Interface!);
- if (implDef is not null && implDef == type)
- {
- skipExclusiveEvents = true;
- break;
- }
- }
- }
- }
-
- // Fast ABI: if this interface is the default interface of a fast-abi class, the
- // generated Methods class must include the merged members of the default interface
- // PLUS each [ExclusiveTo] non-default interface in vtable order, with progressively
- // increasing slot indices. Mirrors C++ code_writers.h:9113-9128.
- // For non-fast-abi interfaces, the segment list is just [(type, INSPECTABLE_METHOD_COUNT, skipExclusiveEvents)].
- const int InspectableMethodCount = 6;
- var segments = new List<(TypeDefinition Iface, int StartSlot, bool SkipEvents)>();
- var fastAbi = GetFastAbiClassForInterface(type);
- bool isFastAbiDefault = fastAbi is not null && fastAbi.Value.Default is not null
- && InterfacesEqualByName(fastAbi.Value.Default, type);
- if (isFastAbiDefault)
- {
- int slot = InspectableMethodCount;
- // Default interface: skip its events (they're inlined in the RCW class).
- segments.Add((type, slot, true));
- slot += CountMethods(type) + GetClassHierarchyIndex(fastAbi!.Value.Class);
- foreach (TypeDefinition other in fastAbi.Value.Others)
- {
- segments.Add((other, slot, false));
- slot += CountMethods(other);
- }
- }
- else
- {
- segments.Add((type, InspectableMethodCount, skipExclusiveEvents));
- }
-
- // Skip emission if the entire merged class would be empty.
- bool hasAnyMember = false;
- foreach ((TypeDefinition seg, int _, bool segSkipEvents) in segments)
- {
- if (HasEmittableMembers(seg, segSkipEvents)) { hasAnyMember = true; break; }
- }
- if (!hasAnyMember) { return; }
-
- w.Write(useInternal ? "internal static class " : "public static class ");
- w.Write(nameStripped);
- w.Write("Methods\n{\n");
-
- foreach ((TypeDefinition iface, int startSlot, bool segSkipEvents) in segments)
- {
- EmitMethodsClassMembersFor(w, iface, startSlot, segSkipEvents);
- }
-
- w.Write("}\n");
- }
-
- /// True if the interface has at least one non-special method, property, or non-skipped event.
- private static bool HasEmittableMembers(TypeDefinition iface, bool skipExclusiveEvents)
- {
- foreach (MethodDefinition m in iface.Methods)
- {
- if (!Helpers.IsSpecial(m)) { return true; }
- }
- foreach (PropertyDefinition _ in iface.Properties) { return true; }
- if (!skipExclusiveEvents)
- {
- foreach (EventDefinition _ in iface.Events) { return true; }
- }
- return false;
- }
-
- /// Returns the number of methods (including special accessors) on the interface.
- private static int CountMethods(TypeDefinition iface)
- {
- int count = 0;
- foreach (MethodDefinition _ in iface.Methods) { count++; }
- return count;
- }
-
- /// Mirrors C++ get_class_hierarchy_index: distance from in inheritance.
- private static int GetClassHierarchyIndex(TypeDefinition classType)
- {
- if (classType.BaseType is null) { return 0; }
- string ns = classType.BaseType.Namespace?.Value ?? string.Empty;
- string nm = classType.BaseType.Name?.Value ?? string.Empty;
- if (ns == "System" && nm == "Object") { return 0; }
- TypeDefinition? baseDef = classType.BaseType as TypeDefinition;
- if (baseDef is null && _cacheRef is not null)
- {
- try { baseDef = classType.BaseType.Resolve(_cacheRef.RuntimeContext); }
- catch { baseDef = null; }
- baseDef ??= _cacheRef.Find(string.IsNullOrEmpty(ns) ? nm : (ns + "." + nm));
- }
- if (baseDef is null) { return 0; }
- return GetClassHierarchyIndex(baseDef) + 1;
- }
-
- private static bool InterfacesEqualByName(TypeDefinition a, TypeDefinition b)
- {
- if (a == b) { return true; }
- return (a.Namespace?.Value ?? string.Empty) == (b.Namespace?.Value ?? string.Empty)
- && (a.Name?.Value ?? string.Empty) == (b.Name?.Value ?? string.Empty);
- }
-
- ///
- /// Emits the per-interface members (methods, properties, events) into an already-open Methods
- /// static class. Used both for the standalone case and for the fast-abi merged emission.
- ///
- private static void EmitMethodsClassMembersFor(TypeWriter w, TypeDefinition type, int startSlot, bool skipExclusiveEvents)
- {
- // Build a map from each MethodDefinition to its WinMD vtable slot.
- // Mirrors C++ get_vmethod_index: slot = (method.index() - vtable_base) + start_slot.
- // In AsmResolver, type.Methods is iterated in MethodDef row order, so the position of each
- // method in type.Methods (relative to the first method of the type) gives us the same value.
- Dictionary methodSlot = new();
- {
- int idx = 0;
- foreach (MethodDefinition m in type.Methods)
- {
- methodSlot[m] = idx + startSlot;
- idx++;
- }
- }
-
- // Emit non-special methods first (output order is unchanged from before; only the slot lookup changes).
- foreach (MethodDefinition method in type.Methods)
- {
- if (Helpers.IsSpecial(method)) { continue; }
- string mname = method.Name?.Value ?? string.Empty;
- MethodSig sig = new(method);
-
- w.Write(" [MethodImpl(MethodImplOptions.NoInlining)]\n");
- w.Write(" public static unsafe ");
- WriteProjectionReturnType(w, sig);
- w.Write(" ");
- w.Write(mname);
- w.Write("(WindowsRuntimeObjectReference thisReference");
- if (sig.Params.Count > 0) { w.Write(", "); }
- WriteParameterList(w, sig);
- w.Write(")");
-
- // Emit the body if we can handle this case. Slot comes from the method's WinMD index.
- EmitAbiMethodBodyIfSimple(w, sig, methodSlot[method], isNoExcept: Helpers.IsNoExcept(method));
- }
-
- // Emit property accessors. Each getter / setter consumes one vtable slot — looked up from the underlying method.
- foreach (PropertyDefinition prop in type.Properties)
- {
- string pname = prop.Name?.Value ?? string.Empty;
- (MethodDefinition? getter, MethodDefinition? setter) = Helpers.GetPropertyMethods(prop);
- string propType = WritePropType(w, prop);
- (MethodDefinition? gMethod, MethodDefinition? sMethod) = (getter, setter);
- // Mirrors C++ helpers.h:46-49: the [NoException] check on properties applies to BOTH
- // accessors of the property (the attribute is on the property itself, not on the
- // individual accessors).
- bool propIsNoExcept = Helpers.IsNoExcept(prop);
- if (gMethod is not null)
- {
- MethodSig getSig = new(gMethod);
- w.Write(" [MethodImpl(MethodImplOptions.NoInlining)]\n");
- w.Write(" public static unsafe ");
- w.Write(propType);
- w.Write(" ");
- w.Write(pname);
- w.Write("(WindowsRuntimeObjectReference thisReference)");
- EmitAbiMethodBodyIfSimple(w, getSig, methodSlot[gMethod], isNoExcept: propIsNoExcept);
- }
- if (sMethod is not null)
- {
- MethodSig setSig = new(sMethod);
- w.Write(" [MethodImpl(MethodImplOptions.NoInlining)]\n");
- w.Write(" public static unsafe void ");
- w.Write(pname);
- w.Write("(WindowsRuntimeObjectReference thisReference, ");
- // Mirrors C++ code_writers.h:7193 — setter parameter uses the is_set_property=true
- // form of write_prop_type, which for SZ array types emits ReadOnlySpan instead
- // of T[] (the getter's return-type form).
- w.Write(WritePropType(w, prop, isSetProperty: true));
- w.Write(" value)");
- EmitAbiMethodBodyIfSimple(w, setSig, methodSlot[sMethod], paramNameOverride: "value", isNoExcept: propIsNoExcept);
- }
- }
-
- // Emit event member methods (returns an event source, takes thisObject + thisReference).
- // Skip events on exclusive interfaces used by their class — they're inlined directly in
- // the RCW class. (Mirrors C++ skip_exclusive_events.)
- foreach (EventDefinition evt in type.Events)
- {
- if (skipExclusiveEvents) { continue; }
- string evtName = evt.Name?.Value ?? string.Empty;
- AsmResolver.DotNet.Signatures.TypeSignature evtSig = evt.EventType!.ToTypeSignature(false);
- bool isGenericEvent = evtSig is AsmResolver.DotNet.Signatures.GenericInstanceTypeSignature;
-
- // Use the add method's WinMD slot. Mirrors C++: events use the add_X method's vmethod_index.
- (MethodDefinition? addMethod, MethodDefinition? _) = Helpers.GetEventMethods(evt);
- int eventSlot = addMethod is not null && methodSlot.TryGetValue(addMethod, out int es) ? es : 0;
-
- // Build the projected event source type name. For non-generic delegate handlers, the
- // EventSource subclass lives in the ABI namespace alongside this Methods class, so
- // we need to use the ABI-qualified name. For generic handlers (Windows.Foundation.*EventHandler),
- // it's mapped to global::WindowsRuntime.InteropServices.EventHandlerEventSource<...>.
- string eventSourceProjectedFull;
- if (isGenericEvent)
- {
- eventSourceProjectedFull = w.WriteTemp("%", new System.Action(_ =>
- WriteTypeName(w, TypeSemanticsFactory.Get(evtSig), TypedefNameType.EventSource, true)));
- if (!eventSourceProjectedFull.StartsWith("global::", System.StringComparison.Ordinal))
- {
- eventSourceProjectedFull = "global::" + eventSourceProjectedFull;
- }
- }
- else
- {
- // Non-generic delegate handler: the EventSource lives in the same ABI namespace
- // as this Methods class, so we use just the short name (matches truth output).
- string delegateName = string.Empty;
- if (evtSig is AsmResolver.DotNet.Signatures.TypeDefOrRefSignature td)
- {
- delegateName = td.Type?.Name?.Value ?? string.Empty;
- delegateName = Helpers.StripBackticks(delegateName);
- }
- eventSourceProjectedFull = delegateName + "EventSource";
- }
- string eventSourceInteropType = isGenericEvent
- ? EncodeInteropTypeName(evtSig, TypedefNameType.EventSource) + ", WinRT.Interop"
- : string.Empty;
-
- // Emit the per-event ConditionalWeakTable static field.
- w.Write("\n private static ConditionalWeakTable