通过 TypeBuilder 和 Emit 实现时无法正确获取接口签名

Cannot get interface signature correct when implementing via TypeBuilder and Emit

我在动态 classes 和 emit 方面的工作不多,我一直在努力解决这个问题。我想要的是在托管内存和本机内存之间移动结构和其他值类型时使用的助手。

我以为我大体上明白了,但我似乎无法让我的生成类型正确支持我定义的接口。每当我最终调用 TypeBuilder.CreateType() 时,它都会抛出一个异常,说该类型没有实现接口中两个方法中的第一个,我敢肯定它认为我也不支持第二个,但我不走那么远。我在使用和不使用 DefineGenericParameters 的情况下都完成了(传递我关心的实际类型)。我已经尝试了很多变体,例如 byte * 参数。我尝试指定通用参数,告诉它是不可为 null 的值类型。我在这里查看了关于发射和接口的其他问题,我想我已经考虑了这些问题的建议。

我不知道这是否需要简单的调整,或者我是否从根本上遗漏了什么。

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace Testing
{
    class Program
    {
        static unsafe void Main(string[] args)
        {
            NativeValueAccessFactory factory = new NativeValueAccessFactory();
            INativeValueAccessor<XTestStruct> caller = (INativeValueAccessor<XTestStruct>)factory.GetNativeValueAccessor<XTestStruct>();
            byte[] buffer = new byte[1024];
            XTestStruct item1 = new XTestStruct(1000, 2000);

            fixed (byte* pBuffer = &buffer[0])
            {
                caller.WriteData(item1, pBuffer);
            }

            XTestStruct item2 = new XTestStruct(1, 1);
            fixed (byte* pBuffer = &buffer[0])
            {
                item2 = caller.ReadData(pBuffer);
            }

            // Compare item1 and item2
        }
    }

    public struct XTestStruct
    {
        public int i;
        public int j;
        public XTestStruct(int i, int j) { this.i = i; this.j = j; }
    }

    public unsafe interface INativeValueAccessor<T> where T : struct
    {
        T ReadData(byte* data);
        void WriteData(T item, byte* data);
    }

    public class NativeValueAccessFactory
    {
        public NativeValueAccessFactory() { }

        public object GetNativeValueAccessor<T>() where T : struct
        {
            Type itemType = typeof(T);

            var rand = new Random();
            var name = string.Format("{0}_{1}", itemType.Name, rand.Next());
            var assemblyName = new AssemblyName(name);
            var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            var moduleBuilder = assemblyBuilder.DefineDynamicModule(name + ".dll");

            TypeBuilder typeBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public);
            typeBuilder.AddInterfaceImplementation(typeof(INativeValueAccessor<T>));

            // ReadData method
            MethodBuilder methodBuilder1 = typeBuilder.DefineMethod("ReadData", MethodAttributes.Public | MethodAttributes.Virtual);

            GenericTypeParameterBuilder[] generics1 = methodBuilder1.DefineGenericParameters("T");
            generics1[0].SetGenericParameterAttributes(GenericParameterAttributes.NotNullableValueTypeConstraint);
            methodBuilder1.SetReturnType(generics1[0]);
            methodBuilder1.SetParameters(typeof(byte*)); // also tried typeof(byte).MakeByRefType()

            ILGenerator codeGen1 = methodBuilder1.GetILGenerator();
            codeGen1.Emit(OpCodes.Ldarg_1);
            codeGen1.Emit(OpCodes.Ldobj, itemType);
            codeGen1.Emit(OpCodes.Ret);

            // WriteData method
            MethodBuilder methodBuilder2 = typeBuilder.DefineMethod("WriteData", MethodAttributes.Public | MethodAttributes.Virtual);

            GenericTypeParameterBuilder[] generics2 = methodBuilder2.DefineGenericParameters("T");
            generics2[0].SetGenericParameterAttributes(GenericParameterAttributes.NotNullableValueTypeConstraint);
            methodBuilder2.SetReturnType(null);
            methodBuilder2.SetParameters(generics2[0], typeof(byte*));

            ILGenerator codeGen2 = methodBuilder2.GetILGenerator();
            codeGen2.Emit(OpCodes.Ldarg_2);
            codeGen2.Emit(OpCodes.Ldarg_1);
            codeGen2.Emit(OpCodes.Stobj, itemType);
            codeGen2.Emit(OpCodes.Ret);

            Type generatedType = typeBuilder.CreateType(); // Throws exception here saying interface not implemented
            return Activator.CreateInstance(generatedType); // not at all confident this will work, but not even getting here
        }
    }
}

我知道很多其他方法可以在托管内存和本机内存之间复制结构,但其中大多数要么非常慢(例如 BinaryReader,Marshal.PtrToStructure),要么对 [=21] 的用户不方便=](需要提供执行副本的代表等)。我真的很想看看这是否可以用动态方法来完成,而且似乎可以,如果我能弄清楚我做错了什么。

我的错误是让动态方法将接口中通用的参数设置为动态方法中的通用参数。那是错误的。此时,由于我们正在处理特定类型 T XTestStruct,因此方法的正确签名是 "XTestStruct ReadData(byte*)" 和 "void WriteData(XTestStruct, byte*)"。通过在动态方法中仍然将这些参数视为通用参数,我基本上将签名设置为 "T ReadData(byte*)",这是不正确的。这是我最终得到的代码:

    TypeBuilder typeBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public);
    typeBuilder.AddInterfaceImplementation(typeof(INativeValueAccessor<T>));

    // ReadData method
    MethodBuilder methodBuilder1 = typeBuilder.DefineMethod("ReadData", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig);
    methodBuilder1.SetReturnType(itemType);
    methodBuilder1.SetParameters(typeof(byte*));

    ILGenerator codeGen1 = methodBuilder1.GetILGenerator();
    codeGen1.Emit(OpCodes.Ldarg_1);
    codeGen1.Emit(OpCodes.Ldobj, itemType);
    codeGen1.Emit(OpCodes.Ret);

    // WriteData method
    MethodBuilder methodBuilder2 = typeBuilder.DefineMethod("WriteData", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig);
    methodBuilder2.SetReturnType(null);
    methodBuilder2.SetParameters(itemType, typeof(byte*));

    ILGenerator codeGen2 = methodBuilder2.GetILGenerator();
    codeGen2.Emit(OpCodes.Ldarg_2);
    codeGen2.Emit(OpCodes.Ldarg_1);
    codeGen2.Emit(OpCodes.Stobj, itemType);
    codeGen2.Emit(OpCodes.Ret);

    Type generatedType = typeBuilder.CreateType();
    return Activator.CreateInstance(generatedType);

这个,插入到上面的完整示例中,运行没有错误,将数据复制到内存中并读回。