@@ -1655,23 +1655,31 @@ public void GenerateVTable(Class @class)
16551655 if ( wrappedEntries . Count == 0 )
16561656 return ;
16571657
1658+ bool generateNativeToManaged = Options . GenerateNativeToManagedFor ( @class ) ;
1659+
16581660 PushBlock ( BlockKind . Region ) ;
16591661 WriteLine ( "#region Virtual table interop" ) ;
16601662 NewLine ( ) ;
16611663
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 ) ;
16681664 bool hasDynamicBase = @class . NeedsBase && @class . BaseClass . IsDynamic ;
16691665 var originalTableClass = @class . IsDependent ? @class . Specializations [ 0 ] : @class ;
1670- var destructorOnly = "destructorOnly" ;
16711666
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 )
16731671 {
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 ( $@ "
16751683 private static volatile bool initialized;
16761684 private static readonly IntPtr*[] ManagedVTables = new IntPtr*[{ @class . Layout . VTablePointers . Count } ];{ ( hasVirtualDtor ? $@ "
16771685 private static readonly IntPtr*[] ManagedVTablesDtorOnly = new IntPtr*[{ @class . Layout . VTablePointers . Count } ];" : "" ) }
@@ -1681,80 +1689,81 @@ public void GenerateVTable(Class @class)
16811689 SafeHandles = new global::System.Collections.Generic.List<CppSharp.Runtime.SafeUnmanagedMemoryHandle>();
16821690 " , trimIndentation : true ) ;
16831691
1684- using ( WriteBlock ( $ "static VTableLoader()") )
1685- {
1686- foreach ( var entry in wrappedEntries . Distinct ( ) . Where ( e => ! e . Method . Ignore ) )
1692+ using ( WriteBlock ( $ "static VTableLoader()") )
16871693 {
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 ) )
16951695 {
16961696 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+ }
16981707 }
16991708 }
1700- }
1701- NewLine ( ) ;
1709+ NewLine ( ) ;
17021710
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)") )
17061712 {
1707- WriteOpenBraceAndIndent ( ) ;
1708- WriteLine ( $ "lock (ManagedVTables)") ;
1709- WriteOpenBraceAndIndent ( ) ;
17101713 WriteLine ( $ "if (!initialized)") ;
17111714 {
17121715 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)") ;
17231719 {
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 ( ) ;
17261736 }
17271737 UnindentAndWriteCloseBrace ( ) ;
1738+ UnindentAndWriteCloseBrace ( ) ;
17281739 }
1729- UnindentAndWriteCloseBrace ( ) ;
1730- UnindentAndWriteCloseBrace ( ) ;
1731- }
1732- NewLine ( ) ;
1740+ NewLine ( ) ;
17331741
1734- if ( hasVirtualDtor )
1735- {
1736- WriteLine ( $ "if ({ destructorOnly } )") ;
1742+ if ( hasVirtualDtor )
17371743 {
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+ }
17411756 }
1742- WriteLine ( " else" ) ;
1757+ else
17431758 {
1744- WriteOpenBraceAndIndent ( ) ;
17451759 AssignNewVTableEntries ( @class , "ManagedVTables" ) ;
1746- UnindentAndWriteCloseBrace ( ) ;
17471760 }
1748- }
1749- else
1750- {
1751- AssignNewVTableEntries ( @class , "ManagedVTables" ) ;
1752- }
17531761
1754- WriteLine ( "return VTables;" ) ;
1762+ WriteLine ( "return VTables;" ) ;
1763+ }
17551764 }
1765+ NewLine ( ) ;
17561766 }
1757- NewLine ( ) ;
17581767
17591768 if ( ! hasDynamicBase )
17601769 WriteLine ( "protected CppSharp.Runtime.VTables __vtables;" ) ;
@@ -1775,9 +1784,13 @@ public void GenerateVTable(Class @class)
17751784
17761785 using ( WriteBlock ( $ "internal { ( hasDynamicBase ? "override" : "virtual" ) } void SetupVTables(bool destructorOnly = false)") )
17771786 {
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+ }
17811794 }
17821795
17831796 WriteLine ( "#endregion" ) ;
@@ -2399,22 +2412,16 @@ private void GenerateNativeConstructor(Class @class)
23992412 NewLine ( ) ;
24002413 }
24012414
2402- if ( HasVirtualTables ( @class ) )
2415+ // __GetInstance doesn't work without a ManagedToNativeMap, so don't generate it
2416+ if ( HasVirtualTables ( @class ) && generateNativeToManaged )
24032417 {
24042418 @new = @class . HasBase && HasVirtualTables ( @class . Bases . First ( ) . Class ) ;
24052419
24062420 WriteLines ( $@ "
24072421internal static{ ( @new ? " new" : string . Empty ) } { printedClass } __GetInstance({ TypePrinter . IntPtrType } native)
2408- {{" ) ;
2409-
2410- if ( generateNativeToManaged )
2411- {
2412- WriteLines ( $@ "
2422+ {{
24132423 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"");
24182425 var result = ({ printedClass } )managed;
24192426 if (result.{ Helpers . OwnsNativeInstanceIdentifier } )
24202427 result.SetupVTables();
@@ -2950,12 +2957,24 @@ private void GenerateOperator(Method method, QualifiedType returnType)
29502957
29512958 private void GenerateClassConstructor ( Method method , Class @class )
29522959 {
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+
29532972 var @internal = TypePrinter . PrintNative (
29542973 @class . IsAbstractImpl ? @class . BaseClass : @class ) ;
29552974 WriteLine ( $ "{ Helpers . InstanceIdentifier } = Marshal.AllocHGlobal(sizeof({ @internal } ));") ;
29562975 WriteLine ( $ "{ Helpers . OwnsNativeInstanceIdentifier } = true;") ;
29572976
2958- if ( Options . GenerateNativeToManagedFor ( @class ) )
2977+ if ( generateNativeToManaged )
29592978 WriteLine ( $ "{ Helpers . RecordNativeToManagedMappingIdentifier } ({ Helpers . InstanceIdentifier } , this);") ;
29602979
29612980 if ( method . IsCopyConstructor )
0 commit comments