Browse Source

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
pull/1698/head
Fabio Anderegg 3 years ago committed by GitHub
parent
commit
eab7a0cdde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 45
      src/Generator/Generators/CSharp/CSharpSources.cs

45
src/Generator/Generators/CSharp/CSharpSources.cs

@ -1655,18 +1655,26 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat
if (wrappedEntries.Count == 0) if (wrappedEntries.Count == 0)
return; return;
bool generateNativeToManaged = Options.GenerateNativeToManagedFor(@class);
PushBlock(BlockKind.Region); PushBlock(BlockKind.Region);
WriteLine("#region Virtual table interop"); WriteLine("#region Virtual table interop");
NewLine(); NewLine();
bool hasDynamicBase = @class.NeedsBase && @class.BaseClass.IsDynamic;
var originalTableClass = @class.IsDependent ? @class.Specializations[0] : @class;
// vtable hooks don't work without a NativeToManaged map, because we can't look up the managed
// instance from a native pointer without the map, so don't generate them here.
// this also means we can't inherit from this class and override virtual methods in C#
if (generateNativeToManaged)
{
// Generate a delegate type for each method. // Generate a delegate type for each method.
foreach (var method in wrappedEntries.Select(e => e.Method).Where(m => !m.Ignore)) foreach (var method in wrappedEntries.Select(e => e.Method).Where(m => !m.Ignore))
GenerateVTableMethodDelegates(containingClass, method.Namespace.IsDependent ? GenerateVTableMethodDelegates(containingClass, method.Namespace.IsDependent ?
(Method)method.InstantiatedFrom : method); (Method)method.InstantiatedFrom : method);
var hasVirtualDtor = wrappedEntries.Any(e => e.Method.IsDestructor); var hasVirtualDtor = wrappedEntries.Any(e => e.Method.IsDestructor);
bool hasDynamicBase = @class.NeedsBase && @class.BaseClass.IsDynamic;
var originalTableClass = @class.IsDependent ? @class.Specializations[0] : @class;
var destructorOnly = "destructorOnly"; var destructorOnly = "destructorOnly";
using (WriteBlock($"internal static{(hasDynamicBase ? " new" : string.Empty)} class VTableLoader")) using (WriteBlock($"internal static{(hasDynamicBase ? " new" : string.Empty)} class VTableLoader"))
@ -1755,6 +1763,7 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat
} }
} }
NewLine(); NewLine();
}
if (!hasDynamicBase) if (!hasDynamicBase)
WriteLine("protected CppSharp.Runtime.VTables __vtables;"); WriteLine("protected CppSharp.Runtime.VTables __vtables;");
@ -1774,11 +1783,15 @@ internal static bool {Helpers.TryGetNativeToManagedMappingIdentifier}(IntPtr nat
} }
using (WriteBlock($"internal {(hasDynamicBase ? "override" : "virtual")} void SetupVTables(bool destructorOnly = false)")) using (WriteBlock($"internal {(hasDynamicBase ? "override" : "virtual")} void SetupVTables(bool destructorOnly = false)"))
{
// same reason as above, we can't hook vtable without ManagedToNative map
if (generateNativeToManaged)
{ {
WriteLines($@" WriteLines($@"
if (__VTables.IsTransient) if (__VTables.IsTransient)
__VTables = VTableLoader.SetupVTables(__Instance, destructorOnly);", trimIndentation: true); __VTables = VTableLoader.SetupVTables(__Instance, destructorOnly);", trimIndentation: true);
} }
}
WriteLine("#endregion"); WriteLine("#endregion");
PopBlock(NewLineKind.BeforeNextBlock); PopBlock(NewLineKind.BeforeNextBlock);
@ -2399,22 +2412,16 @@ internal static{(@new ? " new" : string.Empty)} {printedClass} __GetOrCreateInst
NewLine(); NewLine();
} }
if (HasVirtualTables(@class)) // __GetInstance doesn't work without a ManagedToNativeMap, so don't generate it
if (HasVirtualTables(@class) && generateNativeToManaged)
{ {
@new = @class.HasBase && HasVirtualTables(@class.Bases.First().Class); @new = @class.HasBase && HasVirtualTables(@class.Bases.First().Class);
WriteLines($@" WriteLines($@"
internal static{(@new ? " new" : string.Empty)} {printedClass} __GetInstance({TypePrinter.IntPtrType} native) internal static{(@new ? " new" : string.Empty)} {printedClass} __GetInstance({TypePrinter.IntPtrType} native)
{{"); {{
if (generateNativeToManaged)
{
WriteLines($@"
if (!{Helpers.TryGetNativeToManagedMappingIdentifier}(native, out var managed)) if (!{Helpers.TryGetNativeToManagedMappingIdentifier}(native, out var managed))
throw new global::System.Exception(""No managed instance was found"");"); throw new global::System.Exception(""No managed instance was found"");
}
WriteLines($@"
var result = ({printedClass})managed; var result = ({printedClass})managed;
if (result.{Helpers.OwnsNativeInstanceIdentifier}) if (result.{Helpers.OwnsNativeInstanceIdentifier})
result.SetupVTables(); result.SetupVTables();
@ -2950,12 +2957,24 @@ internal static{(@new ? " new" : string.Empty)} {printedClass} __GetInstance({Ty
private void GenerateClassConstructor(Method method, Class @class) private void GenerateClassConstructor(Method method, Class @class)
{ {
var generateNativeToManaged = Options.GenerateNativeToManagedFor(@class);
if (!generateNativeToManaged)
{
// if we don't have a NativeToManaged map, we can't do vtable hooking, because we can't
// fetch the managed class from the native pointer. vtable hooking is required to allow C++
// code to call virtual methods defined on a C++ class but overwritten in a C# class.
// todo: throwing an exception at runtime is ugly, we should seal the class instead
var typeFullName = TypePrinter.VisitClassDecl(@class).Type.Replace("global::", string.Empty);
WriteLine($@"if (GetType().FullName != ""{typeFullName}"")");
WriteLineIndent($@"throw new Exception(""{typeFullName}: Can't inherit from classes with disabled NativeToManaged map"");");
}
var @internal = TypePrinter.PrintNative( var @internal = TypePrinter.PrintNative(
@class.IsAbstractImpl ? @class.BaseClass : @class); @class.IsAbstractImpl ? @class.BaseClass : @class);
WriteLine($"{Helpers.InstanceIdentifier} = Marshal.AllocHGlobal(sizeof({@internal}));"); WriteLine($"{Helpers.InstanceIdentifier} = Marshal.AllocHGlobal(sizeof({@internal}));");
WriteLine($"{Helpers.OwnsNativeInstanceIdentifier} = true;"); WriteLine($"{Helpers.OwnsNativeInstanceIdentifier} = true;");
if (Options.GenerateNativeToManagedFor(@class)) if (generateNativeToManaged)
WriteLine($"{Helpers.RecordNativeToManagedMappingIdentifier}({Helpers.InstanceIdentifier}, this);"); WriteLine($"{Helpers.RecordNativeToManagedMappingIdentifier}({Helpers.InstanceIdentifier}, this);");
if (method.IsCopyConstructor) if (method.IsCopyConstructor)

Loading…
Cancel
Save