|
| 1 | +using System; |
| 2 | +using System.IO; |
| 3 | +using System.Reflection; |
| 4 | +using System.Runtime.InteropServices; |
| 5 | +using Mono.Cecil; |
| 6 | +using Mono.Cecil.Cil; |
| 7 | +using ProjectCeilidh.NativeTK.Attributes; |
| 8 | +using ProjectCeilidh.NativeTK.Native; |
| 9 | + |
| 10 | +namespace ProjectCeilidh.NativeTK |
| 11 | +{ |
| 12 | + public class IndirectBindingFactory : BindingFactory |
| 13 | + { |
| 14 | + public override NativeBindingType BindingType => NativeBindingType.Indirect; |
| 15 | + |
| 16 | + public override object CreateBinding(Type contractType) |
| 17 | + { |
| 18 | + if (RuntimeInformation.ProcessArchitecture == Architecture.X86) throw new PlatformNotSupportedException(); |
| 19 | + |
| 20 | + // Get the NativeLibraryLoader for this platform |
| 21 | + var loader = NativeLibraryLoader.GetLibraryLoaderForPlatform(); |
| 22 | + |
| 23 | + if (!contractType.IsInterface) throw new ArgumentException("Type argument must be an interface."); |
| 24 | + |
| 25 | + var libraryAttr = contractType.GetCustomAttribute<NativeLibraryContractAttribute>(); |
| 26 | + |
| 27 | + if (libraryAttr == null) throw new ArgumentException("Type argument must have a NativeLibraryContractAttribute"); |
| 28 | + |
| 29 | + var handle = loader.LoadNativeLibrary(libraryAttr.LibraryName, libraryAttr.Version); |
| 30 | + |
| 31 | + // Create the dynamic assembly which will contain the binding |
| 32 | + var asm = AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition(libraryAttr.LibraryName, libraryAttr.Version), |
| 33 | + "<Module>", ModuleKind.Dll); |
| 34 | + // Create the binding type |
| 35 | + var implTyp = new TypeDefinition("", $"{contractType.Name}Impl", Mono.Cecil.TypeAttributes.Public, asm.MainModule.TypeSystem.Object); |
| 36 | + implTyp.Interfaces.Add(new InterfaceImplementation(asm.MainModule.ImportReference(contractType))); |
| 37 | + asm.MainModule.Types.Add(implTyp); |
| 38 | + |
| 39 | + // Create a default constructor for the binding type |
| 40 | + var implCtor = new MethodDefinition(".ctor", |
| 41 | + Mono.Cecil.MethodAttributes.SpecialName | Mono.Cecil.MethodAttributes.RTSpecialName | Mono.Cecil.MethodAttributes.Public | |
| 42 | + Mono.Cecil.MethodAttributes.HideBySig, asm.MainModule.TypeSystem.Void); |
| 43 | + implTyp.Methods.Add(implCtor); |
| 44 | + // Simple ctor body - load `this`, call `new object()` against it |
| 45 | + var ctorProc = implCtor.Body.GetILProcessor(); |
| 46 | + ctorProc.Emit(OpCodes.Ldarg_0); |
| 47 | + ctorProc.Emit(OpCodes.Call, asm.MainModule.ImportReference(typeof(object).GetConstructor(new Type[0]))); |
| 48 | + ctorProc.Emit(OpCodes.Ret); |
| 49 | + |
| 50 | + // Implement all the methods in the interface |
| 51 | + foreach (var intMethod in contractType.GetMethods()) |
| 52 | + { |
| 53 | + // If the method has a special name, ignore it. This excludes property getters/setters |
| 54 | + if (intMethod.IsSpecialName) continue; |
| 55 | + |
| 56 | + // The method cannot have varargs (this actually /can/ be achieved later, but it's too complicated for now) |
| 57 | + if (intMethod.CallingConvention == CallingConventions.VarArgs) throw new ArgumentException("Type argument cannot contain a method with varargs"); |
| 58 | + |
| 59 | + var intAttr = intMethod.GetCustomAttribute<NativeImportAttribute>(); |
| 60 | + |
| 61 | + if (intAttr == null) throw new ArgumentException($"Type argument contains a method without a NativeImportAttribute ({intMethod.Name})"); |
| 62 | + |
| 63 | + // Create the dynamic method for the implementation |
| 64 | + var meth = new MethodDefinition(intMethod.Name, |
| 65 | + Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Final | |
| 66 | + Mono.Cecil.MethodAttributes.Virtual, |
| 67 | + asm.MainModule.ImportReference(intMethod.ReturnType)); |
| 68 | + implTyp.Methods.Add(meth); |
| 69 | + |
| 70 | + // The body for the dynamic method |
| 71 | + var proc = meth.Body.GetILProcessor(); |
| 72 | + |
| 73 | + // Generate a CallSite for the unmanaged function |
| 74 | + var callSite = new CallSite(asm.MainModule.ImportReference(intMethod.ReturnType)); |
| 75 | + |
| 76 | + switch (intAttr.CallingConvention) |
| 77 | + { |
| 78 | + case CallingConvention.Cdecl: |
| 79 | + callSite.CallingConvention = MethodCallingConvention.C; |
| 80 | + break; |
| 81 | + case CallingConvention.FastCall: |
| 82 | + callSite.CallingConvention = MethodCallingConvention.FastCall; |
| 83 | + break; |
| 84 | + case CallingConvention.Winapi: |
| 85 | + case CallingConvention.StdCall: |
| 86 | + callSite.CallingConvention = MethodCallingConvention.StdCall; |
| 87 | + break; |
| 88 | + case CallingConvention.ThisCall: |
| 89 | + callSite.CallingConvention = MethodCallingConvention.ThisCall; |
| 90 | + break; |
| 91 | + default: |
| 92 | + throw new ArgumentOutOfRangeException(); |
| 93 | + } |
| 94 | + |
| 95 | + var i = 0; |
| 96 | + foreach (var param in intMethod.GetParameters()) |
| 97 | + { |
| 98 | + callSite.Parameters.Add( |
| 99 | + new ParameterDefinition(param.Name, (Mono.Cecil.ParameterAttributes)param.Attributes, |
| 100 | + asm.MainModule.ImportReference(param.ParameterType))); |
| 101 | + meth.Parameters.Add(new ParameterDefinition(param.Name, (Mono.Cecil.ParameterAttributes)param.Attributes, |
| 102 | + asm.MainModule.ImportReference(param.ParameterType))); |
| 103 | + |
| 104 | + proc.Emit(OpCodes.Ldarg, ++i); |
| 105 | + } |
| 106 | + |
| 107 | + // Load the symbol address for this function as a long, then convert to an IntPtr (native int) |
| 108 | + proc.Emit(OpCodes.Ldc_I8, (long)handle.GetSymbolAddress(intAttr.EntryPoint ?? intMethod.Name)); |
| 109 | + proc.Emit(OpCodes.Conv_I); |
| 110 | + |
| 111 | + // Invoke the method with a CallIndirect, then return the result |
| 112 | + proc.Emit(OpCodes.Calli, callSite); |
| 113 | + proc.Emit(OpCodes.Ret); |
| 114 | + } |
| 115 | + |
| 116 | + // Implement all the properties in the interface |
| 117 | + foreach (var intProp in contractType.GetProperties()) |
| 118 | + { |
| 119 | + var intAttr = intProp.GetCustomAttribute<NativeImportAttribute>(); |
| 120 | + |
| 121 | + if (intAttr == null) throw new ArgumentException($"Type argument contains a property without a NativeImportAttribute ({intProp.Name})"); |
| 122 | + |
| 123 | + if (!intProp.PropertyType.IsByRef) throw new ArgumentException($"Type argument's properties must be ref returns ({intProp.Name})"); |
| 124 | + if (intProp.CanWrite) throw new ArgumentException($"Type argument's properties cannot have a setter ({intProp.Name})"); |
| 125 | + |
| 126 | + if (intProp.PropertyType.GetElementType()?.IsValueType != true) throw new ArgumentException("Type argument's properties must be a reference to a value type"); |
| 127 | + |
| 128 | + // Generate the property and get method |
| 129 | + var prop = new PropertyDefinition(intProp.Name, Mono.Cecil.PropertyAttributes.None, |
| 130 | + asm.MainModule.ImportReference(intProp.PropertyType)); |
| 131 | + var propMethod = new MethodDefinition($"get_{intProp.Name}", |
| 132 | + Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Virtual | Mono.Cecil.MethodAttributes.Final | |
| 133 | + Mono.Cecil.MethodAttributes.SpecialName, asm.MainModule.ImportReference(intProp.PropertyType)); |
| 134 | + prop.GetMethod = propMethod; |
| 135 | + implTyp.Properties.Add(prop); |
| 136 | + implTyp.Methods.Add(propMethod); |
| 137 | + |
| 138 | + // Generate a get body which resolves the symbol |
| 139 | + var getProc = propMethod.Body.GetILProcessor(); |
| 140 | + |
| 141 | + // Load the symbol address and convert to an IntPtr (native int) |
| 142 | + getProc.Emit(OpCodes.Ldc_I8, (long)handle.GetSymbolAddress(intAttr.EntryPoint ?? intProp.Name)); |
| 143 | + getProc.Emit(OpCodes.Conv_I); |
| 144 | + // Return this unmodified - the result is that the pointer is converted to a reference by the CLR |
| 145 | + getProc.Emit(OpCodes.Ret); |
| 146 | + } |
| 147 | + |
| 148 | + // Write the newly generated assembly to memory, load it, and instatiate the newly generated binding |
| 149 | + using (var mem = new MemoryStream()) |
| 150 | + { |
| 151 | + asm.Write(mem); |
| 152 | + var newAsm = Assembly.Load(mem.ToArray()); |
| 153 | + var newTyp = newAsm.GetType($"{contractType.Name}Impl"); |
| 154 | + return newTyp.GetConstructor(new Type[0])?.Invoke(new object[0]); |
| 155 | + } |
| 156 | + } |
| 157 | + } |
| 158 | +} |
0 commit comments