Skip to content

Commit eab7a0c

Browse files
author
Fabio Anderegg
authored
Fix disabling NativeToManaged map for classes with vtable (#1696)
* dont generate GetInstance on classes with disabled NativeToManaged map * disable vtable hooking for classes with disabled ManagedToNative * throw exception when trying to inherit from class with disabled NativeToManaged map
1 parent f2e26ec commit eab7a0c

File tree

1 file changed

+93
-74
lines changed

1 file changed

+93
-74
lines changed

src/Generator/Generators/CSharp/CSharpSources.cs

Lines changed: 93 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -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($@"
24072421
internal 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

Comments
 (0)