public override object CreateBinding(Type contractType) { if (RuntimeInformation.ProcessArchitecture == Architecture.X86) { throw new PlatformNotSupportedException(); } // Get the NativeLibraryLoader for this platform var loader = NativeLibraryLoader.GetLibraryLoaderForPlatform(); if (!contractType.IsInterface) { throw new ArgumentException("Type argument must be an interface."); } var libraryAttr = contractType.GetCustomAttribute <NativeLibraryContractAttribute>(); if (libraryAttr == null) { throw new ArgumentException("Type argument must have a NativeLibraryContractAttribute"); } var handle = loader.LoadNativeLibrary(libraryAttr.LibraryName, libraryAttr.Version); // Create the dynamic assembly which will contain the binding var asm = AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition(libraryAttr.LibraryName, libraryAttr.Version), "<Module>", ModuleKind.Dll); // Create the binding type var implTyp = new TypeDefinition("", $"{contractType.Name}Impl", Mono.Cecil.TypeAttributes.Public, asm.MainModule.TypeSystem.Object); implTyp.Interfaces.Add(new InterfaceImplementation(asm.MainModule.ImportReference(contractType))); asm.MainModule.Types.Add(implTyp); // Create a default constructor for the binding type var implCtor = new MethodDefinition(".ctor", Mono.Cecil.MethodAttributes.SpecialName | Mono.Cecil.MethodAttributes.RTSpecialName | Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.HideBySig, asm.MainModule.TypeSystem.Void); implTyp.Methods.Add(implCtor); // Simple ctor body - load `this`, call `new object()` against it var ctorProc = implCtor.Body.GetILProcessor(); ctorProc.Emit(OpCodes.Ldarg_0); ctorProc.Emit(OpCodes.Call, asm.MainModule.ImportReference(typeof(object).GetConstructor(new Type[0]))); ctorProc.Emit(OpCodes.Ret); // Implement all the methods in the interface foreach (var intMethod in contractType.GetMethods()) { // If the method has a special name, ignore it. This excludes property getters/setters if (intMethod.IsSpecialName) { continue; } // The method cannot have varargs (this actually /can/ be achieved later, but it's too complicated for now) if (intMethod.CallingConvention == CallingConventions.VarArgs) { throw new ArgumentException("Type argument cannot contain a method with varargs"); } var intAttr = intMethod.GetCustomAttribute <NativeImportAttribute>(); if (intAttr == null) { throw new ArgumentException($"Type argument contains a method without a NativeImportAttribute ({intMethod.Name})"); } // Create the dynamic method for the implementation var meth = new MethodDefinition(intMethod.Name, Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Final | Mono.Cecil.MethodAttributes.Virtual, asm.MainModule.ImportReference(intMethod.ReturnType)); implTyp.Methods.Add(meth); // The body for the dynamic method var proc = meth.Body.GetILProcessor(); // Generate a CallSite for the unmanaged function var callSite = new CallSite(asm.MainModule.ImportReference(intMethod.ReturnType)); switch (intAttr.CallingConvention) { case CallingConvention.Cdecl: callSite.CallingConvention = MethodCallingConvention.C; break; case CallingConvention.FastCall: callSite.CallingConvention = MethodCallingConvention.FastCall; break; case CallingConvention.Winapi: case CallingConvention.StdCall: callSite.CallingConvention = MethodCallingConvention.StdCall; break; case CallingConvention.ThisCall: callSite.CallingConvention = MethodCallingConvention.ThisCall; break; default: throw new ArgumentOutOfRangeException(); } var i = 0; foreach (var param in intMethod.GetParameters()) { callSite.Parameters.Add( new ParameterDefinition(param.Name, (Mono.Cecil.ParameterAttributes)param.Attributes, asm.MainModule.ImportReference(param.ParameterType))); meth.Parameters.Add(new ParameterDefinition(param.Name, (Mono.Cecil.ParameterAttributes)param.Attributes, asm.MainModule.ImportReference(param.ParameterType))); proc.Emit(OpCodes.Ldarg, ++i); } // Load the symbol address for this function as a long, then convert to an IntPtr (native int) proc.Emit(OpCodes.Ldc_I8, (long)handle.GetSymbolAddress(intAttr.EntryPoint ?? intMethod.Name)); proc.Emit(OpCodes.Conv_I); // Invoke the method with a CallIndirect, then return the result proc.Emit(OpCodes.Calli, callSite); proc.Emit(OpCodes.Ret); } // Implement all the properties in the interface foreach (var intProp in contractType.GetProperties()) { var intAttr = intProp.GetCustomAttribute <NativeImportAttribute>(); if (intAttr == null) { throw new ArgumentException($"Type argument contains a property without a NativeImportAttribute ({intProp.Name})"); } if (!intProp.PropertyType.IsByRef) { throw new ArgumentException($"Type argument's properties must be ref returns ({intProp.Name})"); } if (intProp.CanWrite) { throw new ArgumentException($"Type argument's properties cannot have a setter ({intProp.Name})"); } if (intProp.PropertyType.GetElementType()?.IsValueType != true) { throw new ArgumentException("Type argument's properties must be a reference to a value type"); } // Generate the property and get method var prop = new PropertyDefinition(intProp.Name, Mono.Cecil.PropertyAttributes.None, asm.MainModule.ImportReference(intProp.PropertyType)); var propMethod = new MethodDefinition($"get_{intProp.Name}", Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Virtual | Mono.Cecil.MethodAttributes.Final | Mono.Cecil.MethodAttributes.SpecialName, asm.MainModule.ImportReference(intProp.PropertyType)); prop.GetMethod = propMethod; implTyp.Properties.Add(prop); implTyp.Methods.Add(propMethod); // Generate a get body which resolves the symbol var getProc = propMethod.Body.GetILProcessor(); // Load the symbol address and convert to an IntPtr (native int) getProc.Emit(OpCodes.Ldc_I8, (long)handle.GetSymbolAddress(intAttr.EntryPoint ?? intProp.Name)); getProc.Emit(OpCodes.Conv_I); // Return this unmodified - the result is that the pointer is converted to a reference by the CLR getProc.Emit(OpCodes.Ret); } // Write the newly generated assembly to memory, load it, and instatiate the newly generated binding using (var mem = new MemoryStream()) { asm.Write(mem); var newAsm = Assembly.Load(mem.ToArray()); var newTyp = newAsm.GetType($"{contractType.Name}Impl"); return(newTyp.GetConstructor(new Type[0])?.Invoke(new object[0])); } }
public override object CreateBinding(Type bindingContract) { // Get the NativeLibraryLoader for this platform var loader = NativeLibraryLoader.GetLibraryLoaderForPlatform(); if (!bindingContract.IsInterface) { throw new ArgumentException("Type argument must be an interface."); } var libraryAttr = bindingContract.GetCustomAttribute <NativeLibraryContractAttribute>(); if (libraryAttr == null) { throw new ArgumentException("Type argument must have a NativeLibraryContractAttribute"); } var handle = loader.LoadNativeLibrary(libraryAttr.LibraryName, libraryAttr.Version); // Create the dynamic assembly which will contain the binding var asm = AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition(libraryAttr.LibraryName, libraryAttr.Version), "<Module>", ModuleKind.Dll); // Create the binding type var implTyp = new TypeDefinition("", $"{bindingContract.Name}Impl", Mono.Cecil.TypeAttributes.Public, asm.MainModule.TypeSystem.Object); implTyp.Interfaces.Add(new InterfaceImplementation(asm.MainModule.ImportReference(bindingContract))); asm.MainModule.Types.Add(implTyp); // Create a default constructor for the binding type var implCtor = new MethodDefinition(".ctor", Mono.Cecil.MethodAttributes.SpecialName | Mono.Cecil.MethodAttributes.RTSpecialName | Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.HideBySig, asm.MainModule.TypeSystem.Void); implTyp.Methods.Add(implCtor); // Simple ctor body - load `this`, call `new object()` against it var ctorProc = implCtor.Body.GetILProcessor(); ctorProc.Emit(OpCodes.Ldarg_0); ctorProc.Emit(OpCodes.Call, asm.MainModule.ImportReference(typeof(object).GetConstructor(new Type[0]))); ctorProc.Emit(OpCodes.Ret); // Implement all the methods in the interface foreach (var intMethod in bindingContract.GetMethods()) { // If the method has a special name, ignore it. This excludes property getters/setters if (intMethod.IsSpecialName) { continue; } // The method cannot have varargs (this actually /can/ be achieved later, but it's too complicated for now) if (intMethod.CallingConvention == CallingConventions.VarArgs) { throw new ArgumentException("Type argument cannot contain a method with varargs"); } var intAttr = intMethod.GetCustomAttribute <NativeImportAttribute>(); if (intAttr == null) { throw new ArgumentException($"Type argument contains a method without a NativeImportAttribute ({intMethod.Name})"); } // Create a static method with a PInvokeImpl that points to the library / function we want var bindMeth = new MethodDefinition($"{intMethod.Name}Impl", Mono.Cecil.MethodAttributes.Private | Mono.Cecil.MethodAttributes.Static | Mono.Cecil.MethodAttributes.PInvokeImpl | Mono.Cecil.MethodAttributes.HideBySig, asm.MainModule.ImportReference(intMethod.ReturnType)) { ImplAttributes = Mono.Cecil.MethodImplAttributes.PreserveSig, }; if (intMethod.GetCustomAttribute <SuppressUnmanagedCodeSecurityAttribute>() != null) { bindMeth.CustomAttributes.Add(new CustomAttribute(asm.MainModule.ImportReference(typeof(SuppressUnmanagedCodeSecurityAttribute).GetConstructor(new Type[0])))); } { var module = new ModuleReference(handle.LibraryPath); asm.MainModule.ModuleReferences.Add(module); var pInvokeAttributes = PInvokeAttributes.NoMangle; switch (intAttr.CallingConvention) { case CallingConvention.Cdecl: pInvokeAttributes |= PInvokeAttributes.CallConvCdecl; break; case CallingConvention.FastCall: pInvokeAttributes |= PInvokeAttributes.CallConvFastcall; break; case CallingConvention.StdCall: pInvokeAttributes |= PInvokeAttributes.CallConvStdCall; break; case CallingConvention.ThisCall: pInvokeAttributes |= PInvokeAttributes.CallConvThiscall; break; case CallingConvention.Winapi: pInvokeAttributes |= PInvokeAttributes.CallConvWinapi; break; default: throw new ArgumentOutOfRangeException(); } switch (intAttr.CharSet) { case CharSet.Ansi: pInvokeAttributes |= PInvokeAttributes.CharSetAnsi; break; case CharSet.Auto: pInvokeAttributes |= PInvokeAttributes.CharSetAuto; break; case CharSet.None: pInvokeAttributes |= PInvokeAttributes.CharSetNotSpec; break; case CharSet.Unicode: pInvokeAttributes |= PInvokeAttributes.CharSetUnicode; break; default: throw new ArgumentOutOfRangeException(); } if (intAttr.SetLastError) { pInvokeAttributes |= PInvokeAttributes.SupportsLastError; } pInvokeAttributes |= intAttr.BestFitMapping ? PInvokeAttributes.BestFitEnabled : PInvokeAttributes.BestFitDisabled; pInvokeAttributes |= intAttr.ThrowOnUnmappableChar ? PInvokeAttributes.ThrowOnUnmappableCharEnabled : PInvokeAttributes.ThrowOnUnmappableCharDisabled; bindMeth.PInvokeInfo = new PInvokeInfo(pInvokeAttributes, intAttr.EntryPoint ?? intMethod.Name, module); } var retMarshal = intMethod.ReturnParameter?.GetCustomAttribute <MarshalAsAttribute>(); if (retMarshal != null) { bindMeth.MethodReturnType.MarshalInfo = GetMarshalInfo(asm.MainModule, retMarshal); } // Add all the parameters we want foreach (var parameter in intMethod.GetParameters()) { var implParam = new ParameterDefinition(parameter.Name, (Mono.Cecil.ParameterAttributes)parameter.Attributes, asm.MainModule.ImportReference(parameter.ParameterType)); foreach (var marshal in parameter.GetCustomAttributes <MarshalAsAttribute>()) { implParam.MarshalInfo = GetMarshalInfo(asm.MainModule, marshal); } bindMeth.Parameters.Add(implParam); } implTyp.Methods.Add(bindMeth); // Create the dynamic method for the implementation var meth = new MethodDefinition(intMethod.Name, Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Final | Mono.Cecil.MethodAttributes.Virtual, asm.MainModule.ImportReference(intMethod.ReturnType)); implTyp.Methods.Add(meth); // The body for the dynamic method var proc = meth.Body.GetILProcessor(); var i = 0; foreach (var param in intMethod.GetParameters()) { meth.Parameters.Add(new ParameterDefinition(param.Name, (Mono.Cecil.ParameterAttributes)param.Attributes, asm.MainModule.ImportReference(param.ParameterType))); proc.Emit(OpCodes.Ldarg, ++i); } // Invoke the method with a Call, then return the result proc.Emit(OpCodes.Call, bindMeth); proc.Emit(OpCodes.Ret); } // Implement all the properties in the interface foreach (var intProp in bindingContract.GetProperties()) { var intAttr = intProp.GetCustomAttribute <NativeImportAttribute>(); if (intAttr == null) { throw new ArgumentException($"Type argument contains a property without a NativeImportAttribute ({intProp.Name})"); } if (!intProp.PropertyType.IsByRef) { throw new ArgumentException($"Type argument's properties must be ref returns ({intProp.Name})"); } if (intProp.CanWrite) { throw new ArgumentException($"Type argument's properties cannot have a setter ({intProp.Name})"); } if (intProp.PropertyType.GetElementType()?.IsValueType != true) { throw new ArgumentException("Type argument's properties must be a reference to a value type"); } // Generate the property and get method var prop = new PropertyDefinition(intProp.Name, Mono.Cecil.PropertyAttributes.None, asm.MainModule.ImportReference(intProp.PropertyType)); var propMethod = new MethodDefinition($"get_{intProp.Name}", Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Virtual | Mono.Cecil.MethodAttributes.Final | Mono.Cecil.MethodAttributes.SpecialName, asm.MainModule.ImportReference(intProp.PropertyType)); prop.GetMethod = propMethod; implTyp.Properties.Add(prop); implTyp.Methods.Add(propMethod); // Generate a get body which resolves the symbol var getProc = propMethod.Body.GetILProcessor(); // Load the symbol address and convert to an IntPtr (native int) getProc.Emit(OpCodes.Ldc_I8, (long)handle.GetSymbolAddress(intAttr.EntryPoint ?? intProp.Name)); getProc.Emit(OpCodes.Conv_I); // Return this unmodified - the result is that the pointer is converted to a reference by the CLR getProc.Emit(OpCodes.Ret); } // Write the newly generated assembly to memory, load it, and instatiate the newly generated binding using (var mem = new MemoryStream()) { asm.Write(mem); var newAsm = Assembly.Load(mem.ToArray()); var newTyp = newAsm.GetType($"{bindingContract.Name}Impl"); return(newTyp.GetConstructor(new Type[0])?.Invoke(new object[0])); } }