@@ -1655,23 +1655,31 @@ public void GenerateVTable(Class @class)
1655
1655
if ( wrappedEntries . Count == 0 )
1656
1656
return ;
1657
1657
1658
+ bool generateNativeToManaged = Options . GenerateNativeToManagedFor ( @class ) ;
1659
+
1658
1660
PushBlock ( BlockKind . Region ) ;
1659
1661
WriteLine ( "#region Virtual table interop" ) ;
1660
1662
NewLine ( ) ;
1661
1663
1662
- // Generate a delegate type for each method.
1663
- foreach ( var method in wrappedEntries . Select ( e => e . Method ) . Where ( m => ! m . Ignore ) )
1664
- GenerateVTableMethodDelegates ( containingClass , method . Namespace . IsDependent ?
1665
- ( Method ) method . InstantiatedFrom : method ) ;
1666
-
1667
- var hasVirtualDtor = wrappedEntries . Any ( e => e . Method . IsDestructor ) ;
1668
1664
bool hasDynamicBase = @class . NeedsBase && @class . BaseClass . IsDynamic ;
1669
1665
var originalTableClass = @class . IsDependent ? @class . Specializations [ 0 ] : @class ;
1670
- var destructorOnly = "destructorOnly" ;
1671
1666
1672
- using ( WriteBlock ( $ "internal static{ ( hasDynamicBase ? " new" : string . Empty ) } class VTableLoader") )
1667
+ // vtable hooks don't work without a NativeToManaged map, because we can't look up the managed
1668
+ // instance from a native pointer without the map, so don't generate them here.
1669
+ // this also means we can't inherit from this class and override virtual methods in C#
1670
+ if ( generateNativeToManaged )
1673
1671
{
1674
- WriteLines ( $@ "
1672
+ // Generate a delegate type for each method.
1673
+ foreach ( var method in wrappedEntries . Select ( e => e . Method ) . Where ( m => ! m . Ignore ) )
1674
+ GenerateVTableMethodDelegates ( containingClass , method . Namespace . IsDependent ?
1675
+ ( Method ) method . InstantiatedFrom : method ) ;
1676
+
1677
+ var hasVirtualDtor = wrappedEntries . Any ( e => e . Method . IsDestructor ) ;
1678
+ var destructorOnly = "destructorOnly" ;
1679
+
1680
+ using ( WriteBlock ( $ "internal static{ ( hasDynamicBase ? " new" : string . Empty ) } class VTableLoader") )
1681
+ {
1682
+ WriteLines ( $@ "
1675
1683
private static volatile bool initialized;
1676
1684
private static readonly IntPtr*[] ManagedVTables = new IntPtr*[{ @class . Layout . VTablePointers . Count } ];{ ( hasVirtualDtor ? $@ "
1677
1685
private static readonly IntPtr*[] ManagedVTablesDtorOnly = new IntPtr*[{ @class . Layout . VTablePointers . Count } ];" : "" ) }
@@ -1681,80 +1689,81 @@ public void GenerateVTable(Class @class)
1681
1689
SafeHandles = new global::System.Collections.Generic.List<CppSharp.Runtime.SafeUnmanagedMemoryHandle>();
1682
1690
" , trimIndentation : true ) ;
1683
1691
1684
- using ( WriteBlock ( $ "static VTableLoader()") )
1685
- {
1686
- foreach ( var entry in wrappedEntries . Distinct ( ) . Where ( e => ! e . Method . Ignore ) )
1692
+ using ( WriteBlock ( $ "static VTableLoader()") )
1687
1693
{
1688
- var name = GetVTableMethodDelegateName ( entry . Method ) ;
1689
- WriteLine ( $ "{ name + "Instance" } += { name } Hook;") ;
1690
- }
1691
- for ( var i = 0 ; i < wrappedEntries . Count ; ++ i )
1692
- {
1693
- var entry = wrappedEntries [ i ] ;
1694
- if ( ! entry . Method . Ignore )
1694
+ foreach ( var entry in wrappedEntries . Distinct ( ) . Where ( e => ! e . Method . Ignore ) )
1695
1695
{
1696
1696
var name = GetVTableMethodDelegateName ( entry . Method ) ;
1697
- WriteLine ( $ "Thunks[{ i } ] = Marshal.GetFunctionPointerForDelegate({ name + "Instance" } );") ;
1697
+ WriteLine ( $ "{ name + "Instance" } += { name } Hook;") ;
1698
+ }
1699
+ for ( var i = 0 ; i < wrappedEntries . Count ; ++ i )
1700
+ {
1701
+ var entry = wrappedEntries [ i ] ;
1702
+ if ( ! entry . Method . Ignore )
1703
+ {
1704
+ var name = GetVTableMethodDelegateName ( entry . Method ) ;
1705
+ WriteLine ( $ "Thunks[{ i } ] = Marshal.GetFunctionPointerForDelegate({ name + "Instance" } );") ;
1706
+ }
1698
1707
}
1699
1708
}
1700
- }
1701
- NewLine ( ) ;
1709
+ NewLine ( ) ;
1702
1710
1703
- using ( WriteBlock ( $ "public static CppSharp.Runtime.VTables SetupVTables(IntPtr instance, bool { destructorOnly } = false)") )
1704
- {
1705
- WriteLine ( $ "if (!initialized)") ;
1711
+ using ( WriteBlock ( $ "public static CppSharp.Runtime.VTables SetupVTables(IntPtr instance, bool { destructorOnly } = false)") )
1706
1712
{
1707
- WriteOpenBraceAndIndent ( ) ;
1708
- WriteLine ( $ "lock (ManagedVTables)") ;
1709
- WriteOpenBraceAndIndent ( ) ;
1710
1713
WriteLine ( $ "if (!initialized)") ;
1711
1714
{
1712
1715
WriteOpenBraceAndIndent ( ) ;
1713
- WriteLine ( $ "initialized = true;") ;
1714
- WriteLine ( $ "VTables.Tables = { ( $ "new IntPtr[] {{ { string . Join ( ", " , originalTableClass . Layout . VTablePointers . Select ( x => $ "*(IntPtr*)(instance + { x . Offset } )") ) } }}") } ;") ;
1715
- WriteLine ( $ "VTables.Methods = new Delegate[{ originalTableClass . Layout . VTablePointers . Count } ][];") ;
1716
-
1717
- if ( hasVirtualDtor )
1718
- AllocateNewVTables ( @class , wrappedEntries , destructorOnly : true , "ManagedVTablesDtorOnly" ) ;
1719
-
1720
- AllocateNewVTables ( @class , wrappedEntries , destructorOnly : false , "ManagedVTables" ) ;
1721
-
1722
- if ( ! hasVirtualDtor )
1716
+ WriteLine ( $ "lock (ManagedVTables)") ;
1717
+ WriteOpenBraceAndIndent ( ) ;
1718
+ WriteLine ( $ "if (!initialized)") ;
1723
1719
{
1724
- WriteLine ( $ "if ({ destructorOnly } )") ;
1725
- WriteLineIndent ( "return VTables;" ) ;
1720
+ WriteOpenBraceAndIndent ( ) ;
1721
+ WriteLine ( $ "initialized = true;") ;
1722
+ WriteLine ( $ "VTables.Tables = { ( $ "new IntPtr[] {{ { string . Join ( ", " , originalTableClass . Layout . VTablePointers . Select ( x => $ "*(IntPtr*)(instance + { x . Offset } )") ) } }}") } ;") ;
1723
+ WriteLine ( $ "VTables.Methods = new Delegate[{ originalTableClass . Layout . VTablePointers . Count } ][];") ;
1724
+
1725
+ if ( hasVirtualDtor )
1726
+ AllocateNewVTables ( @class , wrappedEntries , destructorOnly : true , "ManagedVTablesDtorOnly" ) ;
1727
+
1728
+ AllocateNewVTables ( @class , wrappedEntries , destructorOnly : false , "ManagedVTables" ) ;
1729
+
1730
+ if ( ! hasVirtualDtor )
1731
+ {
1732
+ WriteLine ( $ "if ({ destructorOnly } )") ;
1733
+ WriteLineIndent ( "return VTables;" ) ;
1734
+ }
1735
+ UnindentAndWriteCloseBrace ( ) ;
1726
1736
}
1727
1737
UnindentAndWriteCloseBrace ( ) ;
1738
+ UnindentAndWriteCloseBrace ( ) ;
1728
1739
}
1729
- UnindentAndWriteCloseBrace ( ) ;
1730
- UnindentAndWriteCloseBrace ( ) ;
1731
- }
1732
- NewLine ( ) ;
1740
+ NewLine ( ) ;
1733
1741
1734
- if ( hasVirtualDtor )
1735
- {
1736
- WriteLine ( $ "if ({ destructorOnly } )") ;
1742
+ if ( hasVirtualDtor )
1737
1743
{
1738
- WriteOpenBraceAndIndent ( ) ;
1739
- AssignNewVTableEntries ( @class , "ManagedVTablesDtorOnly" ) ;
1740
- UnindentAndWriteCloseBrace ( ) ;
1744
+ WriteLine ( $ "if ({ destructorOnly } )") ;
1745
+ {
1746
+ WriteOpenBraceAndIndent ( ) ;
1747
+ AssignNewVTableEntries ( @class , "ManagedVTablesDtorOnly" ) ;
1748
+ UnindentAndWriteCloseBrace ( ) ;
1749
+ }
1750
+ WriteLine ( "else" ) ;
1751
+ {
1752
+ WriteOpenBraceAndIndent ( ) ;
1753
+ AssignNewVTableEntries ( @class , "ManagedVTables" ) ;
1754
+ UnindentAndWriteCloseBrace ( ) ;
1755
+ }
1741
1756
}
1742
- WriteLine ( " else" ) ;
1757
+ else
1743
1758
{
1744
- WriteOpenBraceAndIndent ( ) ;
1745
1759
AssignNewVTableEntries ( @class , "ManagedVTables" ) ;
1746
- UnindentAndWriteCloseBrace ( ) ;
1747
1760
}
1748
- }
1749
- else
1750
- {
1751
- AssignNewVTableEntries ( @class , "ManagedVTables" ) ;
1752
- }
1753
1761
1754
- WriteLine ( "return VTables;" ) ;
1762
+ WriteLine ( "return VTables;" ) ;
1763
+ }
1755
1764
}
1765
+ NewLine ( ) ;
1756
1766
}
1757
- NewLine ( ) ;
1758
1767
1759
1768
if ( ! hasDynamicBase )
1760
1769
WriteLine ( "protected CppSharp.Runtime.VTables __vtables;" ) ;
@@ -1775,9 +1784,13 @@ public void GenerateVTable(Class @class)
1775
1784
1776
1785
using ( WriteBlock ( $ "internal { ( hasDynamicBase ? "override" : "virtual" ) } void SetupVTables(bool destructorOnly = false)") )
1777
1786
{
1778
- WriteLines ( $@ "
1779
- if (__VTables.IsTransient)
1780
- __VTables = VTableLoader.SetupVTables(__Instance, destructorOnly);" , trimIndentation : true ) ;
1787
+ // same reason as above, we can't hook vtable without ManagedToNative map
1788
+ if ( generateNativeToManaged )
1789
+ {
1790
+ WriteLines ( $@ "
1791
+ if (__VTables.IsTransient)
1792
+ __VTables = VTableLoader.SetupVTables(__Instance, destructorOnly);" , trimIndentation : true ) ;
1793
+ }
1781
1794
}
1782
1795
1783
1796
WriteLine ( "#endregion" ) ;
@@ -2399,22 +2412,16 @@ private void GenerateNativeConstructor(Class @class)
2399
2412
NewLine ( ) ;
2400
2413
}
2401
2414
2402
- if ( HasVirtualTables ( @class ) )
2415
+ // __GetInstance doesn't work without a ManagedToNativeMap, so don't generate it
2416
+ if ( HasVirtualTables ( @class ) && generateNativeToManaged )
2403
2417
{
2404
2418
@new = @class . HasBase && HasVirtualTables ( @class . Bases . First ( ) . Class ) ;
2405
2419
2406
2420
WriteLines ( $@ "
2407
2421
internal static{ ( @new ? " new" : string . Empty ) } { printedClass } __GetInstance({ TypePrinter . IntPtrType } native)
2408
- {{" ) ;
2409
-
2410
- if ( generateNativeToManaged )
2411
- {
2412
- WriteLines ( $@ "
2422
+ {{
2413
2423
if (!{ Helpers . TryGetNativeToManagedMappingIdentifier } (native, out var managed))
2414
- throw new global::System.Exception(""No managed instance was found"");" ) ;
2415
- }
2416
-
2417
- WriteLines ( $@ "
2424
+ throw new global::System.Exception(""No managed instance was found"");
2418
2425
var result = ({ printedClass } )managed;
2419
2426
if (result.{ Helpers . OwnsNativeInstanceIdentifier } )
2420
2427
result.SetupVTables();
@@ -2950,12 +2957,24 @@ private void GenerateOperator(Method method, QualifiedType returnType)
2950
2957
2951
2958
private void GenerateClassConstructor ( Method method , Class @class )
2952
2959
{
2960
+ var generateNativeToManaged = Options . GenerateNativeToManagedFor ( @class ) ;
2961
+ if ( ! generateNativeToManaged )
2962
+ {
2963
+ // if we don't have a NativeToManaged map, we can't do vtable hooking, because we can't
2964
+ // fetch the managed class from the native pointer. vtable hooking is required to allow C++
2965
+ // code to call virtual methods defined on a C++ class but overwritten in a C# class.
2966
+ // todo: throwing an exception at runtime is ugly, we should seal the class instead
2967
+ var typeFullName = TypePrinter . VisitClassDecl ( @class ) . Type . Replace ( "global::" , string . Empty ) ;
2968
+ WriteLine ( $@ "if (GetType().FullName != ""{ typeFullName } "")") ;
2969
+ WriteLineIndent ( $@ "throw new Exception(""{ typeFullName } : Can't inherit from classes with disabled NativeToManaged map"");") ;
2970
+ }
2971
+
2953
2972
var @internal = TypePrinter . PrintNative (
2954
2973
@class . IsAbstractImpl ? @class . BaseClass : @class ) ;
2955
2974
WriteLine ( $ "{ Helpers . InstanceIdentifier } = Marshal.AllocHGlobal(sizeof({ @internal } ));") ;
2956
2975
WriteLine ( $ "{ Helpers . OwnsNativeInstanceIdentifier } = true;") ;
2957
2976
2958
- if ( Options . GenerateNativeToManagedFor ( @class ) )
2977
+ if ( generateNativeToManaged )
2959
2978
WriteLine ( $ "{ Helpers . RecordNativeToManagedMappingIdentifier } ({ Helpers . InstanceIdentifier } , this);") ;
2960
2979
2961
2980
if ( method . IsCopyConstructor )
0 commit comments