Browse Source

Detect managed instances by vtptr and retrieve original wrapper instance instead of rewrapping

pull/1/head
Alex Corrado 14 years ago
parent
commit
7ebba74718
  1. 36
      src/Mono.VisualC.Interop/ABI/CppAbi.cs
  2. 3
      src/Mono.VisualC.Interop/ABI/Impl/ItaniumAbi.cs
  3. 30
      src/Mono.VisualC.Interop/ABI/VTable.cs
  4. 61
      src/Mono.VisualC.Interop/CppInstancePtr.cs
  5. 1
      tests/VirtualTests.cs

36
src/Mono.VisualC.Interop/ABI/CppAbi.cs

@ -52,7 +52,8 @@ namespace Mono.VisualC.Interop.ABI { @@ -52,7 +52,8 @@ namespace Mono.VisualC.Interop.ABI {
protected static readonly MethodInfo cppobj_native = typeof (ICppObject).GetProperty ("Native").GetGetMethod ();
protected static readonly MethodInfo cppip_native = typeof (CppInstancePtr).GetProperty ("Native").GetGetMethod ();
protected static readonly MethodInfo cppip_managedalloc = typeof (CppInstancePtr).GetProperty ("IsManagedAlloc").GetGetMethod ();
protected static readonly MethodInfo cppip_getmanaged = typeof (CppInstancePtr).GetMethod ("GetManaged", BindingFlags.Static | BindingFlags.NonPublic);
protected static readonly MethodInfo cppip_tomanaged = typeof (CppInstancePtr).GetMethod ("ToManaged", BindingFlags.Static | BindingFlags.NonPublic, null, new Type [] { typeof (IntPtr) }, null);
protected static readonly MethodInfo cppip_tomanaged_size = typeof (CppInstancePtr).GetMethod ("ToManaged", BindingFlags.Static | BindingFlags.NonPublic, null, new Type [] { typeof (IntPtr), typeof (int) }, null);
protected static readonly ConstructorInfo cppip_fromnative = typeof (CppInstancePtr).GetConstructor (new Type [] { typeof (IntPtr) });
protected static readonly ConstructorInfo cppip_fromsize = typeof (CppInstancePtr).GetConstructor (BindingFlags.Instance | BindingFlags.NonPublic, null, new Type [] { typeof (int) }, null);
protected static readonly ConstructorInfo cppip_fromsize_managed = typeof (CppInstancePtr).GetConstructor (BindingFlags.Instance | BindingFlags.NonPublic, null,
@ -375,7 +376,7 @@ namespace Mono.VisualC.Interop.ABI { @@ -375,7 +376,7 @@ namespace Mono.VisualC.Interop.ABI {
il.Emit (OpCodes.Ldarg_0);
il.Emit (OpCodes.Ldc_I4, typeInfo.NativeSize);
var getManagedObj = cppip_getmanaged.MakeGenericMethod (typeInfo.WrapperType);
var getManagedObj = cppip_tomanaged_size.MakeGenericMethod (typeInfo.WrapperType);
il.Emit (OpCodes.Call, getManagedObj);
}
@ -666,6 +667,7 @@ namespace Mono.VisualC.Interop.ABI { @@ -666,6 +667,7 @@ namespace Mono.VisualC.Interop.ABI {
return; // <- yes, this is necessary
var next = il.DefineLabel ();
var ptr = il.DeclareLocal (typeof (CppInstancePtr));
// marshal IntPtr -> ICppObject
if (nativeType == typeof (IntPtr) && typeof (ICppObject).IsAssignableFrom (targetType)) {
@ -677,7 +679,8 @@ namespace Mono.VisualC.Interop.ABI { @@ -677,7 +679,8 @@ namespace Mono.VisualC.Interop.ABI {
il.Emit (OpCodes.Brfalse_S, isNull);
il.Emit (OpCodes.Newobj, cppip_fromnative);
EmitCreateCppObjectFromNative (il, targetType);
il.Emit (OpCodes.Stloc, ptr);
EmitCreateCppObjectFromNative (il, targetType, ptr);
il.Emit (OpCodes.Br_S, next);
il.MarkLabel (isNull);
@ -690,8 +693,6 @@ namespace Mono.VisualC.Interop.ABI { @@ -690,8 +693,6 @@ namespace Mono.VisualC.Interop.ABI {
// Obviously, we lose all managed overrides if we pass by value,
// but this "slicing" happens in vanilla C++ as well
var ptr = il.DeclareLocal (typeof (CppInstancePtr));
il.Emit (OpCodes.Box, nativeType); // structure
il.Emit (OpCodes.Sizeof, nativeType);
@ -703,9 +704,7 @@ namespace Mono.VisualC.Interop.ABI { @@ -703,9 +704,7 @@ namespace Mono.VisualC.Interop.ABI {
il.Emit (OpCodes.Ldc_I4_0); // fDeleteOld
il.Emit (OpCodes.Call, marshal_structuretoptr);
il.Emit (OpCodes.Ldloc, ptr);
EmitCreateCppObjectFromNative (il, targetType);
EmitCreateCppObjectFromNative (il, targetType, ptr);
}
il.MarkLabel (next);
@ -751,8 +750,8 @@ namespace Mono.VisualC.Interop.ABI { @@ -751,8 +750,8 @@ namespace Mono.VisualC.Interop.ABI {
il.Emit (OpCodes.Call, dummytypeinfo_getbase);
}
// Expects CppInstancePtr on stack. No null check performed
protected virtual void EmitCreateCppObjectFromNative (ILGenerator il, Type targetType)
// Expects cppip = CppInstancePtr local
protected virtual void EmitCreateCppObjectFromNative (ILGenerator il, Type targetType, LocalBuilder cppip)
{
if (targetType == typeof (ICppObject))
targetType = typeof (CppInstancePtr);
@ -762,7 +761,24 @@ namespace Mono.VisualC.Interop.ABI { @@ -762,7 +761,24 @@ namespace Mono.VisualC.Interop.ABI {
if (ctor == null)
throw new InvalidProgramException (string.Format ("Type `{0}' implements ICppObject but does not contain a public constructor that takes CppInstancePtr", targetType));
// Basically emitting this:
// CppInstancePtr.ToManaged<targetType> (native) ?? new targetType (native)
var hasWrapper = il.DefineLabel ();
il.Emit (OpCodes.Ldloca, cppip);
il.Emit (OpCodes.Call, cppip_native);
il.Emit (OpCodes.Call, cppip_tomanaged.MakeGenericMethod (targetType));
il.Emit (OpCodes.Dup);
il.Emit (OpCodes.Brtrue_S, hasWrapper);
il.Emit (OpCodes.Pop);
il.Emit (OpCodes.Ldloc, cppip);
il.Emit (OpCodes.Newobj, ctor);
il.MarkLabel (hasWrapper);
}
/**

3
src/Mono.VisualC.Interop/ABI/Impl/ItaniumAbi.cs

@ -235,8 +235,7 @@ namespace Mono.VisualC.Interop.ABI { @@ -235,8 +235,7 @@ namespace Mono.VisualC.Interop.ABI {
base.EmitNativeCall (il, nativeMethod, psig, nativePtr);
if (hiddenReturnByValue) {
il.Emit (OpCodes.Ldloc, returnValue);
EmitCreateCppObjectFromNative (il, method.ReturnType);
EmitCreateCppObjectFromNative (il, method.ReturnType, returnValue);
}
}

30
src/Mono.VisualC.Interop/ABI/VTable.cs

@ -41,11 +41,11 @@ namespace Mono.VisualC.Interop.ABI { @@ -41,11 +41,11 @@ namespace Mono.VisualC.Interop.ABI {
public class VTable : IDisposable {
protected bool initialized;
protected CppTypeInfo typeInfo;
protected CppTypeInfo type_info;
protected IntPtr vtPtr;
public virtual int EntryCount {
get { return typeInfo.VirtualMethods.Count; }
get { return type_info.VirtualMethods.Count; }
}
public virtual int EntrySize {
get { return Marshal.SizeOf (typeof (IntPtr)); }
@ -55,17 +55,19 @@ namespace Mono.VisualC.Interop.ABI { @@ -55,17 +55,19 @@ namespace Mono.VisualC.Interop.ABI {
public VTable (CppTypeInfo typeInfo)
{
this.initialized = false;
this.typeInfo = typeInfo;
this.type_info = typeInfo;
this.vtPtr = Marshal.AllocHGlobal ((EntryCount * EntrySize) + typeInfo.VTableTopPadding + typeInfo.VTableBottomPadding);
WriteOverrides ();
CppInstancePtr.RegisterManagedVTable (this);
}
protected virtual void WriteOverrides ()
{
IntPtr vtEntryPtr;
int currentOffset = typeInfo.VTableTopPadding;
int currentOffset = type_info.VTableTopPadding;
for (int i = 0; i < EntryCount; i++) {
Delegate currentOverride = typeInfo.VTableOverrides [i];
Delegate currentOverride = type_info.VTableOverrides [i];
if (currentOverride != null) // managed override
vtEntryPtr = Marshal.GetFunctionPointerForDelegate (currentOverride);
@ -80,9 +82,9 @@ namespace Mono.VisualC.Interop.ABI { @@ -80,9 +82,9 @@ namespace Mono.VisualC.Interop.ABI {
public virtual T GetVirtualCallDelegate<T> (CppInstancePtr instance, int index)
where T : class /*Delegate*/
{
var vtable = instance.native_vtptr;
var vtable = instance.NativeVTable;
var ftnptr = Marshal.ReadIntPtr (vtable, (index * EntrySize) + typeInfo.VTableTopPadding);
var ftnptr = Marshal.ReadIntPtr (vtable, (index * EntrySize) + type_info.VTableTopPadding);
if (ftnptr == IntPtr.Zero)
throw new NullReferenceException ("Native VTable contains null...possible abstract class???");
@ -96,15 +98,15 @@ namespace Mono.VisualC.Interop.ABI { @@ -96,15 +98,15 @@ namespace Mono.VisualC.Interop.ABI {
var basePtr = Marshal.ReadIntPtr (instance.Native);
Debug.Assert (basePtr != IntPtr.Zero && basePtr != vtPtr);
instance.native_vtptr = basePtr;
instance.NativeVTable = basePtr;
if (!initialized) {
// FIXME: This could probably be a more efficient memcpy
for (int i = 0; i < typeInfo.VTableTopPadding; i++)
for (int i = 0; i < type_info.VTableTopPadding; i++)
Marshal.WriteByte(vtPtr, i, Marshal.ReadByte(basePtr, i));
int currentOffset = typeInfo.VTableTopPadding;
int currentOffset = type_info.VTableTopPadding;
for (int i = 0; i < EntryCount; i++) {
if (Marshal.ReadIntPtr (vtPtr, currentOffset) == IntPtr.Zero)
Marshal.WriteIntPtr (vtPtr, currentOffset, Marshal.ReadIntPtr (basePtr, currentOffset));
@ -113,7 +115,7 @@ namespace Mono.VisualC.Interop.ABI { @@ -113,7 +115,7 @@ namespace Mono.VisualC.Interop.ABI {
}
// FIXME: This could probably be a more efficient memcpy
for (int i = 0; i < typeInfo.VTableBottomPadding; i++)
for (int i = 0; i < type_info.VTableBottomPadding; i++)
Marshal.WriteByte(vtPtr, currentOffset + i, Marshal.ReadByte(basePtr, currentOffset + i));
initialized = true;
@ -124,7 +126,11 @@ namespace Mono.VisualC.Interop.ABI { @@ -124,7 +126,11 @@ namespace Mono.VisualC.Interop.ABI {
public virtual void ResetInstance (CppInstancePtr instance)
{
Marshal.WriteIntPtr (instance.Native, instance.native_vtptr);
Marshal.WriteIntPtr (instance.Native, instance.NativeVTable);
}
public CppTypeInfo TypeInfo {
get { return type_info; }
}
public IntPtr Pointer {

61
src/Mono.VisualC.Interop/CppInstancePtr.cs

@ -37,11 +37,11 @@ using Mono.VisualC.Interop.ABI; @@ -37,11 +37,11 @@ using Mono.VisualC.Interop.ABI;
namespace Mono.VisualC.Interop {
public struct CppInstancePtr : ICppObject {
private IntPtr ptr;
internal IntPtr native_vtptr;
private IntPtr ptr, native_vtptr;
private bool manage_memory;
private static Dictionary<Type,object> implCache = null;
private static Dictionary<IntPtr,int> managed_vtptr_to_gchandle_offset = null;
// TODO: the managed instance argument may only be NULL if all methods in TWrapper
// that correspond to the virtual methods in Iface are static.
@ -102,15 +102,18 @@ namespace Mono.VisualC.Interop { @@ -102,15 +102,18 @@ namespace Mono.VisualC.Interop {
if (native == IntPtr.Zero)
throw new ArgumentOutOfRangeException ("native cannot be null pointer");
// Kludge! CppInstancePtr doesn't know whether this class is virtual or not, but we'll just assume that either
// way it's at least sizeof(void*) and read what would be the vtptr anyway. Supposedly, if it's not virtual,
// the wrappers won't use this field anyway...
native_vtptr = Marshal.ReadIntPtr (native);
ptr = native;
manage_memory = false;
}
// Fulfills ICppObject requirement
public CppInstancePtr (CppInstancePtr copy)
{
this.ptr = copy.ptr;
this.native_vtptr = copy.native_vtptr;
this.manage_memory = copy.manage_memory;
}
// Provide casts to/from IntPtr:
public static implicit operator CppInstancePtr (IntPtr native)
{
@ -132,6 +135,25 @@ namespace Mono.VisualC.Interop { @@ -132,6 +135,25 @@ namespace Mono.VisualC.Interop {
}
}
// Internal for now to prevent attempts to read vtptr from non-virtual class
internal IntPtr NativeVTable {
get {
if (native_vtptr == IntPtr.Zero) {
// For pointers from native code...
// Kludge! CppInstancePtr doesn't know whether this class is virtual or not, but we'll just assume that either
// way it's at least sizeof(void*) and read what would be the vtptr anyway. Supposedly, if it's not virtual,
// the wrappers won't use this field anyway...
native_vtptr = Marshal.ReadIntPtr (ptr);
}
return native_vtptr;
}
set {
native_vtptr = value;
}
}
CppInstancePtr ICppObject.Native {
get { return this; }
}
@ -140,6 +162,14 @@ namespace Mono.VisualC.Interop { @@ -140,6 +162,14 @@ namespace Mono.VisualC.Interop {
get { return manage_memory; }
}
internal static void RegisterManagedVTable (VTable vtable)
{
if (managed_vtptr_to_gchandle_offset == null)
managed_vtptr_to_gchandle_offset = new Dictionary<IntPtr, int> ();
managed_vtptr_to_gchandle_offset [vtable.Pointer] = vtable.TypeInfo.NativeSize;
}
internal static IntPtr MakeGCHandle (object managedWrapper)
{
// TODO: Dispose() should probably be called at some point on this GCHandle.
@ -147,9 +177,24 @@ namespace Mono.VisualC.Interop { @@ -147,9 +177,24 @@ namespace Mono.VisualC.Interop {
return GCHandle.ToIntPtr (handle);
}
// This might be made public, but in this form it only works for classes with vtables.
// Returns null if the native ptr passed in does not appear to be a managed instance.
// (i.e. its vtable ptr is not in managed_vtptr_to_gchandle_offset)
internal static T ToManaged<T> (IntPtr native) where T : class
{
if (managed_vtptr_to_gchandle_offset == null)
return null;
int gchOffset;
if (!managed_vtptr_to_gchandle_offset.TryGetValue (Marshal.ReadIntPtr (native), out gchOffset))
return null;
return ToManaged<T> (native, gchOffset);
}
// WARNING! This method is not safe. DO NOT call
// if we do not KNOW that this instance is managed.
internal static T GetManaged<T> (IntPtr native, int nativeSize) where T : class
internal static T ToManaged<T> (IntPtr native, int nativeSize) where T : class
{
IntPtr handlePtr = Marshal.ReadIntPtr (native, nativeSize);
GCHandle handle = GCHandle.FromIntPtr (handlePtr);

1
tests/VirtualTests.cs

@ -139,6 +139,7 @@ namespace Tests { @@ -139,6 +139,7 @@ namespace Tests {
var cls = roundtripper.GetIt ();
Assert.AreEqual (25, cls.Number, "#1");
Assert.AreEqual (-25, cls.NegativeNumber, "#2");
Assert.IsNotNull (cls as ManagedOverride1);
}
}

Loading…
Cancel
Save