diff --git a/CPPPOC/Main.cs b/CPPPOC/Main.cs index ea3cf4e9..298fb1d5 100644 --- a/CPPPOC/Main.cs +++ b/CPPPOC/Main.cs @@ -13,7 +13,7 @@ namespace CPPPOC public static void Main(string[] args) { // bind all wrapper classes to their native implementations - CppLibrary cppTest = new CppLibrary("CPPTest", new Mono.VisualC.Interop.ABI.Itanium()); + CppLibrary cppTest = new CppLibrary("CPPTest", new Mono.VisualC.Interop.ABI.ItaniumAbi()); CSimpleClass.Bind(cppTest); CSimpleClass csc1 = new CSimpleClass(CreateCSimpleSubClass(10)); diff --git a/Mono.VisualC.Interop/ABI/CppAbi.cs b/Mono.VisualC.Interop/ABI/CppAbi.cs index 7f6c2663..a5509dd4 100644 --- a/Mono.VisualC.Interop/ABI/CppAbi.cs +++ b/Mono.VisualC.Interop/ABI/CppAbi.cs @@ -31,11 +31,22 @@ namespace Mono.VisualC.Interop.ABI { protected FieldBuilder vtableField; protected ILGenerator ctorIL; - public virtual Iface ImplementClass (ModuleBuilder implModule, Type wrapperType, string lib, string className) + // default settings that subclasses can override: + protected MakeVTableDelegate makeVTableMethod = VTable.DefaultImplementation; + protected MemberFilter vtableOverrideFilter = VTable.BindToSignatureAndAttribute; + + + private struct EmptyNativeLayout { } + public Iface ImplementClass (Type wrapperType, string lib, string className) + { + return this.ImplementClass (wrapperType, lib, className); + } + + public virtual Iface ImplementClass (Type wrapperType, string lib, string className) where NLayout : struct //where Iface : ICppClassInstantiatable or ICppClassOverridable { - this.implModule = implModule; + this.implModule = CppLibrary.interopModule; this.library = lib; this.className = className; this.interfaceType = typeof (Iface); @@ -44,6 +55,15 @@ namespace Mono.VisualC.Interop.ABI { DefineImplType (); + var properties = ( // 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 + ); + var methods = ( // get all methods defined on the interface from method in interfaceType.GetMethods () orderby method.MetadataToken @@ -57,9 +77,9 @@ namespace Mono.VisualC.Interop.ABI { var managedOverrides = from method in methods where Modifiers.IsVirtual (method) orderby method.MetadataToken - select GetManagedOverrideTrampoline (method, VTable.BindToSignatureAndAttribute); + select GetManagedOverrideTrampoline (method, vtableOverrideFilter); - vtable = MakeVTable (managedOverrides.ToArray ()); + vtable = makeVTableMethod (managedOverrides.ToArray ()); // Implement all methods int vtableIndex = 0; @@ -75,9 +95,8 @@ namespace Mono.VisualC.Interop.ABI { } // Implement all properties - foreach (var property in interfaceType.GetProperties ()) - DefineFieldProperty (property); - + foreach (var property in properties) + DefineProperty (property); ctorIL.Emit (OpCodes.Ret); return (Iface)Activator.CreateInstance (implType.CreateType (), vtable); @@ -110,11 +129,6 @@ namespace Mono.VisualC.Interop.ABI { } } - protected virtual VTable MakeVTable (Delegate[] overrides) - { - return new VTableManaged (implModule, overrides); - } - // The members below must be implemented for a given C++ ABI: public abstract string GetMangledMethodName (MethodInfo methodInfo); @@ -147,9 +161,17 @@ namespace Mono.VisualC.Interop.ABI { // 1. Generate managed trampoline to call native method MethodBuilder trampoline = GetMethodBuilder (interfaceMethod); + ILGenerator il = trampoline.GetILGenerator (); - if (methodType == MethodType.ManagedAlloc) { + if (methodType == MethodType.NoOp) { + // return NULL if method is supposed to return a value + // TODO: this will make value types explode? + if (!interfaceMethod.ReturnType.Equals (typeof (void))) + il.Emit (OpCodes.Ldnull); + il.Emit (OpCodes.Ret); + return trampoline; + } else if (methodType == MethodType.ManagedAlloc) { EmitManagedAlloc (il); il.Emit (OpCodes.Ret); return trampoline; @@ -187,7 +209,7 @@ namespace Mono.VisualC.Interop.ABI { return trampoline; } - protected virtual PropertyBuilder DefineFieldProperty (PropertyInfo property) + protected virtual PropertyBuilder DefineProperty (PropertyInfo property) { if (property.CanWrite) throw new InvalidProgramException ("Properties in C++ interface must be read-only."); @@ -196,29 +218,35 @@ namespace Mono.VisualC.Interop.ABI { string methodName = imethod.Name; string propName = property.Name; Type retType = imethod.ReturnType; - - if ((!retType.IsGenericType) || (!retType.GetGenericTypeDefinition ().Equals (typeof (CppField<>)))) + FieldBuilder fieldData; + + // C++ interface properties are either to return the VTable or to access C++ fields + if (retType.IsGenericType && retType.GetGenericTypeDefinition ().Equals (typeof (CppField<>))) { + // define a new field for the property + fieldData = implType.DefineField ("__" + propName + "_Data", retType, FieldAttributes.InitOnly | FieldAttributes.Private); + + // init our field data with a new instance of CppField + // first, get field offset + ctorIL.Emit (OpCodes.Ldarg_0); + + /* TODO: Code prolly should not emit hardcoded offsets n such, in case we end up saving these assemblies in the future. + * Something more like this perhaps? (need to figure out how to get field offset padding into this) + * ctorIL.Emit(OpCodes.Ldtoken, nativeLayout); + * ctorIL.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")); + * ctorIL.Emit(OpCodes.Ldstr, propName); + * ctorIL.Emit(OpCodes.Call, typeof(Marshal).GetMethod("OffsetOf")); + */ + int fieldOffset = ((int)Marshal.OffsetOf (layoutType, propName)) + FieldOffsetPadding; + ctorIL.Emit (OpCodes.Ldc_I4, fieldOffset); + ctorIL.Emit (OpCodes.Newobj, retType.GetConstructor (new Type[] { typeof(int) })); + + ctorIL.Emit (OpCodes.Stfld, fieldData); + } else if (retType.Equals (typeof (VTable))) + fieldData = vtableField; + else throw new InvalidProgramException ("Properties in C++ interface can only be of type CppField."); PropertyBuilder fieldProp = implType.DefineProperty (propName, PropertyAttributes.None, retType, Type.EmptyTypes); - FieldBuilder fieldData = implType.DefineField ("__" + propName + "_Data", retType, FieldAttributes.InitOnly | FieldAttributes.Private); - - // init our field data with a new instance of CppField - // first, get field offset - ctorIL.Emit (OpCodes.Ldarg_0); - - /* TODO: Code prolly should not emit hardcoded offsets n such, in case we end up saving these assemblies in the future. - * Something more like this perhaps? (need to figure out how to get field offset padding into this) - * ctorIL.Emit(OpCodes.Ldtoken, nativeLayout); - * ctorIL.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")); - * ctorIL.Emit(OpCodes.Ldstr, propName); - * ctorIL.Emit(OpCodes.Call, typeof(Marshal).GetMethod("OffsetOf")); - */ - int fieldOffset = ((int)Marshal.OffsetOf (layoutType, propName)) + FieldOffsetPadding; - ctorIL.Emit (OpCodes.Ldc_I4, fieldOffset); - ctorIL.Emit (OpCodes.Newobj, retType.GetConstructor (new Type[] { typeof(int) })); - - ctorIL.Emit (OpCodes.Stfld, fieldData); MethodAttributes methodAttr = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig; MethodBuilder fieldGetter = implType.DefineMethod (methodName, methodAttr, retType, Type.EmptyTypes); @@ -252,6 +280,7 @@ namespace Mono.VisualC.Interop.ABI { DynamicMethod trampolineIn = new DynamicMethod (wrapperType.Name + "_" + interfaceMethod.Name + "_FromNative", interfaceMethod.ReturnType, parameterTypes, typeof (CppInstancePtr).Module, true); + Util.ApplyMethodParameterAttributes (interfaceMethod, trampolineIn); ILGenerator il = trampolineIn.GetILGenerator (); // for static methods: @@ -301,7 +330,7 @@ namespace Mono.VisualC.Interop.ABI { Type[] parameterTypes = Util.GetMethodParameterTypes (interfaceMethod, false); MethodBuilder methodBuilder = implType.DefineMethod (interfaceMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual, interfaceMethod.ReturnType, parameterTypes); - + Util.ApplyMethodParameterAttributes (interfaceMethod, methodBuilder); return methodBuilder; } diff --git a/Mono.VisualC.Interop/ABI/Impl/Itanium.cs b/Mono.VisualC.Interop/ABI/Impl/Itanium.cs index e082429f..601942c5 100644 --- a/Mono.VisualC.Interop/ABI/Impl/Itanium.cs +++ b/Mono.VisualC.Interop/ABI/Impl/Itanium.cs @@ -6,9 +6,9 @@ using System.Runtime.InteropServices; namespace Mono.VisualC.Interop.ABI { - public class Itanium : CppAbi { + public class ItaniumAbi : CppAbi { - public Itanium() {} + public ItaniumAbi() {} public override CallingConvention DefaultCallingConvention { get { diff --git a/Mono.VisualC.Interop/ABI/Impl/VirtualOnlyAbi.cs b/Mono.VisualC.Interop/ABI/Impl/VirtualOnlyAbi.cs new file mode 100644 index 00000000..ea620bfa --- /dev/null +++ b/Mono.VisualC.Interop/ABI/Impl/VirtualOnlyAbi.cs @@ -0,0 +1,38 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using Mono.VisualC.Interop.ABI; + +namespace Mono.VisualC.Interop { + + public class VirtualOnlyAbi : CppAbi { + + public VirtualOnlyAbi (MakeVTableDelegate makeVTable, MemberFilter vtableOverrideFilter) + { + this.makeVTableMethod = makeVTable; + this.vtableOverrideFilter = vtableOverrideFilter; + } + public VirtualOnlyAbi () { } + + public override MethodType GetMethodType (MethodInfo imethod) + { + MethodType defaultType = base.GetMethodType (imethod); + if (defaultType == MethodType.NativeCtor || defaultType == MethodType.NativeDtor) + return MethodType.NoOp; + return defaultType; + } + + public override string GetMangledMethodName (MethodInfo methodInfo) + { + throw new NotSupportedException ("Name mangling is not supported by this class. All C++ interface methods must be declared virtual."); + } + + public override CallingConvention DefaultCallingConvention { + get { + throw new NotSupportedException ("This class does not support this property."); + } + } + } +} + diff --git a/Mono.VisualC.Interop/ABI/MethodType.cs b/Mono.VisualC.Interop/ABI/MethodType.cs index 9df6b9d9..91df1ca1 100644 --- a/Mono.VisualC.Interop/ABI/MethodType.cs +++ b/Mono.VisualC.Interop/ABI/MethodType.cs @@ -1,6 +1,7 @@ using System; namespace Mono.VisualC.Interop { public enum MethodType { + NoOp, Native, NativeCtor, NativeDtor, diff --git a/Mono.VisualC.Interop/ABI/VTable.cs b/Mono.VisualC.Interop/ABI/VTable.cs index c0c985fc..8844bf87 100644 --- a/Mono.VisualC.Interop/ABI/VTable.cs +++ b/Mono.VisualC.Interop/ABI/VTable.cs @@ -15,8 +15,19 @@ using System.Reflection.Emit; using System.Runtime.InteropServices; namespace Mono.VisualC.Interop.ABI { + public delegate VTable MakeVTableDelegate (Delegate[] overrides); + public abstract class VTable : IDisposable { + + // The COM-interop-based implemenation is the default because it offers better + // performance (I think?) than the fully-managed implementation. + public static MakeVTableDelegate DefaultImplementation = VTableManaged.Implementation; + protected IntPtr basePtr, vtPtr; + + // This is a bit of a misnomer since the Length of the array will be equal + // to the number of entries in the vtable; entries that aren't overridden + // will be NULL. protected Delegate[] overrides; public virtual int EntryCount { @@ -29,7 +40,7 @@ namespace Mono.VisualC.Interop.ABI { public abstract MethodInfo PrepareVirtualCall (MethodInfo target, ILGenerator callsite, FieldInfo vtableField, LocalBuilder native, int vtableIndex); - // Creates a new VTable + // Subclasses must allocate vtPtr! public VTable (Delegate[] overrides) { this.overrides = overrides; @@ -54,17 +65,17 @@ namespace Mono.VisualC.Interop.ABI { public virtual void InitInstance (IntPtr instance) { - if (basePtr == IntPtr.Zero) { + if (basePtr == IntPtr.Zero) { basePtr = Marshal.ReadIntPtr (instance); + if ((basePtr != IntPtr.Zero) && (basePtr != vtPtr)) { + int offset = 0; + for (int i = 0; i < EntryCount; i++) { - int offset = 0; - for (int i = 0; i < EntryCount; i++) { - - IntPtr vtEntryPtr = Marshal.ReadIntPtr (vtPtr, offset); - if (vtEntryPtr == IntPtr.Zero) - Marshal.WriteIntPtr (vtPtr, offset, Marshal.ReadIntPtr (basePtr, offset)); + if (overrides [i] == null) + Marshal.WriteIntPtr (vtPtr, offset, Marshal.ReadIntPtr (basePtr, offset)); - offset += EntrySize; + offset += EntrySize; + } } } @@ -89,7 +100,7 @@ namespace Mono.VisualC.Interop.ABI { } // TODO: This WON'T usually be called because VTables are associated with classes - // and managed C++ class wrappers are staticly held? + // (not instances) and managed C++ class wrappers are staticly held? public void Dispose () { Dispose (true); diff --git a/Mono.VisualC.Interop/ABI/VTableCOM.cs b/Mono.VisualC.Interop/ABI/VTableCOM.cs index 0384eee9..60645d3d 100644 --- a/Mono.VisualC.Interop/ABI/VTableCOM.cs +++ b/Mono.VisualC.Interop/ABI/VTableCOM.cs @@ -18,7 +18,13 @@ using System.Runtime.InteropServices; namespace Mono.VisualC.Interop.ABI { public class VTableCOM : VTable { - public VTableCOM (Delegate[] entries) : base(entries) + private static VTable MakeVTableCOM (Delegate[] entries) + { + return new VTableCOM (entries); + } + public static MakeVTableDelegate Implementation = MakeVTableCOM; + + private VTableCOM (Delegate[] entries) : base(entries) { int managedOverrides = (from entry in entries where entry != null diff --git a/Mono.VisualC.Interop/ABI/VTableManaged.cs b/Mono.VisualC.Interop/ABI/VTableManaged.cs index c90cbcef..d4dc8f38 100644 --- a/Mono.VisualC.Interop/ABI/VTableManaged.cs +++ b/Mono.VisualC.Interop/ABI/VTableManaged.cs @@ -18,9 +18,15 @@ namespace Mono.VisualC.Interop.ABI { public class VTableManaged : VTable { private ModuleBuilder implModule; - public VTableManaged (ModuleBuilder implModule, Delegate[] entries) : base(entries) + private static VTable MakeVTableManaged (Delegate[] entries) { - this.implModule = implModule; + return new VTableManaged (entries); + } + public static MakeVTableDelegate Implementation = MakeVTableManaged; + + private VTableManaged (Delegate[] entries) : base(entries) + { + this.implModule = CppLibrary.interopModule; this.vtPtr = Marshal.AllocHGlobal (EntryCount * EntrySize); WriteOverrides (0); @@ -60,7 +66,7 @@ namespace Mono.VisualC.Interop.ABI { } - public T GetDelegateForNative (IntPtr native, int index) where T : class /*Delegate*/ + public virtual T GetDelegateForNative (IntPtr native, int index) where T : class /*Delegate*/ { IntPtr vtable = Marshal.ReadIntPtr (native); if (vtable == vtPtr) // do not return managed overrides diff --git a/Mono.VisualC.Interop/CppInstancePtr.cs b/Mono.VisualC.Interop/CppInstancePtr.cs index 12734e4b..5eb6681a 100644 --- a/Mono.VisualC.Interop/CppInstancePtr.cs +++ b/Mono.VisualC.Interop/CppInstancePtr.cs @@ -8,19 +8,53 @@ // using System; +using System.Reflection.Emit; +using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Diagnostics; + +using Mono.VisualC.Interop.ABI; namespace Mono.VisualC.Interop { public struct CppInstancePtr : ICppObject { private IntPtr ptr; private bool manageMemory; + private static Dictionary implCache = new Dictionary (); + public static CppInstancePtr ForManagedObject (TWrapper managed) + where Iface : ICppClassOverridable + { + object cachedImpl; + Iface impl; + + if (!implCache.TryGetValue (typeof (Iface), out cachedImpl)) + { + // Since we're only using the VTable to allow C++ code to call managed methods, + // there is no advantage to using VTableCOM. Also, VTableCOM is based on this. + VirtualOnlyAbi virtualABI = new VirtualOnlyAbi (VTableManaged.Implementation, VTable.BindToSignature); + impl = virtualABI.ImplementClass (typeof (TWrapper), string.Empty, string.Empty); + implCache.Add (typeof (Iface), impl); + } + else + impl = (Iface)cachedImpl; + + CppInstancePtr instance = impl.Alloc (managed); + impl.ClassVTable.InitInstance ((IntPtr)instance); + + return instance; + } + // Alloc a new C++ instance internal CppInstancePtr (int nativeSize, object managedWrapper) { // Under the hood, we're secretly subclassing this C++ class to store a // handle to the managed wrapper. - ptr = Marshal.AllocHGlobal (nativeSize + Marshal.SizeOf (typeof (IntPtr))); + int allocSize = nativeSize + Marshal.SizeOf (typeof (IntPtr)); + ptr = Marshal.AllocHGlobal (allocSize); + + // zero memory for sanity + byte[] zeroArray = new byte [allocSize]; + Marshal.Copy (zeroArray, 0, ptr, allocSize); IntPtr handlePtr = GetGCHandle (managedWrapper); Marshal.WriteIntPtr (ptr, nativeSize, handlePtr); diff --git a/Mono.VisualC.Interop/CppLibrary.cs b/Mono.VisualC.Interop/CppLibrary.cs index 6568cc5d..2693d4fc 100644 --- a/Mono.VisualC.Interop/CppLibrary.cs +++ b/Mono.VisualC.Interop/CppLibrary.cs @@ -23,8 +23,8 @@ namespace Mono.VisualC.Interop public sealed class CppLibrary { - private static AssemblyBuilder interopAssembly; - private static ModuleBuilder interopModule; + internal static AssemblyBuilder interopAssembly; + internal static ModuleBuilder interopModule; private CppAbi abi; private string name; @@ -64,7 +64,7 @@ namespace Mono.VisualC.Interop where NativeLayout : struct { - return Abi.ImplementClass (interopModule, null, Name, className); + return Abi.ImplementClass (null, Name, className); } // For a class that may have fields and virtual methods to be overridden @@ -74,7 +74,7 @@ namespace Mono.VisualC.Interop where Managed : ICppObject { - return Abi.ImplementClass (interopModule, typeof (Managed), Name, className); + return Abi.ImplementClass (typeof (Managed), Name, className); } // TODO: Define a method for pure virtual classes (no NativeLayout)? diff --git a/Mono.VisualC.Interop/Interfaces.cs b/Mono.VisualC.Interop/Interfaces.cs index a65503d3..b2e49fa0 100644 --- a/Mono.VisualC.Interop/Interfaces.cs +++ b/Mono.VisualC.Interop/Interfaces.cs @@ -8,19 +8,26 @@ // using System; +using Mono.VisualC.Interop.ABI; namespace Mono.VisualC.Interop { public interface ICppObject : IDisposable { - IntPtr Native { get; } + IntPtr Native { get; } } - public interface ICppClassInstantiatable { - CppInstancePtr Alloc (); + public interface ICppClass { + VTable ClassVTable { get; } void Destruct (CppInstancePtr instance); } - public interface ICppClassOverridable where T : ICppObject { + public interface ICppClassInstantiatable : ICppClass { + CppInstancePtr Alloc (); + } + + // It is recommended that managed wrappers implement ICppObject, but + // I'm not making it required so that any arbitrary object can be exposed to + // C++ via CppInstancePtr.ForManagedObject. + public interface ICppClassOverridable : ICppClass /* where T : ICppObject */ { CppInstancePtr Alloc (T managed); - void Destruct (CppInstancePtr instance); } } \ No newline at end of file diff --git a/Mono.VisualC.Interop/Mono.VisualC.Interop.csproj b/Mono.VisualC.Interop/Mono.VisualC.Interop.csproj index 66054745..dc853d6b 100644 --- a/Mono.VisualC.Interop/Mono.VisualC.Interop.csproj +++ b/Mono.VisualC.Interop/Mono.VisualC.Interop.csproj @@ -54,7 +54,6 @@ - @@ -64,6 +63,8 @@ + + diff --git a/Mono.VisualC.Interop/Util.cs b/Mono.VisualC.Interop/Util.cs index 53b08c45..8e43b6a4 100644 --- a/Mono.VisualC.Interop/Util.cs +++ b/Mono.VisualC.Interop/Util.cs @@ -64,5 +64,19 @@ namespace Mono.VisualC.Interop { return parameterTypes; } + + public static void ApplyMethodParameterAttributes (MethodInfo source, MethodBuilder target) + { + ParameterInfo[] parameters = source.GetParameters (); + foreach (var parameter in parameters) + target.DefineParameter (parameter.Position, parameter.Attributes, parameter.Name); + } + + public static void ApplyMethodParameterAttributes (MethodInfo source, DynamicMethod target) + { + ParameterInfo[] parameters = source.GetParameters (); + foreach (var parameter in parameters) + target.DefineParameter (parameter.Position, parameter.Attributes, parameter.Name); + } } }