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]));
            }
        }
Пример #2
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]));
            }
        }