Browse Source

Create VTables class to cache delegates pointing to native virtual methods (#1468)

pull/1472/head
josetr 5 years ago committed by GitHub
parent
commit
52140cb78b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 220
      src/Generator/Generators/CSharp/CSharpSources.cs
  2. 34
      src/Generator/Utils/BlockGenerator.cs
  3. 6
      src/Runtime/MarshalUtil.cs
  4. 48
      src/Runtime/VTables.cs

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

@ -1580,98 +1580,127 @@ namespace CppSharp.Generators.CSharp @@ -1580,98 +1580,127 @@ namespace CppSharp.Generators.CSharp
GenerateVTableMethodDelegates(containingClass, method.Namespace.IsDependent ?
(Method) method.InstantiatedFrom : method);
WriteLine("private static void*[] __ManagedVTables;");
if (wrappedEntries.Any(e => e.Method.IsDestructor))
WriteLine("private static void*[] __ManagedVTablesDtorOnly;");
WriteLine("private static void*[] _Thunks;");
WriteLine("private static void*[] __originalVTables;");
var hasVirtualDtor = wrappedEntries.Any(e => e.Method.IsDestructor);
bool hasDynamicBase = @class.NeedsBase && @class.BaseClass.IsDynamic;
Write($@"protected internal {(hasDynamicBase ? "override" : "virtual"
)} void*[] __OriginalVTables => __originalVTables ?? ");
SaveOriginalVTablePointers(@class);
NewLine();
var originalTableClass = @class.IsDependent ? @class.Specializations[0] : @class;
var originalTablePointers = $"new IntPtr[] {{ {string.Join(", ", originalTableClass.Layout.VTablePointers.Select(v => $"*(IntPtr*)({Helpers.InstanceIdentifier} + {v.Offset})"))} }}";
var destructorOnly = "destructorOnly";
GenerateVTableClassSetup(@class, wrappedEntries);
WriteLine("private static readonly global::System.Collections.Generic.List<" +
"global::CppSharp.Runtime.SafeUnmanagedMemoryHandle> __handleManagedVTables" +
" = new global::System.Collections.Generic.List<" +
"global::CppSharp.Runtime.SafeUnmanagedMemoryHandle>();");
NewLine();
WriteLine("internal static class VTableLoader");
{
WriteOpenBraceAndIndent();
WriteLine("private static bool initialized;");
WriteLine("private static IntPtr[] ManagedVTables;");
WriteLine("private static IntPtr[] Thunks;");
WriteLine("public static CppSharp.Runtime.VTables VTables;");
if (hasVirtualDtor)
WriteLine($"private static IntPtr[] ManagedVTablesDtorOnly;");
WriteLine("private static readonly global::System.Collections.Generic.List<global::CppSharp.Runtime.SafeUnmanagedMemoryHandle>");
WriteLineIndent("HandleManagedVTables = new global::System.Collections.Generic.List<global::CppSharp.Runtime.SafeUnmanagedMemoryHandle>();");
WriteLine("#endregion");
PopBlock(NewLineKind.BeforeNextBlock);
}
WriteLine($"public static CppSharp.Runtime.VTables SetupVTables(IntPtr __Instance, bool {destructorOnly} = false)");
{
WriteOpenBraceAndIndent();
WriteLine("if (!initialized)");
{
WriteOpenBraceAndIndent();
WriteLine($"Thunks = new IntPtr[{wrappedEntries.Count}];");
WriteLine($"var methodDelegates = new Delegate[{originalTableClass.Layout.VTablePointers.Count}][];");
private void GenerateVTableClassSetup(Class @class, IList<VTableComponent> wrappedEntries)
{
const string destructorOnly = "destructorOnly";
WriteLine("private void SetupVTables(bool {0} = false)", destructorOnly);
WriteOpenBraceAndIndent();
var uniqueEntries = new HashSet<VTableComponent>();
var hasVirtualDtor = wrappedEntries.Any(e => e.Method.IsDestructor);
if (!hasVirtualDtor)
{
WriteLine("if ({0})", destructorOnly);
WriteLineIndent("return;");
}
for (var i = 0; i < wrappedEntries.Count; ++i)
{
var entry = wrappedEntries[i];
var name = GetVTableMethodDelegateName(entry.Method);
if (uniqueEntries.Add(entry))
WriteLine($"{name + "Instance"} += {name}Hook;");
WriteLine("if (_Thunks == null)");
WriteOpenBraceAndIndent();
WriteLine("_Thunks = new void*[{0}];", wrappedEntries.Count);
WriteLine($"Thunks[{i}] = Marshal.GetFunctionPointerForDelegate({name + "Instance"});");
}
var uniqueEntries = new HashSet<VTableComponent>();
NewLine();
WriteLine($"var originalVTables = {originalTablePointers};");
for (int i = 0; i < wrappedEntries.Count; i++)
{
var entry = wrappedEntries[i];
var method = entry.Method;
var name = GetVTableMethodDelegateName(method);
var instance = name + "Instance";
if (uniqueEntries.Add(entry))
WriteLine("{0} += {1}Hook;", instance, name);
WriteLine("_Thunks[{0}] = Marshal.GetFunctionPointerForDelegate({1}).ToPointer();",
i, instance);
}
if (hasVirtualDtor)
AllocateNewVTables(@class, wrappedEntries, destructorOnly: true, "ManagedVTablesDtorOnly");
NewLine();
AllocateNewVTables(@class, wrappedEntries, destructorOnly: false, "ManagedVTables");
if (hasVirtualDtor)
AllocateNewVTables(@class, wrappedEntries, destructorOnly: true);
WriteLine($"initialized = true;");
WriteLine($"VTables = new CppSharp.Runtime.VTables(originalVTables, methodDelegates);");
AllocateNewVTables(@class, wrappedEntries, destructorOnly: false);
if (!hasVirtualDtor)
{
WriteLine($"if ({destructorOnly})");
WriteLineIndent("return VTables;");
}
Write("__originalVTables = ");
SaveOriginalVTablePointers(@class);
UnindentAndWriteCloseBrace();
UnindentAndWriteCloseBrace();
}
NewLine();
NewLine();
if (hasVirtualDtor)
{
WriteLine($"if ({destructorOnly})");
WriteOpenBraceAndIndent();
AssignNewVTableEntries(@class, "__ManagedVTablesDtorOnly");
UnindentAndWriteCloseBrace();
WriteLine("else");
WriteOpenBraceAndIndent();
AssignNewVTableEntries(@class, "__ManagedVTables");
UnindentAndWriteCloseBrace();
}
else
{
AssignNewVTableEntries(@class, "__ManagedVTables");
}
if (hasVirtualDtor)
{
WriteLine($"if ({destructorOnly})");
{
WriteOpenBraceAndIndent();
AssignNewVTableEntries(@class, "ManagedVTablesDtorOnly");
UnindentAndWriteCloseBrace();
}
WriteLine("else");
{
WriteOpenBraceAndIndent();
AssignNewVTableEntries(@class, "ManagedVTables");
UnindentAndWriteCloseBrace();
}
}
else
{
AssignNewVTableEntries(@class, "ManagedVTables");
}
WriteLine("return VTables;");
UnindentAndWriteCloseBrace();
}
}
UnindentAndWriteCloseBrace();
NewLine();
if (!hasDynamicBase)
WriteLine("protected CppSharp.Runtime.VTables __vtables;");
WriteLines($@"
internal {(hasDynamicBase ? "override" : "virtual")} CppSharp.Runtime.VTables __VTables
{{
get {{
if (__vtables.IsEmpty)
__vtables = new CppSharp.Runtime.VTables({originalTablePointers});
return __vtables;
}}
set {{
__vtables = value;
}}
}}
internal {(hasDynamicBase ? "override" : "virtual")} void SetupVTables(bool destructorOnly = false)
{{
if (__VTables.IsTransient)
__VTables = VTableLoader.SetupVTables(__Instance, destructorOnly);
}}", trimIndentation : true);
WriteLine("#endregion");
PopBlock(NewLineKind.BeforeNextBlock);
}
private void AllocateNewVTables(Class @class, IList<VTableComponent> wrappedEntries,
bool destructorOnly)
bool destructorOnly, string table)
{
if (Context.ParserOptions.IsMicrosoftAbi)
AllocateNewVTablesMS(@class, wrappedEntries, destructorOnly);
AllocateNewVTablesMS(@class, wrappedEntries, destructorOnly, table);
else
AllocateNewVTablesItanium(@class, wrappedEntries, destructorOnly);
AllocateNewVTablesItanium(@class, wrappedEntries, destructorOnly, table);
NewLine();
}
@ -1681,24 +1710,14 @@ namespace CppSharp.Generators.CSharp @@ -1681,24 +1710,14 @@ namespace CppSharp.Generators.CSharp
for (int i = 0; i < @class.Layout.VTablePointers.Count; i++)
{
var offset = @class.Layout.VTablePointers[i].Offset;
WriteLine($"*(void**) ({Helpers.InstanceIdentifier} + {offset}) = {table}[{i}];");
WriteLine($"*(IntPtr*) ({Helpers.InstanceIdentifier} + {offset}) = {table}[{i}];");
}
}
private void SaveOriginalVTablePointers(Class @class)
{
if (@class.IsDependent)
@class = @class.Specializations[0];
Write($@"new void*[] {{ {string.Join(", ", @class.Layout.VTablePointers.Select(
v => $"*(void**) ({Helpers.InstanceIdentifier} + {v.Offset})"))} }};");
}
private void AllocateNewVTablesMS(Class @class, IList<VTableComponent> wrappedEntries,
bool destructorOnly)
bool destructorOnly, string table)
{
var managedVTables = destructorOnly ? "__ManagedVTablesDtorOnly" : "__ManagedVTables";
WriteLine($"{managedVTables} = new void*[{@class.Layout.VFTables.Count}];");
WriteLine($"{table} = new IntPtr[{@class.Layout.VFTables.Count}];");
for (int i = 0; i < @class.Layout.VFTables.Count; i++)
{
@ -1707,24 +1726,24 @@ namespace CppSharp.Generators.CSharp @@ -1707,24 +1726,24 @@ namespace CppSharp.Generators.CSharp
AllocateNewVTableEntries(vftable.Layout.Components, wrappedEntries,
@class.Layout.VTablePointers[i].Offset, i,
vftable.Layout.Components.Any(c => c.Kind == VTableComponentKind.RTTI) ? 1 : 0,
destructorOnly);
destructorOnly,
table);
}
}
private void AllocateNewVTablesItanium(Class @class, IList<VTableComponent> wrappedEntries,
bool destructorOnly)
bool destructorOnly, string table)
{
var managedVTables = destructorOnly ? "__ManagedVTablesDtorOnly" : "__ManagedVTables";
WriteLine($"{managedVTables} = new void*[1];");
WriteLine($"{table} = new IntPtr[1];");
AllocateNewVTableEntries(@class.Layout.Layout.Components,
wrappedEntries, @class.Layout.VTablePointers[0].Offset, 0,
VTables.ItaniumOffsetToTopAndRTTI, destructorOnly);
VTables.ItaniumOffsetToTopAndRTTI, destructorOnly, table);
}
private void AllocateNewVTableEntries(IList<VTableComponent> entries,
IList<VTableComponent> wrappedEntries, uint vptrOffset, int tableIndex,
int offsetRTTI, bool destructorOnly)
int offsetRTTI, bool destructorOnly, string table)
{
string suffix = (destructorOnly ? "_dtor" : string.Empty) +
(tableIndex == 0 ? string.Empty : tableIndex.ToString(CultureInfo.InvariantCulture));
@ -1734,13 +1753,15 @@ namespace CppSharp.Generators.CSharp @@ -1734,13 +1753,15 @@ namespace CppSharp.Generators.CSharp
// allocate memory for the new v-table
WriteLine($"var {vtptr} = Marshal.AllocHGlobal({size} * {pointerSize});");
// ensure it's automatically freed when no longer needed
WriteLine($"__handleManagedVTables.Add(new global::CppSharp.Runtime.SafeUnmanagedMemoryHandle({vtptr}, true));");
WriteLine($"HandleManagedVTables.Add(new global::CppSharp.Runtime.SafeUnmanagedMemoryHandle({vtptr}, true));");
string vfptr = $"vfptr{suffix}";
// obtain a pointer in the table to the start of virtual functions
WriteLine($"var {vfptr} = {vtptr} + {offsetRTTI} * {pointerSize};");
var managedVTables = destructorOnly ? "__ManagedVTablesDtorOnly" : "__ManagedVTables";
WriteLine($"{managedVTables}[{tableIndex}] = {vfptr}.ToPointer();");
WriteLine($"{table}[{tableIndex}] = {vfptr};");
if (!destructorOnly)
WriteLine($"methodDelegates[{tableIndex}] = new Delegate[{entries.Count}];");
// fill the newly allocated v-table
for (var i = 0; i < entries.Count; i++)
@ -1748,9 +1769,9 @@ namespace CppSharp.Generators.CSharp @@ -1748,9 +1769,9 @@ namespace CppSharp.Generators.CSharp
var entry = entries[i];
var offset = pointerSize * (i - offsetRTTI);
var nativeVftableEntry = $@"*(void**) (new IntPtr(*(void**) {
var nativeVftableEntry = $@"*(IntPtr*) ((*(IntPtr*) {
Helpers.InstanceIdentifier}) + {vptrOffset} + {offset})";
var managedVftableEntry = $"*(void**) ({vfptr} + {offset})";
var managedVftableEntry = $"*(IntPtr*) ({vfptr} + {offset})";
if ((entry.Kind == VTableComponentKind.FunctionPointer ||
entry.Kind == VTableComponentKind.DeletingDtorPointer) &&
@ -1758,7 +1779,7 @@ namespace CppSharp.Generators.CSharp @@ -1758,7 +1779,7 @@ namespace CppSharp.Generators.CSharp
(!destructorOnly || entry.Method.IsDestructor ||
Context.Options.ExplicitlyPatchedVirtualFunctions.Contains(entry.Method.QualifiedOriginalName)))
// patch with pointers to managed code where needed
WriteLine("{0} = _Thunks[{1}];", managedVftableEntry, wrappedEntries.IndexOf(entry));
WriteLine("{0} = Thunks[{1}];", managedVftableEntry, wrappedEntries.IndexOf(entry));
else
// and simply copy the rest
WriteLine("{0} = {1};", managedVftableEntry, nativeVftableEntry);
@ -2162,7 +2183,7 @@ namespace CppSharp.Generators.CSharp @@ -2162,7 +2183,7 @@ namespace CppSharp.Generators.CSharp
ClassLayout layout = (@class.IsDependent ? @class.Specializations[0] : @class).Layout;
for (var i = 0; i < layout.VTablePointers.Count; i++)
WriteLine($@"(({classInternal}*) {Helpers.InstanceIdentifier})->{
layout.VTablePointers[i].Name} = new {TypePrinter.IntPtrType}(__OriginalVTables[{i}]);");
layout.VTablePointers[i].Name} = __VTables.Tables[{i}];");
}
}
@ -2260,7 +2281,7 @@ internal static{(@new ? " new" : string.Empty)} {printedClass} __GetOrCreateInst @@ -2260,7 +2281,7 @@ internal static{(@new ? " new" : string.Empty)} {printedClass} __GetOrCreateInst
if (HasVirtualTables(@class))
{
@new = @class.HasBase;
@new = @class.HasBase && HasVirtualTables(@class.Bases.First().Class);
WriteLines($@"
internal static{(@new ? " new" : string.Empty)} {printedClass} __GetInstance({TypePrinter.IntPtrType} native)
@ -2731,12 +2752,11 @@ internal static{(@new ? " new" : string.Empty)} {printedClass} __GetInstance({Ty @@ -2731,12 +2752,11 @@ internal static{(@new ? " new" : string.Empty)} {printedClass} __GetInstance({Ty
vtableIndex = @class.Layout.VFTables.IndexOf(@class.Layout.VFTables.First(
v => v.Layout.Components.Any(c => c.Method == @virtual)));
var vtables = $@"{ (thisParam != null ? $"{thisParam.Name}." : string.Empty)}__OriginalVTables";
var @delegate = GetVTableMethodDelegateName(@virtual);
var delegateId = Generator.GeneratedIdentifier(@delegate);
WriteLine("var {0} = CppSharp.Runtime.MarshalUtil.GetDelegate<{1}>({2}, {3}, {4});",
delegateId, method.FunctionType.ToString(), vtables, vtableIndex, i);
int id = @class is ClassTemplateSpecialization specialization ? specialization.TemplatedDecl.Specializations.IndexOf(specialization) : 0;
WriteLine($"var {delegateId} = {(thisParam != null ? $"{thisParam.Name}." : "")}__VTables.GetMethodDelegate<{method.FunctionType}>({vtableIndex}, {i}{(id > 0 ? $", {id}" : string.Empty)});");
if (method.IsDestructor && @class.IsAbstract)
{

34
src/Generator/Utils/BlockGenerator.cs

@ -313,10 +313,38 @@ namespace CppSharp @@ -313,10 +313,38 @@ namespace CppSharp
ActiveBlock.WriteLine(msg, args);
}
public void WriteLines(string msg)
public void WriteLines(string msg, bool trimIndentation = false)
{
foreach(var line in msg.TrimStart().Split(LineBreakSequences, StringSplitOptions.None))
WriteLine(line);
var lines = msg.Split(LineBreakSequences, StringSplitOptions.None);
int indentation = int.MaxValue;
if (trimIndentation)
{
foreach(var line in lines)
{
for (int i = 0; i < line.Length; ++i)
{
if (char.IsWhiteSpace(line[i]))
continue;
if (i < indentation)
{
indentation = i;
break;
}
}
}
}
bool foundNonEmptyLine = false;
foreach (var line in lines)
{
if (!foundNonEmptyLine && string.IsNullOrEmpty(line))
continue;
WriteLine(line.Length >= indentation ? line.Substring(indentation) : line);
foundNonEmptyLine = true;
}
}
public void WriteLineIndent(string msg, params object[] args)

6
src/Runtime/MarshalUtil.cs

@ -57,10 +57,10 @@ namespace CppSharp.Runtime @@ -57,10 +57,10 @@ namespace CppSharp.Runtime
return GetArray<IntPtr>(array, size);
}
public static T GetDelegate<T>(void*[] vtables, short table, int i) where T : class
public static T GetDelegate<T>(IntPtr[] vtables, short table, int i) where T : class
{
var slot = *(void**)((IntPtr)vtables[table] + i * sizeof(IntPtr));
return Marshal.GetDelegateForFunctionPointer<T>(new IntPtr(slot));
var slot = *(IntPtr*)(vtables[table] + i * sizeof(IntPtr));
return Marshal.GetDelegateForFunctionPointer<T>(slot);
}
}
}

48
src/Runtime/VTables.cs

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace CppSharp.Runtime
{
public unsafe struct VTables
{
public Delegate[][] Methods { get; }
public IntPtr[] Tables { get; }
private Dictionary<(short, short, int), Delegate> Specializations;
public VTables(IntPtr[] tables, Delegate[][] methods = null)
{
Tables = tables;
Methods = methods;
Specializations = null;
}
public bool IsEmpty => Tables == null;
public bool IsTransient => Methods == null;
public T GetMethodDelegate<T>(short table, int slot, short specialiation = 0) where T : Delegate
{
if (specialiation == 0 && !IsTransient)
{
var method = Methods[table][slot];
if (method == null)
Methods[table][slot] = method = MarshalUtil.GetDelegate<T>(Tables, table, slot);
return (T)method;
}
else
{
if (Specializations == null)
Specializations = new Dictionary<(short, short, int), Delegate>();
var key = (specialiation, table, slot);
if (!Specializations.TryGetValue(key, out var method))
Specializations[key] = method = MarshalUtil.GetDelegate<T>(Tables, table, slot);
return (T)method;
}
}
}
}
Loading…
Cancel
Save