[Ironruby-core] WPF Databinding with RubyObjects, continuation, dynamically create wrappers with strong typed properties

Tomas Matousek Tomas.Matousek at microsoft.com
Tue Jun 14 12:05:04 EDT 2011


My guess would be that you’re not boxing the value.
You need to emit OpCodes.Box to convert bool/int/… to an Object before calling Invoke in setter. The setter should also return Object (your’s is void). In getter you need to use OpCodes.Unbox before returning.
I’d also recommend using Func<> delegate instead of your custom delegate types if possible.

Tomas

From: ironruby-core-bounces at rubyforge.org [mailto:ironruby-core-bounces at rubyforge.org] On Behalf Of Sickboy
Sent: Tuesday, June 14, 2011 8:38 AM
To: ironruby-core at rubyforge.org
Subject: [Ironruby-core] WPF Databinding with RubyObjects, continuation, dynamically create wrappers with strong typed properties

Hello,

I recently stumbled upon this awesome code: http://rubyforge.org/pipermail/ironruby-core/2008-December/003377.html
It does exactly what I need; dynamically wrapping my RubyObject into a CLR class with CLR properties, except that it’s designed only for string properties.

I’ve adjusted the code to support a number of types, incl int, bool and float.

However the CLR crashes when-ever I assign a value to a non-string property, e.g float or int.
“The runtime has encountered a fatal error. The address of the error was at 0x5935788d, on thread 0x1adc. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.”

Any assistance would be deeply appreciated.

In essence, instead of using typeof(string) inside the TypeGenerator.Generate method, i use e.g; typeof(int) or typeof(float)
This is my current code:

C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;

namespace GenerateType
{
    public class TypeGenerator
    {
        public delegate object GetPropertyDelegate(string propertyName);
        public delegate object SetPropertyDelegate(string propertyName, object value);

        public static Type Generate(string className, Dictionary<string, List<string>> properties)
        {
            AssemblyName asmName = new AssemblyName("BindingTypes");
            AssemblyBuilder asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
            ModuleBuilder modBuilder = asmBuilder.DefineDynamicModule("Types");
            TypeBuilder typeBuilder = modBuilder.DefineType(className,
                        TypeAttributes.Public |
                        TypeAttributes.Class |
                        TypeAttributes.AutoClass |
                        TypeAttributes.AnsiClass |
                        TypeAttributes.BeforeFieldInit |
                        TypeAttributes.AutoLayout);


            FieldBuilder getFieldBuilder = typeBuilder.DefineField("OnGet", typeof(GetPropertyDelegate), FieldAttributes.Public);
            FieldBuilder setFieldBuilder = typeBuilder.DefineField("OnSet", typeof(SetPropertyDelegate), FieldAttributes.Public);

            MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            Type type = null;

            foreach (string s in properties.Keys)
            {
                type = null;
                switch (s)
                {
                    case "float":
                        type = typeof(float);
                        break;
                    case "int":
                        type = typeof(int);
                        break;
                    case "string":
                        type = typeof(string);
                        break;
                    case "bool":
                        type = typeof(bool);
                        break;
                    //case "dynamic":
                    //    type = dynamic;
                }
                if (type != null)
                {
                    GenerateProperties(properties[s], type, typeBuilder, getSetAttr, getFieldBuilder, setFieldBuilder);
                }
            }
            return typeBuilder.CreateType();
        }

        public static void GenerateProperties(List<string> properties, Type type, TypeBuilder typeBuilder, MethodAttributes getSetAttr, FieldBuilder getFieldBuilder, FieldBuilder setFieldBuilder)
        {
            foreach (string propertyName in properties)
            {
                PropertyBuilder propBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, typeof(object), new Type[] {});
                MethodBuilder getter = typeBuilder.DefineMethod("get_" + propertyName,
                                   getSetAttr,
                                   type,
                                   Type.EmptyTypes);

                ILGenerator ilGen = getter.GetILGenerator();

                ilGen.Emit(OpCodes.Ldarg_0);
                ilGen.Emit(OpCodes.Ldfld, getFieldBuilder);
                ilGen.Emit(OpCodes.Ldstr, propertyName);
                ilGen.Emit(OpCodes.Callvirt, typeof(GetPropertyDelegate).GetMethod("Invoke"));
                ilGen.Emit(OpCodes.Ret);

                // Define the "set" accessor method for CustomerName.
                MethodBuilder setter = typeBuilder.DefineMethod("set_" + propertyName,
                                               getSetAttr,
                                               null,
                                               new Type[] { type });

                ilGen = setter.GetILGenerator();

                ilGen.Emit(OpCodes.Ldarg_0);
                ilGen.Emit(OpCodes.Ldfld, setFieldBuilder);
                ilGen.Emit(OpCodes.Ldstr, propertyName);
                ilGen.Emit(OpCodes.Ldarg_1);
                ilGen.Emit(OpCodes.Callvirt, typeof(SetPropertyDelegate).GetMethod("Invoke"));
                ilGen.Emit(OpCodes.Pop);
                ilGen.Emit(OpCodes.Ret);

                // Last, we must map the two methods created above to our PropertyBuilder to
                // their corresponding behaviors, "get" and "set" respectively.
                propBuilder.SetGetMethod(getter);
                propBuilder.SetSetMethod(setter);
            }
        }
    }
}




Ruby Code:

include System::Data
include System::Windows::Data
include System::ComponentModel

include GenerateType
class WrapperGenerator
  def initialize
    @wrapper_cache = {}
  end

  def wrap(ruby_object)
    if ruby_object.is_a? Array
      ruby_object.map {|o| wrap(o) }
    else
      cache(ruby_object) unless cached(ruby_object)
      wrapper_class = cached(ruby_object)
      wrapper_class.new(ruby_object)
    end
  end

  def invalidate
    @wrapper_cache.clear
  end
private
  def cached(object)
    @wrapper_cache[object.class.name]
  end

  def cache(object)
    @wrapper_cache[object.class.name] = generate_wrapper(object)
  end

  def generate_wrapper(object)
    wrapper_name = "#{object.class.name}Wrapper"
    properties = Dictionary.of(System::String, List.of(System::String)).new

    if defined?(object.class::PROPERTY_DESCRIPTORS)
      object.class::PROPERTY_DESCRIPTORS.each_pair do |k, v|
        properties[k.to_clr_string] = List.of(System::String).new
        properties[k.to_clr_string].replace v.map{|e| e.to_clr_string}
      end
    else
      # Default to String properties
      properties["string"] = List.of(System::String).new
      properties["string"].replace (object.methods - Object.instance_methods).map{|e| e.to_clr_string}
    end

    wrapper_base_type = TypeGenerator.generate("#{wrapper_name}Base", properties)
    base_instance = System::Activator.create_instance wrapper_base_type

    eval <<EOS
      class #{wrapper_name} < base_instance.class
        def initialize(original)
          self.on_get = lambda do |prop|
            original.send prop
          end

          self.on_set = lambda do |prop, val|
            original.send "\#{prop}=", val
          end
        end
      end
      return #{wrapper_name} # return the class
EOS
  end
end

class Person
  attr_accessor :test
  PROPERTY_DESCRIPTORS = { :int => [:test] }
end

wrapper = WrapperGenerator.new
wrapped = wrapper.wrap(Person.new)
puts "Wrapped"
# wrapped.test = "35" # properly generates Exception: Cannot convert String to Fixnum
wrapped.test = 35 # !!!!! Crashes here !!!!!
puts "Assigned"
puts wrapped.test, wrapped.test.inspect, wrapped.test.class
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://rubyforge.org/pipermail/ironruby-core/attachments/20110614/0b1f497b/attachment-0001.html>


More information about the Ironruby-core mailing list