mirror of https://github.com/mono/CppSharp.git
c-sharpdotnetmonobindingsbridgecclangcpluspluscppsharpglueinteropparserparsingpinvokeswigsyntax-treevisitorsxamarinxamarin-bindings
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.
936 lines
36 KiB
936 lines
36 KiB
// |
|
// Mono.Cxxi.Abi.CppAbi.cs: Represents an abstract C++ ABI |
|
// |
|
// Author: |
|
// Alexander Corrado (alexander.corrado@gmail.com) |
|
// |
|
// Copyright (C) 2010-2011 Alexander Corrado |
|
// Copyright 2011 Xamarin Inc (http://www.xamarin.com) |
|
// |
|
// 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.Runtime.InteropServices; |
|
using System.Diagnostics; |
|
|
|
using Mono.Cxxi.Util; |
|
|
|
namespace Mono.Cxxi.Abi { |
|
|
|
//FIXME: Exception handling, operator overloading etc. |
|
//FIXME: Allow interface to override default calling convention |
|
|
|
// Subclasses should be singletons |
|
public abstract partial class CppAbi { |
|
// (other part of this partial class in Attributes.cs) |
|
|
|
protected Dictionary<Type,CppTypeInfo> wrapper_to_typeinfo = new Dictionary<Type, CppTypeInfo> (); |
|
protected MemberFilter vtable_override_filter = VTable.BindToSignatureAndAttribute; |
|
|
|
// Cache some frequently used methodinfos: |
|
protected static readonly MethodInfo typeinfo_nativesize = typeof (CppTypeInfo).GetProperty ("NativeSize").GetGetMethod (); |
|
protected static readonly MethodInfo typeinfo_vtable = typeof (CppTypeInfo).GetProperty ("VTable").GetGetMethod (); |
|
protected static readonly MethodInfo typeinfo_adjvcall = typeof (CppTypeInfo).GetMethod ("GetAdjustedVirtualCall"); |
|
protected static readonly MethodInfo typeinfo_fieldoffset = typeof (CppTypeInfo).GetProperty ("FieldOffsetPadding").GetGetMethod (); |
|
protected static readonly MethodInfo vtable_initinstance = typeof (VTable).GetMethod ("InitInstance"); |
|
protected static readonly MethodInfo vtable_resetinstance = typeof (VTable).GetMethod ("ResetInstance"); |
|
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_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 MethodInfo cppip_dispose = typeof (CppInstancePtr).GetMethod ("Dispose"); |
|
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_fromtype_managed = typeof (CppInstancePtr).GetConstructor (BindingFlags.Instance | BindingFlags.NonPublic, null, |
|
new Type[] { typeof (CppTypeInfo), typeof (object) }, null); |
|
protected static readonly ConstructorInfo notimplementedexception = typeof (NotImplementedException).GetConstructor (new Type [] { typeof (string) }); |
|
protected static readonly MethodInfo type_gettypefromhandle = typeof (Type).GetMethod ("GetTypeFromHandle"); |
|
protected static readonly MethodInfo marshal_offsetof = typeof (Marshal).GetMethod ("OffsetOf"); |
|
protected static readonly MethodInfo marshal_structuretoptr = typeof (Marshal).GetMethod ("StructureToPtr"); |
|
protected static readonly MethodInfo marshal_ptrtostructure = typeof (Marshal).GetMethod ("PtrToStructure", BindingFlags.Static | BindingFlags.Public, null, new Type [] { typeof (IntPtr), typeof (Type) }, null); |
|
protected static readonly FieldInfo intptr_zero = typeof (IntPtr).GetField ("Zero"); |
|
|
|
// These methods might be more commonly overridden for a given C++ ABI: |
|
|
|
public virtual MethodType GetMethodType (CppTypeInfo typeInfo, MethodInfo imethod) |
|
{ |
|
if (IsInline (imethod) && !IsVirtual (imethod) && typeInfo.Library.InlineMethodPolicy == InlineMethods.NotPresent) |
|
return MethodType.NotImplemented; |
|
else if (imethod.IsDefined (typeof (ConstructorAttribute), false)) |
|
return MethodType.NativeCtor; |
|
else if (imethod.Name.Equals ("Alloc")) |
|
return MethodType.ManagedAlloc; |
|
else if (imethod.IsDefined (typeof (DestructorAttribute), false)) |
|
return MethodType.NativeDtor; |
|
|
|
return MethodType.Native; |
|
} |
|
|
|
// The members below must be implemented for a given C++ ABI: |
|
|
|
public abstract CallingConvention? GetCallingConvention (MethodInfo methodInfo); |
|
protected abstract string GetMangledMethodName (CppTypeInfo typeInfo, MethodInfo methodInfo); |
|
|
|
// --------------------------------- |
|
|
|
private struct EmptyNativeLayout { } |
|
|
|
public virtual ICppClass ImplementClass (CppTypeInfo typeInfo) |
|
{ |
|
if (typeInfo.WrapperType == null || !wrapper_to_typeinfo.ContainsKey (typeInfo.WrapperType)) { |
|
|
|
if (typeInfo.WrapperType != null) |
|
wrapper_to_typeinfo.Add (typeInfo.WrapperType, typeInfo); |
|
|
|
DefineImplType (typeInfo); |
|
|
|
var properties = GetProperties (typeInfo.InterfaceType); |
|
var methods = GetMethods (typeInfo.InterfaceType).Select (m => GetPInvokeSignature (typeInfo, m)); |
|
|
|
// Implement all methods |
|
int vtableIndex = 0; |
|
foreach (var method in methods) |
|
DefineMethod (typeInfo, method, ref vtableIndex); |
|
|
|
// Implement all properties |
|
foreach (var property in properties) |
|
DefineProperty (typeInfo, property); |
|
|
|
typeInfo.emit_info.ctor_il.Emit (OpCodes.Ret); |
|
return (ICppClass)Activator.CreateInstance (typeInfo.emit_info.type_builder.CreateType (), typeInfo); |
|
} |
|
|
|
throw new InvalidOperationException ("This type has already been implemented"); |
|
} |
|
|
|
public virtual CppTypeInfo MakeTypeInfo (CppLibrary lib, string typeName, Type interfaceType, Type layoutType/*?*/, Type/*?*/ wrapperType) |
|
{ |
|
Debug.Assert (lib.Abi == this); |
|
return new CppTypeInfo (lib, typeName, interfaceType, layoutType ?? typeof (EmptyNativeLayout), wrapperType); |
|
} |
|
|
|
public virtual IEnumerable<PInvokeSignature> GetVirtualMethodSlots (CppTypeInfo typeInfo, Type interfaceType) |
|
{ |
|
return from m in GetMethods (interfaceType) |
|
where IsVirtual (m) |
|
select GetPInvokeSignature (typeInfo, m); |
|
} |
|
|
|
protected virtual IEnumerable<MethodInfo> GetMethods (Type interfaceType) |
|
{ |
|
// get all methods defined on inherited interfaces first |
|
var methods = ( |
|
from iface in interfaceType.GetInterfaces () |
|
from method in iface.GetMethods () |
|
where !method.IsSpecialName |
|
select method |
|
).Concat ( |
|
from method in interfaceType.GetMethods () |
|
where !method.IsSpecialName |
|
orderby method.MetadataToken |
|
select method |
|
); |
|
|
|
return methods; |
|
} |
|
|
|
protected virtual IEnumerable<PropertyInfo> GetProperties (Type interfaceType) |
|
{ |
|
return ( // get all properties defined on the interface |
|
from property in interfaceType.GetProperties () |
|
select property |
|
).Union ( // ... as well as those defined on inherited interfaces |
|
from iface in interfaceType.GetInterfaces () |
|
from property in iface.GetProperties () |
|
select property |
|
); |
|
} |
|
|
|
protected virtual void DefineImplType (CppTypeInfo typeInfo) |
|
{ |
|
string implTypeName = typeInfo.InterfaceType.Name + "_"; |
|
if (typeInfo.NativeLayout != null) |
|
implTypeName += typeInfo.NativeLayout.Name + "_"; |
|
implTypeName += this.GetType ().Name + "_Impl"; |
|
|
|
var impl_type = CppLibrary.interopModule.DefineType (implTypeName, TypeAttributes.Class | TypeAttributes.Sealed); |
|
impl_type.AddInterfaceImplementation (typeInfo.InterfaceType); |
|
|
|
var typeinfo_field = impl_type.DefineField ("_typeInfo", typeof (CppTypeInfo), FieldAttributes.InitOnly | FieldAttributes.Private); |
|
|
|
ConstructorBuilder ctor = impl_type.DefineConstructor (MethodAttributes.Public, CallingConventions.Standard, |
|
new Type[] { typeof (CppTypeInfo) }); |
|
|
|
var ctor_il = ctor.GetILGenerator (); |
|
|
|
// this._typeInfo = (CppTypeInfo passed to constructor) |
|
ctor_il.Emit (OpCodes.Ldarg_0); |
|
ctor_il.Emit (OpCodes.Ldarg_1); |
|
ctor_il.Emit (OpCodes.Stfld, typeinfo_field); |
|
|
|
typeInfo.emit_info.ctor_il = ctor_il; |
|
typeInfo.emit_info.typeinfo_field = typeinfo_field; |
|
typeInfo.emit_info.type_builder = impl_type; |
|
} |
|
|
|
protected virtual MethodBuilder DefineMethod (CppTypeInfo typeInfo, PInvokeSignature psig, ref int vtableIndex) |
|
{ |
|
var interfaceMethod = psig.OrigMethod; |
|
|
|
// 1. Generate managed trampoline to call native method |
|
var trampoline = GetMethodBuilder (typeInfo, interfaceMethod); |
|
var il = typeInfo.emit_info.current_il = trampoline.GetILGenerator (); |
|
|
|
switch (psig.Type) { |
|
|
|
case MethodType.NotImplemented: |
|
il.Emit (OpCodes.Ldstr, "This method is not available."); |
|
il.Emit (OpCodes.Newobj, notimplementedexception); |
|
il.Emit (OpCodes.Throw); |
|
|
|
goto case MethodType.NoOp; // fallthrough |
|
case MethodType.NoOp: |
|
// return NULL if method is supposed to return a value |
|
if (!interfaceMethod.ReturnType.Equals (typeof (void))) |
|
il.Emit (OpCodes.Ldnull); |
|
il.Emit (OpCodes.Ret); |
|
return trampoline; |
|
|
|
case MethodType.ManagedAlloc: |
|
EmitManagedAlloc (typeInfo, interfaceMethod); |
|
il.Emit (OpCodes.Ret); |
|
return trampoline; |
|
} |
|
|
|
var isStatic = IsStatic (interfaceMethod); |
|
LocalBuilder cppInstancePtr = null; |
|
LocalBuilder nativePtr = null; |
|
|
|
// If we're an instance method, load up the "this" pointer |
|
if (!isStatic) |
|
{ |
|
if (psig.ParameterTypes.Count == 0) |
|
throw new ArgumentException ("First argument to non-static C++ method must be instance pointer."); |
|
|
|
// 2. Load the native C++ instance pointer |
|
EmitLoadInstancePtr (il, interfaceMethod.GetParameters () [0].ParameterType, out cppInstancePtr, out nativePtr); |
|
|
|
// 3. Make sure our native pointer is a valid reference. If not, throw ObjectDisposedException |
|
EmitCheckDisposed (il, nativePtr, psig.Type); |
|
} |
|
|
|
MethodInfo nativeMethod; |
|
|
|
if (IsVirtual (interfaceMethod) && psig.Type != MethodType.NativeDtor) { |
|
nativeMethod = EmitPrepareVirtualCall (typeInfo, cppInstancePtr, vtableIndex++); |
|
} else { |
|
if (IsVirtual (interfaceMethod)) |
|
vtableIndex++; |
|
|
|
nativeMethod = GetPInvokeForMethod (typeInfo, psig); |
|
} |
|
|
|
switch (psig.Type) { |
|
case MethodType.NativeCtor: |
|
EmitConstruct (typeInfo, nativeMethod, psig, cppInstancePtr, nativePtr); |
|
break; |
|
case MethodType.NativeDtor: |
|
EmitDestruct (typeInfo, nativeMethod, psig, cppInstancePtr, nativePtr); |
|
break; |
|
default: |
|
EmitNativeCall (typeInfo, nativeMethod, psig, nativePtr); |
|
break; |
|
} |
|
|
|
il.Emit (OpCodes.Ret); |
|
return trampoline; |
|
} |
|
|
|
protected virtual PropertyBuilder DefineProperty (CppTypeInfo typeInfo, PropertyInfo property) |
|
{ |
|
if (property.CanWrite) |
|
throw new InvalidProgramException ("Properties in C++ interface must be read-only."); |
|
|
|
var imethod = property.GetGetMethod (); |
|
var methodName = imethod.Name; |
|
var propName = property.Name; |
|
var retType = imethod.ReturnType; |
|
|
|
var fieldProp = typeInfo.emit_info.type_builder.DefineProperty (propName, PropertyAttributes.None, retType, Type.EmptyTypes); |
|
|
|
var methodAttr = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig; |
|
var fieldGetter = typeInfo.emit_info.type_builder.DefineMethod (methodName, methodAttr, retType, Type.EmptyTypes); |
|
var il = fieldGetter.GetILGenerator (); |
|
|
|
// C++ interface properties are either to return the CppTypeInfo or to access C++ fields |
|
if (retType.IsGenericType && retType.GetGenericTypeDefinition ().Equals (typeof (CppField<>))) { |
|
|
|
// define a new field for the property |
|
var fieldData = typeInfo.emit_info.type_builder.DefineField ("__" + propName + "_Data", retType, FieldAttributes.InitOnly | FieldAttributes.Private); |
|
|
|
// we need to lazy init the field because we don't have accurate field offset until after |
|
// all base classes have been added (by ctor) |
|
var lazyInit = il.DefineLabel (); |
|
|
|
il.Emit (OpCodes.Ldarg_0); |
|
il.Emit (OpCodes.Ldfld, fieldData); |
|
il.Emit (OpCodes.Dup); |
|
il.Emit (OpCodes.Brfalse_S, lazyInit); |
|
il.Emit (OpCodes.Ret); |
|
|
|
// init new CppField |
|
il.MarkLabel (lazyInit); |
|
il.Emit (OpCodes.Pop); |
|
|
|
il.Emit (OpCodes.Ldarg_0); |
|
|
|
// first, get field offset |
|
// = ((int)Marshal.OffsetOf (layout_type, propName)) + FieldOffsetPadding; |
|
il.Emit(OpCodes.Ldtoken, typeInfo.NativeLayout); |
|
il.Emit(OpCodes.Call, type_gettypefromhandle); |
|
il.Emit(OpCodes.Ldstr, propName); |
|
il.Emit(OpCodes.Call, marshal_offsetof); |
|
|
|
il.Emit (OpCodes.Ldarg_0); |
|
il.Emit (OpCodes.Ldfld, typeInfo.emit_info.typeinfo_field); |
|
il.Emit (OpCodes.Callvirt, typeinfo_fieldoffset); |
|
|
|
il.Emit (OpCodes.Add); |
|
|
|
// new CppField<T> (<field offset>) |
|
il.Emit (OpCodes.Newobj, retType.GetConstructor (new Type[] { typeof(int) })); |
|
|
|
il.Emit (OpCodes.Stfld, fieldData); |
|
|
|
il.Emit (OpCodes.Ldarg_0); |
|
il.Emit (OpCodes.Ldfld, fieldData); |
|
il.Emit (OpCodes.Ret); |
|
|
|
} else if (retType.Equals (typeof (CppTypeInfo))) { |
|
il.Emit (OpCodes.Ldarg_0); |
|
il.Emit (OpCodes.Ldfld, typeInfo.emit_info.typeinfo_field); |
|
il.Emit (OpCodes.Ret); |
|
} else |
|
throw new InvalidProgramException ("Properties in C++ interface can only be of type CppField."); |
|
|
|
fieldProp.SetGetMethod (fieldGetter); |
|
|
|
return fieldProp; |
|
} |
|
|
|
/** |
|
* Implements the managed trampoline that will be invoked from the vtable by native C++ code when overriding |
|
* the specified C++ virtual method with the specified managed one. |
|
*/ |
|
// FIXME: This should be moved into CppTypeInfo class |
|
internal virtual Delegate GetManagedOverrideTrampoline (CppTypeInfo typeInfo, int vtableIndex) |
|
{ |
|
if (typeInfo.WrapperType == null) |
|
return null; |
|
|
|
var sig = typeInfo.VirtualMethods [vtableIndex]; |
|
if (sig == null) |
|
return null; |
|
|
|
var interfaceMethod = sig.OrigMethod; |
|
var targetMethod = FindManagedOverrideTarget (typeInfo.WrapperType, interfaceMethod); |
|
if (targetMethod == null) |
|
return null; |
|
|
|
var interfaceArgs = ReflectionHelper.GetMethodParameterTypes (interfaceMethod); |
|
var nativeArgs = sig.ParameterTypes.ToArray (); |
|
|
|
// TODO: According to http://msdn.microsoft.com/en-us/library/w16z8yc4.aspx |
|
// The dynamic method created with this constructor has access to public and internal members of all the types contained in module m. |
|
// This does not appear to hold true, so we also disable JIT visibility checks. |
|
var trampolineIn = new DynamicMethod (typeInfo.WrapperType.Name + "_" + interfaceMethod.Name + "_FromNative", sig.ReturnType, |
|
nativeArgs, typeof (CppInstancePtr).Module, true); |
|
|
|
ReflectionHelper.ApplyMethodParameterAttributes (interfaceMethod, trampolineIn, true); |
|
var il = trampolineIn.GetILGenerator (); |
|
|
|
// for static (target) methods: |
|
OpCode callInstruction = OpCodes.Call; |
|
int argLoadStart = IsStatic (interfaceMethod)? 0 : 1; // chop off C++ instance ptr if there is one |
|
|
|
// for instance methods, we need a managed instance to call them on! |
|
if (!targetMethod.IsStatic) { |
|
callInstruction = OpCodes.Callvirt; |
|
argLoadStart = 1; |
|
|
|
il.Emit (OpCodes.Ldarg_0); |
|
il.Emit (OpCodes.Ldc_I4, typeInfo.GCHandleOffset); |
|
|
|
var getManagedObj = cppip_tomanaged_size.MakeGenericMethod (typeInfo.WrapperType); |
|
il.Emit (OpCodes.Call, getManagedObj); |
|
} |
|
|
|
for (int i = argLoadStart; i < interfaceArgs.Length; i++) { |
|
il.Emit (OpCodes.Ldarg, i); |
|
EmitInboundMarshal (il, nativeArgs [i], interfaceArgs [i]); |
|
} |
|
|
|
il.Emit (callInstruction, targetMethod); |
|
EmitOutboundMarshal (il, targetMethod.ReturnType, sig.ReturnType); |
|
il.Emit (OpCodes.Ret); |
|
|
|
return trampolineIn.CreateDelegate (typeInfo.VTableDelegateTypes [vtableIndex]); |
|
} |
|
|
|
protected virtual MethodInfo FindManagedOverrideTarget (Type wrapper, MethodInfo interfaceMethod) |
|
{ |
|
if (interfaceMethod == null) |
|
return null; |
|
|
|
var possibleMembers = wrapper.FindMembers (MemberTypes.Method, BindingFlags.Public | BindingFlags.NonPublic | |
|
BindingFlags.Instance | BindingFlags.Static, vtable_override_filter, interfaceMethod); |
|
|
|
if (possibleMembers.Length > 1) |
|
throw new InvalidProgramException ("More than one possible override found when binding virtual method: " + interfaceMethod.Name); |
|
else if (possibleMembers.Length == 0) |
|
return null; |
|
|
|
return (MethodInfo)possibleMembers [0]; |
|
} |
|
|
|
/** |
|
* Defines a new MethodBuilder with the same signature as the passed MethodInfo |
|
*/ |
|
protected virtual MethodBuilder GetMethodBuilder (CppTypeInfo typeInfo, MethodInfo interfaceMethod) |
|
{ |
|
var parameterTypes = ReflectionHelper.GetMethodParameterTypes (interfaceMethod); |
|
var methodBuilder = typeInfo.emit_info.type_builder.DefineMethod (interfaceMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual, |
|
interfaceMethod.ReturnType, parameterTypes); |
|
ReflectionHelper.ApplyMethodParameterAttributes (interfaceMethod, methodBuilder, false); |
|
return methodBuilder; |
|
} |
|
|
|
/** |
|
* Defines a new MethodBuilder that calls the specified C++ (non-virtual) method using its mangled name |
|
*/ |
|
protected virtual MethodBuilder GetPInvokeForMethod (CppTypeInfo typeInfo, PInvokeSignature sig) |
|
{ |
|
var entryPoint = sig.Name; |
|
if (entryPoint == null) |
|
throw new NotSupportedException ("Could not mangle method name."); |
|
|
|
string lib; |
|
if (IsInline (sig.OrigMethod) && typeInfo.Library.InlineMethodPolicy == InlineMethods.SurrogateLib) |
|
lib = typeInfo.Library.Name + "-inline"; |
|
else |
|
lib = typeInfo.Library.Name; |
|
|
|
var builder = typeInfo.emit_info.type_builder.DefinePInvokeMethod (entryPoint, lib, entryPoint, |
|
MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.PinvokeImpl, |
|
CallingConventions.Standard, sig.ReturnType, sig.ParameterTypes.ToArray (), |
|
sig.CallingConvention.Value, CharSet.Ansi); |
|
builder.SetImplementationFlags (builder.GetMethodImplementationFlags () | MethodImplAttributes.PreserveSig); |
|
ReflectionHelper.ApplyMethodParameterAttributes (sig.OrigMethod, builder, true); |
|
return builder; |
|
} |
|
|
|
/** |
|
* Emits the IL to load the correct delegate instance and/or retrieve the MethodInfo from the VTable |
|
* for a C++ virtual call. |
|
*/ |
|
protected virtual MethodInfo EmitPrepareVirtualCall (CppTypeInfo typeInfo, LocalBuilder cppInstancePtr, int vtableIndex) |
|
{ |
|
var il = typeInfo.emit_info.current_il; |
|
var vtableDelegateType = typeInfo.VTableDelegateTypes [vtableIndex]; |
|
var getDelegate = typeinfo_adjvcall.MakeGenericMethod (vtableDelegateType); |
|
|
|
// this._typeInfo.GetAdjustedVirtualCall<T> (cppInstancePtr, vtableIndex); |
|
il.Emit (OpCodes.Ldarg_0); |
|
il.Emit (OpCodes.Ldfld, typeInfo.emit_info.typeinfo_field); |
|
il.Emit (OpCodes.Ldloc_S, cppInstancePtr); |
|
il.Emit (OpCodes.Ldc_I4, vtableIndex); |
|
il.Emit (OpCodes.Callvirt, getDelegate); |
|
|
|
return ReflectionHelper.GetMethodInfoForDelegate (vtableDelegateType); |
|
} |
|
|
|
/** |
|
* Emits IL to allocate the memory for a new instance of the C++ class. |
|
* To complete method, emit OpCodes.Ret. |
|
*/ |
|
protected virtual void EmitManagedAlloc (CppTypeInfo typeInfo, MethodInfo interfaceMethod) |
|
{ |
|
var il = typeInfo.emit_info.current_il; |
|
|
|
// this._typeInfo.NativeSize |
|
il.Emit (OpCodes.Ldarg_0); |
|
il.Emit (OpCodes.Ldfld, typeInfo.emit_info.typeinfo_field); |
|
|
|
if (typeInfo.WrapperType != null && interfaceMethod.GetParameters ().Any ()) { |
|
// load managed wrapper |
|
il.Emit (OpCodes.Ldarg_1); |
|
il.Emit (OpCodes.Newobj, cppip_fromtype_managed); |
|
} else { |
|
il.Emit (OpCodes.Callvirt, typeinfo_nativesize); |
|
il.Emit (OpCodes.Newobj, cppip_fromsize); |
|
} |
|
} |
|
|
|
protected virtual void EmitConstruct (CppTypeInfo typeInfo, MethodInfo nativeMethod, PInvokeSignature psig, |
|
LocalBuilder cppInstancePtr, LocalBuilder nativePtr) |
|
{ |
|
Debug.Assert (psig.Type == MethodType.NativeCtor); |
|
var il = typeInfo.emit_info.current_il; |
|
|
|
EmitNativeCall (typeInfo, nativeMethod, psig, nativePtr); |
|
|
|
if (cppInstancePtr != null && psig.OrigMethod.ReturnType == typeof (CppInstancePtr)) { |
|
EmitInitVTable (typeInfo, cppInstancePtr); |
|
il.Emit (OpCodes.Ldloc_S, cppInstancePtr); |
|
|
|
} else if (psig.OrigMethod.DeclaringType.GetInterfaces ().Any (i => i.IsGenericType && i.GetGenericTypeDefinition () == typeof (ICppClassOverridable<>))) { |
|
throw new InvalidProgramException ("In ICppClassOverridable, native constructors must take as first argument and return CppInstancePtr"); |
|
} |
|
} |
|
|
|
protected virtual void EmitDestruct (CppTypeInfo typeInfo, MethodInfo nativeMethod, PInvokeSignature psig, |
|
LocalBuilder cppInstancePtr, LocalBuilder nativePtr) |
|
{ |
|
Debug.Assert (psig.Type == MethodType.NativeDtor); |
|
var il = typeInfo.emit_info.current_il; |
|
|
|
// we don't do anything if the object wasn't managed alloc |
|
if (cppInstancePtr == null) |
|
return; |
|
|
|
EmitCheckManagedAlloc (il, cppInstancePtr); |
|
|
|
EmitResetVTable (typeInfo, cppInstancePtr); |
|
EmitNativeCall (typeInfo, nativeMethod, psig, nativePtr); |
|
} |
|
|
|
/** |
|
* Emits IL to call the native method. nativeMethod should be either a method obtained by |
|
* GetPInvokeForMethod or the MethodInfo of a vtable method. |
|
* To complete method, emit OpCodes.Ret. |
|
*/ |
|
protected virtual void EmitNativeCall (CppTypeInfo typeInfo, MethodInfo nativeMethod, PInvokeSignature psig, LocalBuilder nativePtr) |
|
{ |
|
var il = typeInfo.emit_info.current_il; |
|
var interfaceMethod = psig.OrigMethod; |
|
var interfaceArgs = interfaceMethod.GetParameters (); |
|
|
|
int argLoadStart = 1; // For static methods, just strip off arg0 (.net this pointer) |
|
if (!IsStatic (interfaceMethod)) |
|
{ |
|
argLoadStart = 2; // For instance methods, strip off CppInstancePtr and pass the corresponding IntPtr |
|
il.Emit (OpCodes.Ldloc_S, nativePtr); |
|
} |
|
|
|
// load and marshal arguments |
|
for (int i = argLoadStart; i <= interfaceArgs.Length; i++) { |
|
il.Emit (OpCodes.Ldarg, i); |
|
EmitOutboundMarshal (il, interfaceArgs [i - 1].ParameterType, psig.ParameterTypes [i - 1]); |
|
} |
|
|
|
il.Emit (OpCodes.Call, nativeMethod); |
|
|
|
// Marshal return value |
|
if (psig.Type != MethodType.NativeCtor) |
|
EmitInboundMarshal (il, psig.ReturnType, interfaceMethod.ReturnType); |
|
} |
|
|
|
|
|
public virtual PInvokeSignature GetPInvokeSignature (CppTypeInfo typeInfo, MethodInfo method) |
|
{ |
|
var methodType = GetMethodType (typeInfo, method); |
|
var parameters = method.GetParameters (); |
|
var pinvokeTypes = new List<Type> (parameters.Length); |
|
|
|
foreach (var pi in parameters) { |
|
pinvokeTypes.Add (ToPInvokeType (pi.ParameterType, pi)); |
|
} |
|
|
|
return new PInvokeSignature { |
|
OrigMethod = method, |
|
Name = GetMangledMethodName (typeInfo, method), |
|
Type = methodType, |
|
CallingConvention = GetCallingConvention (method), |
|
ParameterTypes = pinvokeTypes, |
|
ReturnType = methodType == MethodType.NativeCtor? typeof (void) : |
|
ToPInvokeType (method.ReturnType, method.ReturnTypeCustomAttributes) |
|
}; |
|
} |
|
|
|
public virtual Type ToPInvokeType (Type t, ICustomAttributeProvider icap) |
|
{ |
|
if (t == typeof (bool)) { |
|
return typeof (byte); |
|
|
|
} else if (typeof (ICppObject).IsAssignableFrom (t)) { |
|
|
|
if (IsByVal (icap)) { |
|
|
|
return GetTypeInfo (t).NativeLayout; |
|
|
|
} else { // by ref |
|
|
|
return typeof (IntPtr); |
|
} |
|
} |
|
|
|
return t; |
|
} |
|
|
|
// The above should return parameter/return types that |
|
// correspond with the marshalling implemented in the next 2 methods. |
|
|
|
// This method marshals from managed -> C++ |
|
// The value it is marshaling will be on the stack. |
|
protected virtual void EmitOutboundMarshal (ILGenerator il, Type managedType, Type targetType) |
|
{ |
|
var nextArg = il.DefineLabel (); |
|
|
|
// FIXME: Why is this needed ? |
|
// auto marshal bool to C++ bool type (0 = false , 1 = true ) |
|
if (managedType.Equals (typeof (bool))) { |
|
Label isTrue = il.DefineLabel (); |
|
Label done = il.DefineLabel (); |
|
il.Emit (OpCodes.Brtrue, isTrue); |
|
il.Emit (OpCodes.Ldc_I4_0); |
|
il.Emit (OpCodes.Br, done); |
|
il.MarkLabel (isTrue); |
|
il.Emit (OpCodes.Ldc_I4_1); |
|
il.MarkLabel (done); |
|
//il.Emit (OpCodes.Conv_I1); |
|
} |
|
|
|
// auto marshal ICppObject |
|
if (typeof (ICppObject).IsAssignableFrom (managedType)) { |
|
|
|
var nullCase = il.DefineLabel (); |
|
var param = il.DeclareLocal (typeof (CppInstancePtr)); |
|
|
|
// check for null |
|
il.Emit (OpCodes.Dup); |
|
il.Emit (OpCodes.Brfalse_S, nullCase); |
|
|
|
// FIXME: ICppObject could be implemented by a value type |
|
il.Emit (OpCodes.Callvirt, cppobj_native); |
|
il.Emit (OpCodes.Stloc, param); |
|
|
|
if (targetType == typeof (IntPtr)) { // by ref |
|
il.Emit (OpCodes.Ldloca, param); |
|
il.Emit (OpCodes.Call, cppip_native); |
|
|
|
il.Emit (OpCodes.Br_S, nextArg); |
|
|
|
il.MarkLabel (nullCase); |
|
// Null case |
|
il.Emit (OpCodes.Pop); |
|
il.Emit (OpCodes.Ldsfld, intptr_zero); |
|
|
|
} else { // by val |
|
|
|
var val = il.DeclareLocal (targetType); |
|
il.Emit (OpCodes.Ldloca, val); // dest |
|
|
|
il.Emit (OpCodes.Ldloca, param); |
|
il.Emit (OpCodes.Call, cppip_native); // src |
|
|
|
il.Emit (OpCodes.Cpobj, targetType); |
|
il.Emit (OpCodes.Ldloc, val); |
|
|
|
il.Emit (OpCodes.Br_S, nextArg); |
|
|
|
il.MarkLabel (nullCase); |
|
// Null case |
|
il.Emit (OpCodes.Ldstr, "Cannot pass null object of type '" + managedType + "' to c++ by value"); |
|
il.Emit (OpCodes.Newobj, typeof (ArgumentException).GetConstructor (new Type[] { typeof(string) })); |
|
il.Emit (OpCodes.Throw); |
|
} |
|
} |
|
|
|
il.MarkLabel (nextArg); |
|
} |
|
|
|
|
|
// This method marshals from C++ -> managed |
|
// The value it is marshaling will be on the stack. |
|
protected virtual void EmitInboundMarshal (ILGenerator il, Type nativeType, Type targetType) |
|
{ |
|
if (nativeType == typeof (void)) |
|
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)) { |
|
|
|
var isNull = il.DefineLabel (); |
|
|
|
// first, we check for null |
|
il.Emit (OpCodes.Dup); |
|
il.Emit (OpCodes.Brfalse_S, isNull); |
|
|
|
il.Emit (OpCodes.Newobj, cppip_fromnative); |
|
il.Emit (OpCodes.Stloc, ptr); |
|
EmitCreateCppObjectFromNative (il, targetType, ptr); |
|
il.Emit (OpCodes.Br_S, next); |
|
|
|
il.MarkLabel (isNull); |
|
il.Emit (OpCodes.Pop); |
|
il.Emit (OpCodes.Ldnull); |
|
|
|
} else if (nativeType.IsValueType && typeof (ICppObject).IsAssignableFrom (targetType)) { |
|
// marshal value type -> ICppObject |
|
|
|
// Obviously, we lose all managed overrides if we pass by value, |
|
// but this "slicing" happens in vanilla C++ as well |
|
|
|
il.Emit (OpCodes.Box, nativeType); // structure |
|
|
|
il.Emit (OpCodes.Sizeof, nativeType); |
|
il.Emit (OpCodes.Newobj, cppip_fromsize); |
|
il.Emit (OpCodes.Stloc, ptr); |
|
il.Emit (OpCodes.Ldloca, ptr); |
|
il.Emit (OpCodes.Call, cppip_native); // ptr |
|
|
|
il.Emit (OpCodes.Ldc_I4_0); // fDeleteOld |
|
|
|
il.Emit (OpCodes.Call, marshal_structuretoptr); |
|
EmitCreateCppObjectFromNative (il, targetType, ptr); |
|
} |
|
|
|
il.MarkLabel (next); |
|
} |
|
|
|
// Gets a typeinfo for another ICppObject. |
|
protected virtual CppTypeInfo GetTypeInfo (Type otherWrapperType) |
|
{ |
|
CppTypeInfo info; |
|
if (wrapper_to_typeinfo.TryGetValue (otherWrapperType, out info)) |
|
return info; |
|
|
|
// pass a "dummy" type info to subclass ctor to trigger the creation of the real one |
|
try { |
|
Activator.CreateInstance (otherWrapperType, (CppTypeInfo)(new DummyCppTypeInfo ())); |
|
|
|
} catch (MissingMethodException) { |
|
|
|
throw new InvalidProgramException (string.Format ("Type `{0}' implements ICppObject but does not contain a public constructor that takes CppTypeInfo", otherWrapperType)); |
|
} |
|
|
|
return wrapper_to_typeinfo [otherWrapperType]; |
|
} |
|
|
|
|
|
|
|
// Expects cppip = CppInstancePtr local |
|
protected virtual void EmitCreateCppObjectFromNative (ILGenerator il, Type targetType, LocalBuilder cppip) |
|
{ |
|
CppTypeInfo targetTypeInfo = null; |
|
if (targetType == typeof (ICppObject)) |
|
targetType = typeof (CppInstancePtr); |
|
|
|
// check for a native constructor (i.e. a public ctor in the wrapper that takes CppInstancePtr) |
|
if (typeof (ICppObject).IsAssignableFrom (targetType)) { |
|
var ctor = targetType.GetConstructor (BindingFlags.ExactBinding | BindingFlags.Public | BindingFlags.Instance, null, new Type [] { typeof (CppInstancePtr) }, null); |
|
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) |
|
// ..but ToManaged is only called if there's a vtable (FIXME: otherwise we rewrap) |
|
|
|
var hasWrapper = il.DefineLabel (); |
|
|
|
if (targetTypeInfo == null) |
|
targetTypeInfo = GetTypeInfo (targetType); // FIXME: woof. do we really have to do this? |
|
if (targetTypeInfo != null && targetTypeInfo.VirtualMethods.Any ()) { |
|
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); |
|
|
|
} else if (targetType.IsValueType) { |
|
|
|
// (targetType)Marshal.PtrToStructure (CppInstancePtr.Native, typeof (targetType)) |
|
il.Emit (OpCodes.Ldloca, cppip); |
|
il.Emit (OpCodes.Call, cppip_native); |
|
il.Emit (OpCodes.Ldtoken, targetType); |
|
il.Emit (OpCodes.Call, type_gettypefromhandle); |
|
il.Emit (OpCodes.Call, marshal_ptrtostructure); |
|
il.Emit (OpCodes.Unbox_Any, targetType); |
|
} |
|
} |
|
|
|
/** |
|
* Emits IL to load the VTable object onto the stack. |
|
*/ |
|
protected virtual void EmitLoadVTable (CppTypeInfo typeInfo) |
|
{ |
|
var il = typeInfo.emit_info.current_il; |
|
|
|
// this._typeInfo.VTable |
|
il.Emit (OpCodes.Ldarg_0); |
|
il.Emit (OpCodes.Ldfld, typeInfo.emit_info.typeinfo_field); |
|
il.Emit (OpCodes.Callvirt, typeinfo_vtable); |
|
} |
|
|
|
/** |
|
* Emits IL to set the vtable pointer of the instance (if class has a vtable). |
|
* This should usually happen in the managed wrapper of the C++ instance constructor. |
|
*/ |
|
protected virtual void EmitInitVTable (CppTypeInfo typeInfo, LocalBuilder cppip) |
|
{ |
|
var il = typeInfo.emit_info.current_il; |
|
|
|
// this._typeInfo.VTable.InitInstance (cppInstancePtr); |
|
EmitLoadVTable (typeInfo); |
|
il.Emit (OpCodes.Ldloca_S, cppip); |
|
EmitCallVTableMethod (typeInfo, vtable_initinstance, 2, false); |
|
} |
|
|
|
protected virtual void EmitResetVTable (CppTypeInfo typeInfo, LocalBuilder cppip) |
|
{ |
|
var il = typeInfo.emit_info.current_il; |
|
|
|
// this._typeInfo.VTable.ResetInstance (cppInstancePtr); |
|
EmitLoadVTable (typeInfo); |
|
il.Emit (OpCodes.Ldloc_S, cppip); |
|
EmitCallVTableMethod (typeInfo, vtable_resetinstance, 2, false); |
|
} |
|
|
|
/** |
|
* A utility function to emit the IL for a vtable-dependant operation. |
|
* In other words, classes with no virtual methods will not have vtables, |
|
* so this method emits code to check for that and either throw an exception |
|
* or do nothing if no vtable exists. To use, push the arguments to the method you |
|
* want to call and pass the stackHeight for the call. If no vtable exists, this method |
|
* will emit code to pop the arguments off the stack. |
|
*/ |
|
protected virtual void EmitCallVTableMethod (CppTypeInfo typeInfo, MethodInfo method, int stackHeight, bool throwOnNoVTable) |
|
{ |
|
var il = typeInfo.emit_info.current_il; |
|
|
|
// prepare a jump; do not call vtable method if no vtable |
|
var noVirt = il.DefineLabel (); |
|
var dontPushOrThrow = il.DefineLabel (); |
|
|
|
EmitLoadVTable (typeInfo); |
|
il.Emit (OpCodes.Brfalse_S, noVirt); // if (vtableInfo == null) goto noVirt |
|
|
|
il.Emit (OpCodes.Callvirt, method); // call method |
|
il.Emit (OpCodes.Br_S, dontPushOrThrow); // goto dontPushOrThrow |
|
|
|
il.MarkLabel (noVirt); |
|
// noVirt: |
|
// since there is no vtable, we did not make the method call. |
|
// pop arguments |
|
for (int i = 0; i < stackHeight; i++) |
|
il.Emit (OpCodes.Pop); |
|
|
|
// if the method was supposed to return a value, we must |
|
// still push something onto the stack |
|
// FIXME: This is a kludge. What about value types? |
|
if (!method.ReturnType.Equals (typeof (void))) |
|
il.Emit (OpCodes.Ldnull); |
|
|
|
if (throwOnNoVTable) { |
|
il.Emit (OpCodes.Ldstr, "Native class has no VTable."); |
|
il.Emit (OpCodes.Newobj, typeof (InvalidOperationException).GetConstructor(new Type[] {typeof (string)})); |
|
il.Emit (OpCodes.Throw); |
|
} |
|
|
|
il.MarkLabel (dontPushOrThrow); |
|
} |
|
|
|
protected virtual void EmitLoadInstancePtr (ILGenerator il, Type firstParamType, out LocalBuilder cppip, |
|
out LocalBuilder native) |
|
{ |
|
cppip = null; |
|
native = null; |
|
|
|
il.Emit (OpCodes.Ldarg_1); |
|
if (firstParamType.Equals (typeof (CppInstancePtr))) { |
|
cppip = il.DeclareLocal (typeof (CppInstancePtr)); |
|
native = il.DeclareLocal (typeof (IntPtr)); |
|
il.Emit (OpCodes.Stloc_S, cppip); |
|
il.Emit (OpCodes.Ldloca_S, cppip); |
|
il.Emit (OpCodes.Call, cppip_native); |
|
il.Emit (OpCodes.Stloc_S, native); |
|
} else if (firstParamType.Equals (typeof (IntPtr))) { |
|
native = il.DeclareLocal (typeof (IntPtr)); |
|
il.Emit (OpCodes.Stloc_S, native); |
|
} else if (firstParamType.IsByRef) { |
|
native = il.DeclareLocal (firstParamType); |
|
il.Emit (OpCodes.Stloc_S, native); |
|
} else |
|
throw new ArgumentException ("First argument to non-static C++ method must be byref, IntPtr or CppInstancePtr."); |
|
} |
|
|
|
protected virtual void EmitCheckManagedAlloc (ILGenerator il, LocalBuilder cppip) |
|
{ |
|
// make sure we were allocated by managed code |
|
// if not, return |
|
var managedAlloc = il.DefineLabel (); |
|
|
|
il.Emit (OpCodes.Ldloca_S, cppip); |
|
il.Emit (OpCodes.Call, cppip_managedalloc); |
|
il.Emit (OpCodes.Brtrue_S, managedAlloc); |
|
il.Emit (OpCodes.Ret); |
|
il.MarkLabel (managedAlloc); |
|
} |
|
|
|
/** |
|
* throw ObjectDisposedException if we have a null pointer for native |
|
* however, allow destructor to be called even if we're disposed (just return immediately) |
|
*/ |
|
protected virtual void EmitCheckDisposed (ILGenerator il, LocalBuilder native, MethodType methodType) |
|
{ |
|
var validRef = il.DefineLabel (); |
|
|
|
il.Emit (OpCodes.Ldloc_S, native); |
|
il.Emit (OpCodes.Brtrue_S, validRef); |
|
|
|
if (methodType == MethodType.NativeDtor) { |
|
il.Emit (OpCodes.Ret); |
|
il.MarkLabel (validRef); |
|
} else { |
|
il.Emit (OpCodes.Ldstr, String.Empty); |
|
il.Emit (OpCodes.Newobj, typeof (ObjectDisposedException).GetConstructor (new Type[] { typeof(string) })); |
|
il.Emit (OpCodes.Throw); |
|
il.MarkLabel (validRef); |
|
} |
|
} |
|
} |
|
|
|
}
|
|
|