vendredi 8 mai 2020

System.Reflection.Emit custom property seems to be identical to my mock up, but I get an InvalidProgramException

To expand on the title, I'm using Reflection to generate a custom subclass that will eventually have an arbitrary number of string properties which they'll internally store in a dictionary. In this case I'm just using one. I've used Ildasm.exe to get the MSIL that I need for the My Set method works, as the debugger shows the value I'm assigning, but when I try to read it back I get an InvalidProgramException, "Common Language Runtime detected an invalid program." which points back to the get method. My get method model is:

/* public class TestWrapper : AttributeWrapper  //This is the source of the following MSIL
            {
                public string Name
                {
                    get { return GetAttribute("Name"); }
                    set { SetAttribute("Name", value); }
                }
            }
*/
    {
      // Code size       17 (0x11)
      .maxstack  2
      .locals init ([0] string V_0)
      IL_0000:  nop
      IL_0001:  ldarg.0
      IL_0002:  ldstr      "Name"
      IL_0007:  call       instance string ConfigXMLParser.frmNodeBuilder/AttributeWrapper::GetAttribute(string)
      IL_000c:  stloc.0
      IL_000d:  br.s       IL_000f

      IL_000f:  ldloc.0
      IL_0010:  ret
    } // end of method TestWrapper::get_Name

And code generating the property in question is:

  public static void CreateSelfNamingProperty(TypeBuilder tb, string propertyName, Type propertyType)
        {
            FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

            PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
            MethodBuilder getPropMthdBldr =
                tb.DefineMethod("get_" + propertyName,
                    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                    typeof(string), Type.EmptyTypes);
            Type[] getAttributeArgs = { typeof(string) };
            Type basetype = tb.BaseType;
            MethodInfo getAttrBase = basetype.GetMethod("GetAttribute", BindingFlags.Instance | BindingFlags.NonPublic);
            Debug.Assert(getAttrBase != null);

            ILGenerator ilGen = getPropMthdBldr.GetILGenerator();
            Label labelReturn = ilGen.DefineLabel();
            ilGen.Emit(OpCodes.Nop);           //The Get function starts here.  
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldstr, "Nope.");
            ilGen.EmitCall(OpCodes.Call, getAttrBase, getAttributeArgs);
            ilGen.Emit(OpCodes.Stloc_0);
            ilGen.Emit(OpCodes.Br_S, labelReturn);
            ilGen.MarkLabel(labelReturn);
            ilGen.Emit(OpCodes.Ldloc_0);
            ilGen.Emit(OpCodes.Ret);

            MethodBuilder setPropMthdBldr =
                tb.DefineMethod("set_" + propertyName,
                    MethodAttributes.Public |
                    MethodAttributes.SpecialName |
                    MethodAttributes.HideBySig,
                    null, new[] { propertyType });

            MethodInfo setAttrBase
                = basetype.GetMethod("SetAttribute", BindingFlags.Instance | BindingFlags.NonPublic);

            ILGenerator setIl = setPropMthdBldr.GetILGenerator();
            Label modifyProperty = setIl.DefineLabel();
            Label exitSet = setIl.DefineLabel();
            Type[] setParamTypes = { typeof(string) , typeof(string) };

            setIl.MarkLabel(modifyProperty);
            setIl.Emit(OpCodes.Nop); //The Set method starts here
            setIl.Emit(OpCodes.Ldarg_0);
            setIl.Emit(OpCodes.Ldstr, propertyName);
            setIl.Emit(OpCodes.Ldarg_1);
            setIl.EmitCall(OpCodes.Call, setAttrBase, setParamTypes);


            setIl.Emit(OpCodes.Nop);
            setIl.MarkLabel(exitSet);
            setIl.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getPropMthdBldr);
            propertyBuilder.SetSetMethod(setPropMthdBldr);
        }


// And this is what builds the TypeBuilder:

        public static TypeBuilder GetTypeBuilder()
        {
            string typeSignature = "MyDynamicType";
            AssemblyName an = new AssemblyName(typeSignature);
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
            TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
                    TypeAttributes.Public |
                    TypeAttributes.Class |
                    TypeAttributes.AutoClass |
                    TypeAttributes.AnsiClass |
                    TypeAttributes.BeforeFieldInit |
                    TypeAttributes.AutoLayout,
                    null);
            return tb;
        }

Finally, putting it all together:

            TypeBuilder tb = GetTypeBuilder("TestType");
            CreateSelfNamingProperty(tb, "Name", typeof(string));
            dynamic instance = Activator.CreateInstance(tb.CreateType());
            instance.Name = "Test"; //Debug shows Name is Test, but
            MessageBox.Show(instance.Name);//Exception occurs here

And the base class is pretty straightforward:

  public class AttributeWrapper
        {
            protected Dictionary<string, string> _attributes =
                new Dictionary<string, string>();

            protected void SetAttribute(string attribute, string value)
            {
                if (_attributes.ContainsKey(attribute))
                {
                    _attributes[attribute] = value;
                }
                else
                {
                    _attributes.Add(attribute, value);
                }
            }

            protected string GetAttribute(string attribute)
            {
                return _attributes.ContainsKey(attribute) ? _attributes[attribute] : "";
            }
        }




Aucun commentaire:

Enregistrer un commentaire