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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions nuget/Microsoft.Windows.CsWinRT.targets
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<CompilerVisibleProperty Include="CsWinRTRcwFactoryFallbackGeneratorForceOptIn" />
<CompilerVisibleProperty Include="CsWinRTRcwFactoryFallbackGeneratorForceOptOut" />
<CompilerVisibleProperty Include="CsWinRTCcwLookupTableGeneratorEnabled" />
<CompilerVisibleProperty Include="CsWinRTMergeReferencedActivationFactories" />
</ItemGroup>

<Import Project="$(MSBuildThisFileDirectory)Microsoft.Windows.CsWinRT.Embedded.targets" Condition="'$(CsWinRTEmbedded)' == 'true'"/>
Expand Down Expand Up @@ -310,6 +311,11 @@ $(CsWinRTInternalProjection)
<CsWinRTEnableIDynamicInterfaceCastableSupport Condition="'$(CsWinRTEnableIDynamicInterfaceCastableSupport)' == ''">true</CsWinRTEnableIDynamicInterfaceCastableSupport>
</PropertyGroup>

<!-- Default values for all other CsWinRT properties (without feature switches) -->
<PropertyGroup>
<CsWinRTMergeReferencedActivationFactories Condition="'$(CsWinRTMergeReferencedActivationFactories)' == ''">false</CsWinRTMergeReferencedActivationFactories>
</PropertyGroup>

<!--
Configuration for the feature switches (to support IL trimming).
See the 'ILLink.Substitutions.xml' file for more details on that.
Expand Down
1 change: 1 addition & 0 deletions nuget/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ C#/WinRT behavior can be customized with these project properties:
| CsWinRTIIDOptimizerOptOut | true \| *false | Determines whether to run the IIDOptimizer on the projection assembly |
| CsWinRTRcwFactoryFallbackGeneratorForceOptIn | true \| *false | Forces the RCW factory fallback generator to be enabled (it only runs on .exe projects by default) |
| CsWinRTRcwFactoryFallbackGeneratorForceOptOut | true \| *false | Forces the RCW factory fallback generator to be disabled (overrides "ForceOptIn" as well) |
| CsWinRTMergeReferencedActivationFactories | true \| *false | Makes the native `DllGetActivationFactory` exported function for AOT scenarios also forward the activation call to all referenced WinRT components, allowing them to all be merged into a single executable or shared library |
\*Default value

**If CsWinRTFilters is not defined, the following effective value is used:
Expand Down
36 changes: 36 additions & 0 deletions src/Authoring/WinRT.SourceGenerator/Extensions/SymbolExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;

Expand Down Expand Up @@ -51,4 +52,39 @@ static bool IsAnyContainingTypePublic(IEnumerable<ISymbol> symbols)
_ => false
};
}

/// <summary>
/// Tries to get an attribute with the specified type.
/// </summary>
/// <param name="symbol">The input <see cref="ISymbol"/> instance to check.</param>
/// <param name="typeSymbol">The <see cref="ITypeSymbol"/> instance for the attribute type to look for.</param>
/// <param name="attributeData">The resulting attribute, if it was found.</param>
/// <returns>Whether or not <paramref name="symbol"/> has an attribute with the specified name.</returns>
public static bool TryGetAttributeWithType(this ISymbol symbol, ITypeSymbol typeSymbol, [NotNullWhen(true)] out AttributeData? attributeData)
{
foreach (AttributeData attribute in symbol.GetAttributes())
{
if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, typeSymbol))
{
attributeData = attribute;

return true;
}
}

attributeData = null;

return false;
}

/// <summary>
/// Checks whether a given symbol is accessible from the assembly of a given compilation (including eg. through nested types).
/// </summary>
/// <param name="symbol">The input <see cref="ISymbol"/> instance.</param>
/// <param name="compilation">The <see cref="Compilation"/> instance currently in use.</param>
/// <returns>Whether <paramref name="symbol"/> is accessible from the assembly for <paramref name="compilation"/>.</returns>
public static bool IsAccessibleFromCompilationAssembly(this ISymbol symbol, Compilation compilation)
{
return compilation.IsSymbolAccessibleWithin(symbol, compilation.Assembly);
}
}
67 changes: 65 additions & 2 deletions src/Authoring/WinRT.SourceGenerator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,14 @@ public static int DllGetActivationFactory(void* activatableClassId, void** facto

try
{
IntPtr obj = GetActivationFactory(MarshalString.FromAbiUnsafe((IntPtr)activatableClassId));
scoped ReadOnlySpan<char> fullyQualifiedTypeName = MarshalString.FromAbiUnsafe((IntPtr)activatableClassId);

IntPtr obj = GetActivationFactory(fullyQualifiedTypeName);

if ((void*)obj is null)
{
obj = TryGetDependentActivationFactory(fullyQualifiedTypeName);
}

if ((void*)obj is null)
{
Expand Down Expand Up @@ -273,6 +280,56 @@ public static int DllCanUnloadNow()
}
""");
}

/// <summary>
/// Generates the native exports for a WinRT component.
/// </summary>
/// <param name="context">The <see cref="GeneratorExecutionContext"/> value to use to produce source files.</param>
public static void GenerateWinRTExportsType(GeneratorExecutionContext context)
{
if (context.Compilation.AssemblyName is not { Length: > 0 } assemblyName)
{
return;
}

// Make sure to escape invalid characters for namespace names.
// See ECMA 335, II.6.2 and II.5.2/3.
if (assemblyName.AsSpan().IndexOfAny("$@`?".AsSpan()) != -1)
{
char[] buffer = new char[assemblyName.Length];

for (int i = 0; i < assemblyName.Length; i++)
{
buffer[i] = assemblyName[i] is '$' or '@' or '`' or '?'
? '_'
: assemblyName[i];
}

assemblyName = new string(buffer);
}

context.AddSource("ExportsType.g.cs", $$"""
// <auto-generated/>
#pragma warning disable

[assembly: global::WinRT.WinRTAssemblyExportsType(typeof(ABI.Exports.{{assemblyName}}.Module))]

namespace ABI.Exports.{{assemblyName}}
{
using global::System;

/// <inheritdoc cref="global::WinRT.Module"/>
public static partial class Module
{
/// <inheritdoc cref="global::WinRT.Module.GetActivationFactory(ReadOnlySpan{char})"/>
public static IntPtr GetActivationFactory(ReadOnlySpan<char> runtimeClassId)
{
return global::WinRT.Module.GetActivationFactory(runtimeClassId);
}
}
}
""");
}
}

[Generator]
Expand All @@ -289,11 +346,17 @@ public void Execute(GeneratorExecutionContext context)
ComponentGenerator generator = new(context);
generator.Generate();

// Also emit the native exports for NAOT compiled WinRT components, if needed
// Emit the native exports for NAOT compiled WinRT components, if needed
if (context.IsCsWinRTComponent() && context.ShouldGenerateWinRTNativeExports())
{
ComponentGenerator.GenerateWinRTNativeExports(context);
}

// Also emit the unique exported 'Module' type to avoid ambiguities in some scenarios (eg. merging)
if (context.IsCsWinRTComponent())
{
ComponentGenerator.GenerateWinRTExportsType(context);
}
}

public void Initialize(GeneratorInitializationContext context)
Expand Down
10 changes: 10 additions & 0 deletions src/Authoring/WinRT.SourceGenerator/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ public static bool GetCsWinRTRcwFactoryFallbackGeneratorForceOptIn(this Analyzer
return false;
}

public static bool GetCsWinRTMergeReferencedActivationFactories(this AnalyzerConfigOptionsProvider provider)
{
if (provider.GlobalOptions.TryGetValue("build_property.CsWinRTMergeReferencedActivationFactories", out var csWinRTMergeReferencedActivationFactories))
{
return bool.TryParse(csWinRTMergeReferencedActivationFactories, out var isCsWinRTMergeReferencedActivationFactories) && isCsWinRTMergeReferencedActivationFactories;
}

return false;
}

public static bool GetCsWinRTRcwFactoryFallbackGeneratorForceOptOut(this AnalyzerConfigOptionsProvider provider)
{
if (provider.GlobalOptions.TryGetValue("build_property.CsWinRTRcwFactoryFallbackGeneratorForceOptOut", out var csWinRTRcwFactoryFallbackGeneratorForceOptOut))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using WinRT.SourceGenerator;

#nullable enable

namespace Generator;

[Generator]
public sealed class MergeReferencedActivationFactoriesGenerator : IIncrementalGenerator
{
/// <inheritdoc/>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Get whether the generator is enabled
IncrementalValueProvider<bool> isGeneratorEnabled = context.AnalyzerConfigOptionsProvider.Select(static (options, token) =>
{
return options.GetCsWinRTMergeReferencedActivationFactories();
});

// Get the fully qualified type names of all assembly exports types to merge
IncrementalValueProvider<EquatableArray<string>> assemblyExportsTypeNames =
context.CompilationProvider
.Combine(isGeneratorEnabled)
.Select(static (item, token) =>
{
// Immediately bail if the generator is disabled
if (!item.Right)
{
return new EquatableArray<string>(ImmutableArray<string>.Empty);
}

ImmutableArray<string>.Builder builder = ImmutableArray.CreateBuilder<string>();

foreach (MetadataReference metadataReference in item.Left.References)
{
token.ThrowIfCancellationRequested();

if (item.Left.GetAssemblyOrModuleSymbol(metadataReference) is not IAssemblySymbol assemblySymbol)
{
continue;
}

token.ThrowIfCancellationRequested();

// Add the type name if the assembly is a WinRT component
if (TryGetDependentAssemblyExportsTypeName(
assemblySymbol,
item.Left,
token,
out string? name))
{
builder.Add(name);
}
}

token.ThrowIfCancellationRequested();

return new EquatableArray<string>(builder.ToImmutable());
});

// Generate the chaining helper
context.RegisterImplementationSourceOutput(assemblyExportsTypeNames, static (context, assemblyExportsTypeNames) =>
{
StringBuilder builder = new();

builder.AppendLine("""
// <auto-generated/>
#pragma warning disable

namespace WinRT
{
using global::System;

/// <inheritdoc cref="Module"/>
partial class Module
{
/// <summary>
/// Tries to retrieve the activation factory from all dependent WinRT components.
/// </summary>
/// <param name="fullyQualifiedTypeName">The marshalled fully qualified type name of the activation factory to retrieve.</param>
/// <returns>The pointer to the activation factory that corresponds with the class specified by <paramref name="fullyQualifiedTypeName"/>.</returns>
internal static unsafe IntPtr TryGetDependentActivationFactory(ReadOnlySpan<char> fullyQualifiedTypeName)
{
""");

if (!assemblyExportsTypeNames.IsEmpty)
{
builder.AppendLine(" IntPtr obj;");
builder.AppendLine();
}

foreach (string assemblyExportsTypeName in assemblyExportsTypeNames)
{
builder.AppendLine($$"""
obj = global::{{assemblyExportsTypeName}}.GetActivationFactory(fullyQualifiedTypeName);

if ((void*)obj is not null)
{
return obj;
}

""");
}

builder.AppendLine("""
return default;
}
}
}
""");

context.AddSource("ChainedExports.g.cs", builder.ToString());
});
}

/// <summary>
/// Tries to get the name of a dependent WinRT component from a given assembly.
/// </summary>
/// <param name="assemblySymbol">The assembly symbol to analyze.</param>
/// <param name="compilation">The <see cref="Compilation"/> instance to use.</param>
/// <param name="token">The <see cref="CancellationToken"/> instance to use.</param>
/// <param name="name">The resulting type name, if found.</param>
/// <returns>Whether a type name was found.</returns>
private static bool TryGetDependentAssemblyExportsTypeName(
IAssemblySymbol assemblySymbol,
Compilation compilation,
CancellationToken token,
[NotNullWhen(true)] out string? name)
{
// Get the attribute to lookup to find the target type to use
INamedTypeSymbol winRTAssemblyExportsTypeAttributeSymbol = compilation.GetTypeByMetadataName("WinRT.WinRTAssemblyExportsTypeAttribute")!;

// Make sure the assembly does have the attribute on it
if (!assemblySymbol.TryGetAttributeWithType(winRTAssemblyExportsTypeAttributeSymbol, out AttributeData? attributeData))
{
name = null;

return false;
}

token.ThrowIfCancellationRequested();

// Sanity check: we should have a valid type in the annotation
if (attributeData.ConstructorArguments is not [{ Kind: TypedConstantKind.Type, Value: INamedTypeSymbol assemblyExportsTypeSymbol }])
{
name = null;

return false;
}

token.ThrowIfCancellationRequested();

// Other sanity check: this type should be accessible from this compilation
if (!assemblyExportsTypeSymbol.IsAccessibleFromCompilationAssembly(compilation))
{
name = null;

return false;
}

token.ThrowIfCancellationRequested();

name = assemblyExportsTypeSymbol.ToDisplayString();

return true;
}
}
1 change: 1 addition & 0 deletions src/Tests/DiagnosticTests/DiagnosticTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFrameworks>$(TestsBuildTFMs)</TargetFrameworks>
<IsPackable>false</IsPackable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand Down
Loading