Tools and libraries to glue C/C++ APIs to high-level languages
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

430 lines
13 KiB

//
// Mono.Cxxi.CppTypeInfo.cs: Type metadata for C++ types
//
// Author:
// Alexander Corrado (alexander.corrado@gmail.com)
// Andreia Gaita (shana@spoiledcat.net)
//
// Copyright (C) 2010-2011 Alexander Corrado
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using Mono.Cxxi.Abi;
using Mono.Cxxi.Util;
namespace Mono.Cxxi {
public enum BaseVirtualMethods {
// Prepends this base's virtual methods to the primary vtable
PrependPrimary,
// Appends this base's virtual methods to the primary vtable
AppendPrimary,
// Creates a new out-of-band vtable for this base's virtual methods
NewVTable,
}
// NOTE: As AddBase is called, properties change.
// TypeComplete indicates when the dust has settled.
public class CppTypeInfo {
public CppLibrary Library { get; private set; }
public string TypeName { get; private set; }
public bool IsPrimaryBase { get; protected set; } // < True by default. set to False in cases where it is cloned as a non-primary base
// returns the number of vtable slots reserved for the
// base class(es) before this class's virtual methods start
public int BaseVTableSlots { get; protected set; }
public Type InterfaceType { get; private set; }
public Type NativeLayout { get; private set; }
public Type WrapperType { get; private set; }
// read only versions:
public IList<PInvokeSignature> VirtualMethods { get; private set; }
public IList<Type> VTableDelegateTypes { get; private set; }
public IList<Delegate> VTableOverrides { get; private set; }
public IList<CppTypeInfo> BaseClasses { get; private set; }
// backing lists:
protected List<PInvokeSignature> virtual_methods;
protected LazyGeneratedList<Type> vt_delegate_types;
protected LazyGeneratedList<Delegate> vt_overrides;
protected List<CppTypeInfo> base_classes;
protected List<int> vtable_index_adjustments;
protected int native_size_without_padding; // <- this refers to the size of all the fields declared in the nativeLayout struct
protected int field_offset_padding_without_vtptr;
protected int gchandle_offset_delta;
private VTable lazy_vtable;
internal EmitInfo emit_info; // <- will be null when the type is done being emitted
public bool TypeComplete { get { return emit_info == null; } }
#region Construction
public CppTypeInfo (CppLibrary lib, string typeName, Type interfaceType, Type nativeLayout, Type/*?*/ wrapperType)
: this ()
{
Library = lib;
TypeName = typeName;
InterfaceType = interfaceType;
NativeLayout = nativeLayout;
WrapperType = wrapperType;
virtual_methods = new List<PInvokeSignature> (Library.Abi.GetVirtualMethodSlots (this, interfaceType));
VirtualMethods = new ReadOnlyCollection<PInvokeSignature> (virtual_methods);
vt_delegate_types = new LazyGeneratedList<Type> (virtual_methods.Count, i => DelegateTypeCache.GetDelegateType (virtual_methods [i]));
VTableDelegateTypes = new ReadOnlyCollection<Type> (vt_delegate_types);
vt_overrides = new LazyGeneratedList<Delegate> (virtual_methods.Count, i => Library.Abi.GetManagedOverrideTrampoline (this, i));
VTableOverrides = new ReadOnlyCollection<Delegate> (vt_overrides);
vtable_index_adjustments = new List<int> (virtual_methods.Count);
if (nativeLayout != null)
native_size_without_padding = nativeLayout.GetFields ().Any ()? Marshal.SizeOf (nativeLayout) : 0;
}
protected CppTypeInfo ()
{
base_classes = new List<CppTypeInfo> ();
BaseClasses = new ReadOnlyCollection<CppTypeInfo> (base_classes);
field_offset_padding_without_vtptr = 0;
gchandle_offset_delta = 0;
IsPrimaryBase = true;
BaseVTableSlots = 0;
lazy_vtable = null;
emit_info = new EmitInfo ();
}
// The contract for Clone is that, if TypeComplete, working with the clone *through the public
// interface* is guaranteed not to affect the original. Note that any subclassses
// have access to protected stuff that is not covered by this guarantee.
public virtual CppTypeInfo Clone ()
{
return this.MemberwiseClone () as CppTypeInfo;
}
#endregion
#region Type Layout
public virtual int Alignment {
get { return IntPtr.Size; }
}
// the extra padding to allocate at the top of the class before the fields begin
// (by default, just the vtable pointer)
public virtual int FieldOffsetPadding {
get { return field_offset_padding_without_vtptr + (virtual_methods.Count != 0? IntPtr.Size : 0); }
}
public virtual int NativeSize {
get {
var basesize = native_size_without_padding + FieldOffsetPadding;
return basesize + (basesize % Alignment);
}
}
public virtual int GCHandleOffset {
get { return NativeSize + gchandle_offset_delta; }
}
public void AddBase (CppTypeInfo baseType)
{
// by default, only the primary base shares the subclass's primary vtable
AddBase (baseType, base_classes.Count == 0 ? BaseVirtualMethods.PrependPrimary : BaseVirtualMethods.NewVTable);
}
protected virtual void AddBase (CppTypeInfo baseType, BaseVirtualMethods location)
{
if (TypeComplete)
return;
var baseVMethodCount = baseType.virtual_methods.Count;
baseType = baseType.Clone ();
switch (location) {
case BaseVirtualMethods.PrependPrimary:
for (int i = 0; i < baseVMethodCount; i++)
virtual_methods.Insert (BaseVTableSlots + i, baseType.virtual_methods [i]);
gchandle_offset_delta = baseType.gchandle_offset_delta;
BaseVTableSlots += baseVMethodCount;
vt_delegate_types.Add (baseVMethodCount);
vt_overrides.Add (baseVMethodCount);
break;
case BaseVirtualMethods.AppendPrimary:
for (int i = 0; i < baseVMethodCount; i++)
virtual_methods.Add (baseType.virtual_methods [i]);
gchandle_offset_delta = baseType.gchandle_offset_delta;
vt_delegate_types.Add (baseVMethodCount);
vt_overrides.Add (baseVMethodCount);
break;
case BaseVirtualMethods.NewVTable:
baseType.IsPrimaryBase = (base_classes.Count == 0);
// offset all previously added bases
foreach (var previousBase in base_classes)
previousBase.gchandle_offset_delta += baseType.NativeSize;
// offset derived (this) type's gchandle
gchandle_offset_delta += baseType.GCHandleOffset;
baseType.gchandle_offset_delta += native_size_without_padding + CountBases (b => !b.IsPrimaryBase) * IntPtr.Size;
// ensure managed override tramps will be regenerated with correct gchandle offset
baseType.vt_overrides = new LazyGeneratedList<Delegate> (baseType.virtual_methods.Count, i => Library.Abi.GetManagedOverrideTrampoline (baseType, i));
baseType.VTableOverrides = new ReadOnlyCollection<Delegate> (baseType.vt_overrides);
baseType.lazy_vtable = null;
break;
}
base_classes.Add (baseType);
field_offset_padding_without_vtptr += baseType.native_size_without_padding +
(location == BaseVirtualMethods.NewVTable? baseType.FieldOffsetPadding : baseType.field_offset_padding_without_vtptr);
}
public virtual void CompleteType ()
{
if (emit_info == null)
return;
foreach (var baseClass in base_classes)
baseClass.CompleteType ();
emit_info = null;
RemoveVTableDuplicates ();
}
public virtual CppType GetMangleType ()
{
var mangleType = Library.Abi.GetMangleType (InterfaceType, InterfaceType);
mangleType.ElementTypeName = TypeName;
return mangleType;
}
public int CountBases (Func<CppTypeInfo, bool> predicate)
{
int count = 0;
foreach (var baseClass in base_classes) {
count += baseClass.CountBases (predicate);
count += predicate (baseClass)? 1 : 0;
}
return count;
}
#endregion
#region Casting
protected virtual CppTypeInfo GetCastInfo (Type sourceType, Type targetType, out int offset)
{
offset = 0;
if (WrapperType.Equals (targetType)) {
// check for downcast (base type -> this type)
foreach (var baseClass in base_classes) {
if (baseClass.WrapperType.Equals (sourceType)) {
return baseClass;
}
offset -= baseClass.NativeSize;
}
} else if (WrapperType.IsAssignableFrom (sourceType)) {
// check for upcast (this type -> base type)
foreach (var baseClass in base_classes) {
if (baseClass.WrapperType.Equals (targetType)) {
return baseClass;
}
offset += baseClass.NativeSize;
}
} else {
throw new ArgumentException ("Either source type or target type must be equal to this wrapper type.");
}
throw new InvalidCastException ("Cannot cast an instance of " + sourceType + " to " + targetType);
}
public virtual CppInstancePtr Cast (ICppObject instance, Type targetType)
{
int offset;
var baseTypeInfo = GetCastInfo (instance.GetType (), targetType, out offset);
var result = new CppInstancePtr (instance.Native, offset);
if (offset > 0 && instance.Native.IsManagedAlloc && baseTypeInfo.HasVTable) {
// we might need to paste the managed base-in-derived vtptr here --also inits native_vtptr
baseTypeInfo.VTable.InitInstance (ref result);
}
return result;
}
public virtual TTarget Cast<TTarget> (ICppObject instance) where TTarget : class
{
TTarget result;
var ptr = Cast (instance, typeof (TTarget));
// Check for existing instance based on vtable ptr
result = CppInstancePtr.ToManaged<TTarget> (ptr.Native);
// Create a new wrapper if necessary
if (result == null)
result = Activator.CreateInstance (typeof (TTarget), ptr) as TTarget;
return result;
}
public virtual void InitNonPrimaryBase (ICppObject baseInDerived, ICppObject derived, Type baseType)
{
int offset;
var baseTypeInfo = GetCastInfo (derived.GetType (), baseType, out offset);
Marshal.WriteIntPtr (baseInDerived.Native.Native, baseTypeInfo.GCHandleOffset, CppInstancePtr.MakeGCHandle (baseInDerived));
}
#endregion
#region V-Table
public virtual bool HasVTable {
get { return VirtualMethods.Any (); }
}
public virtual VTable VTable {
get {
CompleteType ();
if (!HasVTable)
return null;
if (lazy_vtable == null)
lazy_vtable = new VTable (this);
return lazy_vtable;
}
}
// the padding in the data pointed to by the vtable pointer before the list of function pointers starts
public virtual int VTableTopPadding {
get { return 0; }
}
// the amount of extra room alloc'd after the function pointer list of the vtbl
public virtual int VTableBottomPadding {
get { return 0; }
}
public virtual T GetAdjustedVirtualCall<T> (CppInstancePtr instance, int derivedVirtualMethodIndex)
where T : class /* Delegate */
{
var base_adjusted = BaseVTableSlots + derivedVirtualMethodIndex;
return VTable.GetVirtualCallDelegate<T> (instance, base_adjusted + vtable_index_adjustments [base_adjusted]);
}
protected virtual void RemoveVTableDuplicates ()
{
// check that any virtual methods overridden in a subclass are only included once
var vsignatures = new Dictionary<MethodSignature,PInvokeSignature> (MethodSignature.EqualityComparer);
var adjustment = 0;
for (int i = 0; i < virtual_methods.Count; i++) {
vtable_index_adjustments.Add (adjustment);
var sig = virtual_methods [i];
if (sig == null)
continue;
PInvokeSignature existing;
if (vsignatures.TryGetValue (sig, out existing)) {
OnVTableDuplicate (ref i, ref adjustment, sig, existing);
} else {
vsignatures.Add (sig, sig);
}
}
}
protected virtual bool OnVTableDuplicate (ref int iter, ref int adj, PInvokeSignature sig, PInvokeSignature dup)
{
// This predicate ensures that duplicates are only removed
// if declared in different classes (i.e. overridden methods).
// We usually want to allow the same exact virtual methods to appear
// multiple times, in the case of nonvirtual diamond inheritance, for example.
if (!sig.OrigMethod.Equals (dup.OrigMethod)) {
virtual_methods.RemoveAt (iter--);
vt_overrides.Remove (1);
vt_delegate_types.Remove (1);
adj--;
return true;
}
return false;
}
#endregion
}
// This is used internally by CppAbi:
internal class DummyCppTypeInfo : CppTypeInfo {
public CppTypeInfo BaseTypeInfo { get; set; }
protected override void AddBase (CppTypeInfo baseType, BaseVirtualMethods location)
{
BaseTypeInfo = baseType;
}
}
}