@@ -773,21 +773,21 @@ private static void GenerateVtableAttributes(
773
773
GenerateVtableAttributes ( sourceProductionContext . AddSource , value . vtableAttributes , value . context . properties . isCsWinRTComponent , value . context . escapedAssemblyName ) ;
774
774
}
775
775
776
- internal static string GenerateVtableEntry ( VtableAttribute vtableAttribute , string escapedAssemblyName )
776
+ internal static string GenerateVtableEntry ( VtableEntry vtableEntry , string escapedAssemblyName )
777
777
{
778
778
StringBuilder source = new ( ) ;
779
779
780
- foreach ( var genericInterface in vtableAttribute . GenericInterfaces )
780
+ foreach ( var genericInterface in vtableEntry . GenericInterfaces )
781
781
{
782
782
source . AppendLine ( GenericVtableInitializerStrings . GetInstantiationInitFunction (
783
783
genericInterface . GenericDefinition ,
784
784
genericInterface . GenericParameters ,
785
785
escapedAssemblyName ) ) ;
786
786
}
787
787
788
- if ( vtableAttribute . IsDelegate )
788
+ if ( vtableEntry . IsDelegate )
789
789
{
790
- var @interface = vtableAttribute . Interfaces . First ( ) ;
790
+ var @interface = vtableEntry . Interfaces . First ( ) ;
791
791
source . AppendLine ( ) ;
792
792
source . AppendLine ( $$ """
793
793
var delegateInterface = new global::System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry
@@ -799,15 +799,15 @@ internal static string GenerateVtableEntry(VtableAttribute vtableAttribute, stri
799
799
return global::WinRT.DelegateTypeDetails<{{ @interface }} >.GetExposedInterfaces(delegateInterface);
800
800
""" ) ;
801
801
}
802
- else if ( vtableAttribute . Interfaces . Any ( ) )
802
+ else if ( vtableEntry . Interfaces . Any ( ) )
803
803
{
804
804
source . AppendLine ( ) ;
805
805
source . AppendLine ( $$ """
806
806
return new global::System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry[]
807
807
{
808
808
""" ) ;
809
809
810
- foreach ( var @interface in vtableAttribute . Interfaces )
810
+ foreach ( var @interface in vtableEntry . Interfaces )
811
811
{
812
812
var genericStartIdx = @interface . IndexOf ( '<' ) ;
813
813
var interfaceStaticsMethod = @interface [ ..( genericStartIdx == - 1 ? @interface . Length : genericStartIdx ) ] + "Methods" ;
@@ -840,6 +840,10 @@ internal static string GenerateVtableEntry(VtableAttribute vtableAttribute, stri
840
840
841
841
internal static void GenerateVtableAttributes ( Action < string , string > addSource , ImmutableArray < VtableAttribute > vtableAttributes , bool isCsWinRTComponentFromAotOptimizer , string escapedAssemblyName )
842
842
{
843
+ var vtableEntryToVtableClassName = new Dictionary < VtableEntry , string > ( ) ;
844
+ StringBuilder vtableClassesSource = new ( ) ;
845
+ bool firstVtableClass = true ;
846
+
843
847
// Using ToImmutableHashSet to avoid duplicate entries from the use of partial classes by the developer
844
848
// to split out their implementation. When they do that, we will get multiple entries here for that
845
849
// and try to generate the same attribute and file with the same data as we use the semantic model
@@ -850,11 +854,10 @@ internal static void GenerateVtableAttributes(Action<string, string> addSource,
850
854
// from the AOT optimizer, then any public types are not handled
851
855
// right now as they are handled by the WinRT component source generator
852
856
// calling this.
853
- if ( ( ( isCsWinRTComponentFromAotOptimizer && ! vtableAttribute . IsPublic ) || ! isCsWinRTComponentFromAotOptimizer ) &&
857
+ if ( ( ( isCsWinRTComponentFromAotOptimizer && ! vtableAttribute . IsPublic ) || ! isCsWinRTComponentFromAotOptimizer ) &&
854
858
vtableAttribute . Interfaces . Any ( ) )
855
859
{
856
860
StringBuilder source = new ( ) ;
857
- source . AppendLine ( "using static WinRT.TypeExtensions;\n " ) ;
858
861
if ( ! vtableAttribute . IsGlobalNamespace )
859
862
{
860
863
source . AppendLine ( $$ """
@@ -863,6 +866,16 @@ namespace {{vtableAttribute.Namespace}}
863
866
""" ) ;
864
867
}
865
868
869
+ // Check if this class shares the same vtable as another class. If so, reuse the same generated class for it.
870
+ VtableEntry entry = new ( vtableAttribute . Interfaces , vtableAttribute . GenericInterfaces , vtableAttribute . IsDelegate ) ;
871
+ bool vtableEntryExists = vtableEntryToVtableClassName . TryGetValue ( entry , out var ccwClassName ) ;
872
+ if ( ! vtableEntryExists )
873
+ {
874
+ var @namespace = vtableAttribute . IsGlobalNamespace ? "" : $ "{ vtableAttribute . Namespace } .";
875
+ ccwClassName = GeneratorHelper . EscapeTypeNameForIdentifier ( @namespace + vtableAttribute . ClassName ) ;
876
+ vtableEntryToVtableClassName . Add ( entry , ccwClassName ) ;
877
+ }
878
+
866
879
var escapedClassName = GeneratorHelper . EscapeTypeNameForIdentifier ( vtableAttribute . ClassName ) ;
867
880
868
881
// Simple case when the type is not nested
@@ -874,7 +887,7 @@ namespace {{vtableAttribute.Namespace}}
874
887
}
875
888
876
889
source . AppendLine ( $$ """
877
- [global::WinRT.WinRTExposedType(typeof({{ escapedClassName }} WinRTTypeDetails))]
890
+ [global::WinRT.WinRTExposedType(typeof(global::WinRT. {{ escapedAssemblyName }} VtableClasses. {{ ccwClassName }} WinRTTypeDetails))]
878
891
partial class {{ vtableAttribute . ClassName }}
879
892
{
880
893
}
@@ -900,7 +913,7 @@ partial class {{vtableAttribute.ClassName}}
900
913
}
901
914
902
915
source . AppendLine ( $$ """
903
- [global::WinRT.WinRTExposedType(typeof({{ escapedClassName }} WinRTTypeDetails))]
916
+ [global::WinRT.WinRTExposedType(typeof(global::WinRT. {{ escapedAssemblyName }} VtableClasses. {{ ccwClassName }} WinRTTypeDetails))]
904
917
partial {{ classHierarchy [ 0 ] . GetTypeKeyword ( ) }} {{ classHierarchy [ 0 ] . QualifiedName }}
905
918
{
906
919
}
@@ -913,62 +926,78 @@ partial class {{vtableAttribute.ClassName}}
913
926
}
914
927
}
915
928
916
- source . AppendLine ( ) ;
917
- source . AppendLine ( $$ """
918
- internal sealed class {{ escapedClassName }} WinRTTypeDetails : global::WinRT.IWinRTExposedTypeDetails
929
+ // Only generate class, if this is the first time we run into this set of vtables.
930
+ if ( ! vtableEntryExists )
931
+ {
932
+ if ( firstVtableClass )
933
+ {
934
+ vtableClassesSource . AppendLine ( $$ """
935
+ namespace WinRT.{{ escapedAssemblyName }} VtableClasses
936
+ {
937
+ """ ) ;
938
+ firstVtableClass = false ;
939
+ }
940
+ else
941
+ {
942
+ vtableClassesSource . AppendLine ( ) ;
943
+ }
944
+
945
+ vtableClassesSource . AppendLine ( $$ """
946
+ internal sealed class {{ ccwClassName }} WinRTTypeDetails : global::WinRT.IWinRTExposedTypeDetails
919
947
{
920
948
public global::System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry[] GetExposedInterfaces()
921
949
{
922
950
""" ) ;
923
951
924
- if ( vtableAttribute . Interfaces . Any ( ) )
925
- {
926
- foreach ( var genericInterface in vtableAttribute . GenericInterfaces )
952
+ if ( vtableAttribute . Interfaces . Any ( ) )
927
953
{
928
- source . AppendLine ( GenericVtableInitializerStrings . GetInstantiationInitFunction (
929
- genericInterface . GenericDefinition ,
930
- genericInterface . GenericParameters ,
931
- escapedAssemblyName ) ) ;
932
- }
954
+ foreach ( var genericInterface in vtableAttribute . GenericInterfaces )
955
+ {
956
+ vtableClassesSource . AppendLine ( GenericVtableInitializerStrings . GetInstantiationInitFunction (
957
+ genericInterface . GenericDefinition ,
958
+ genericInterface . GenericParameters ,
959
+ escapedAssemblyName ) ) ;
960
+ }
933
961
934
- source . AppendLine ( ) ;
935
- source . AppendLine ( $$ """
962
+ vtableClassesSource . AppendLine ( ) ;
963
+ vtableClassesSource . AppendLine ( $$ """
936
964
return new global::System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry[]
937
965
{
938
966
""" ) ;
939
967
940
- foreach ( var @interface in vtableAttribute . Interfaces )
941
- {
942
- var genericStartIdx = @interface . IndexOf ( '<' ) ;
943
- var interfaceStaticsMethod = @interface [ ..( genericStartIdx == - 1 ? @interface . Length : genericStartIdx ) ] + "Methods" ;
944
- if ( genericStartIdx != - 1 )
968
+ foreach ( var @interface in vtableAttribute . Interfaces )
945
969
{
946
- interfaceStaticsMethod += @interface [ genericStartIdx ..@interface . Length ] ;
947
- }
970
+ var genericStartIdx = @interface . IndexOf ( '<' ) ;
971
+ var interfaceStaticsMethod = @interface [ ..( genericStartIdx == - 1 ? @interface . Length : genericStartIdx ) ] + "Methods" ;
972
+ if ( genericStartIdx != - 1 )
973
+ {
974
+ interfaceStaticsMethod += @interface [ genericStartIdx ..@interface . Length ] ;
975
+ }
948
976
949
- source . AppendLine ( $$ """
977
+ vtableClassesSource . AppendLine ( $$ """
950
978
new global::System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry
951
979
{
952
980
IID = global::ABI.{{ interfaceStaticsMethod }} .IID,
953
981
Vtable = global::ABI.{{ interfaceStaticsMethod }} .AbiToProjectionVftablePtr
954
982
},
955
983
""" ) ;
956
- }
957
- source . AppendLine ( $$ """
984
+ }
985
+ vtableClassesSource . AppendLine ( $$ """
958
986
};
959
987
""" ) ;
960
- }
961
- else
962
- {
963
- source . AppendLine ( $$ """
988
+ }
989
+ else
990
+ {
991
+ vtableClassesSource . AppendLine ( $$ """
964
992
return global::System.Array.Empty<global::System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry>();
965
993
""" ) ;
966
- }
994
+ }
967
995
968
- source . AppendLine ( $$ """
996
+ vtableClassesSource . AppendLine ( $$ """
969
997
}
970
998
}
971
999
""" ) ;
1000
+ }
972
1001
973
1002
if ( ! vtableAttribute . IsGlobalNamespace )
974
1003
{
@@ -979,6 +1008,12 @@ internal sealed class {{escapedClassName}}WinRTTypeDetails : global::WinRT.IWinR
979
1008
addSource ( $ "{ prefix } { escapedClassName } .WinRTVtable.g.cs", source . ToString ( ) ) ;
980
1009
}
981
1010
}
1011
+
1012
+ if ( vtableClassesSource . Length != 0 )
1013
+ {
1014
+ vtableClassesSource . AppendLine ( "}" ) ;
1015
+ addSource ( $ "WinRTCCWVtable.g.cs", vtableClassesSource . ToString ( ) ) ;
1016
+ }
982
1017
}
983
1018
984
1019
private static void GenerateCCWForGenericInstantiation (
@@ -1444,12 +1479,37 @@ private static ComWrappers.ComInterfaceEntry[] LookupVtableEntries(Type type)
1444
1479
""" ) ;
1445
1480
}
1446
1481
1482
+ // We gather all the class names that have the same vtable and generate it
1483
+ // as part of one if to reduce generated code.
1484
+ var vtableEntryToClassNameList = new Dictionary < VtableEntry , List < string > > ( ) ;
1447
1485
foreach ( var vtableAttribute in value . vtableAttributes . ToImmutableHashSet ( ) )
1448
1486
{
1487
+ VtableEntry entry = new ( vtableAttribute . Interfaces , vtableAttribute . GenericInterfaces , vtableAttribute . IsDelegate ) ;
1488
+ if ( ! vtableEntryToClassNameList . TryGetValue ( entry , out var classNameList ) )
1489
+ {
1490
+ classNameList = new List < string > ( ) ;
1491
+ vtableEntryToClassNameList . Add ( entry , classNameList ) ;
1492
+ }
1493
+ classNameList . Add ( vtableAttribute . VtableLookupClassName ) ;
1494
+ }
1495
+
1496
+ foreach ( var vtableEntry in vtableEntryToClassNameList )
1497
+ {
1498
+ source . AppendLine ( $$ """
1499
+ if (typeName == "{{ vtableEntry . Value [ 0 ] }} "
1500
+ """ ) ;
1501
+
1502
+ for ( var i = 1 ; i < vtableEntry . Value . Count ; i ++ )
1503
+ {
1504
+ source . AppendLine ( $$ """
1505
+ || typeName == "{{ vtableEntry . Value [ i ] }} "
1506
+ """ ) ;
1507
+ }
1508
+
1449
1509
source . AppendLine ( $$ """
1450
- if (typeName == " {{ vtableAttribute . VtableLookupClassName }} " )
1510
+ )
1451
1511
{
1452
- {{ GenerateVtableEntry ( vtableAttribute , value . context . escapedAssemblyName ) }}
1512
+ {{ GenerateVtableEntry ( vtableEntry . Key , value . context . escapedAssemblyName ) }}
1453
1513
}
1454
1514
""" ) ;
1455
1515
}
@@ -1469,12 +1529,34 @@ private static string LookupRuntimeClassName(Type type)
1469
1529
string typeName = type.ToString();
1470
1530
""" ) ;
1471
1531
1532
+ var runtimeClassNameToClassNameList = new Dictionary < string , List < string > > ( ) ;
1472
1533
foreach ( var vtableAttribute in value . vtableAttributes . ToImmutableHashSet ( ) . Where ( static v => ! string . IsNullOrEmpty ( v . RuntimeClassName ) ) )
1534
+ {
1535
+ if ( ! runtimeClassNameToClassNameList . TryGetValue ( vtableAttribute . RuntimeClassName , out var classNameList ) )
1536
+ {
1537
+ classNameList = new List < string > ( ) ;
1538
+ runtimeClassNameToClassNameList . Add ( vtableAttribute . RuntimeClassName , classNameList ) ;
1539
+ }
1540
+ classNameList . Add ( vtableAttribute . VtableLookupClassName ) ;
1541
+ }
1542
+
1543
+ foreach ( var entry in runtimeClassNameToClassNameList )
1473
1544
{
1474
1545
source . AppendLine ( $$ """
1475
- if (typeName == "{{ vtableAttribute . VtableLookupClassName }} ")
1546
+ if (typeName == "{{ entry . Value [ 0 ] }} "
1547
+ """ ) ;
1548
+
1549
+ for ( var i = 1 ; i < entry . Value . Count ; i ++ )
1550
+ {
1551
+ source . AppendLine ( $$ """
1552
+ || typeName == "{{ entry . Value [ i ] }} "
1553
+ """ ) ;
1554
+ }
1555
+
1556
+ source . AppendLine ( $$ """
1557
+ )
1476
1558
{
1477
- return "{{ vtableAttribute . RuntimeClassName }} ";
1559
+ return "{{ entry . Key }} ";
1478
1560
}
1479
1561
""" ) ;
1480
1562
}
@@ -1630,6 +1712,11 @@ internal sealed record VtableAttribute(
1630
1712
bool IsPublic ,
1631
1713
string RuntimeClassName = default ) ;
1632
1714
1715
+ sealed record VtableEntry (
1716
+ EquatableArray < string > Interfaces ,
1717
+ EquatableArray < GenericInterface > GenericInterfaces ,
1718
+ bool IsDelegate ) ;
1719
+
1633
1720
internal readonly record struct BindableCustomProperty (
1634
1721
string Name ,
1635
1722
string Type ,
0 commit comments