Forum: IronRuby WPF Databinding with RubyObjects, continuation, dynamically create wrappers with strong typed proper

8b1bd159f2c4a1ee252a0f9ed683b210?d=identicon&s=25 Sickboy (Guest)
on 2011-06-14 17:47
(Received via mailing list)
Hello,

I recently stumbled upon this awesome code:
http://rubyforge.org/pipermail/ironruby-core/2008-...
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
Cb51033949ffccd982ae32c9f890f25a?d=identicon&s=25 Tomas Matousek (Guest)
on 2011-06-14 18:21
(Received via mailing list)
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@rubyforge.org
[mailto:ironruby-core-bounces@rubyforge.org] On Behalf Of Sickboy
Sent: Tuesday, June 14, 2011 8:38 AM
To: ironruby-core@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-...
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
8b1bd159f2c4a1ee252a0f9ed683b210?d=identicon&s=25 Sickboy (Guest)
on 2011-06-14 19:15
(Received via mailing list)
Thanks! Still new to .NET and especially this stuff.

The Box/Unbox seems to have worked, except that if I set e.g 35 as new
value, when reading the value back, something completely different comes
back, e.g; 150007660, but correct class Fixnum.
This number seems randomized, and changes upon accessing the getter for
the property more than once.

Any suggestions or alternatives welcome.
My backup plan is to generate the C# classes from Ruby dynamically
during development and saving them into a .cs file to be compiled with
exe/dll.

Regards


From: Tomas Matousek
Sent: Tuesday, June 14, 2011 6:05 PM
To: ironruby-core@rubyforge.org
Subject: Re: [Ironruby-core] WPF Databinding with RubyObjects,
continuation,dynamically create wrappers with strong typed properties

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@rubyforge.org
[mailto:ironruby-core-bounces@rubyforge.org] On Behalf Of Sickboy
Sent: Tuesday, June 14, 2011 8:38 AM
To: ironruby-core@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-...

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
Cb51033949ffccd982ae32c9f890f25a?d=identicon&s=25 Tomas Matousek (Guest)
on 2011-06-14 19:50
(Received via mailing list)
Your backup plan might actually be better – emitting code at runtime is
definitely slower than loading already compiled code.

You’re still emitting some wrong instructions. Can you send your new
code?

Tomas

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

Thanks! Still new to .NET and especially this stuff.

The Box/Unbox seems to have worked, except that if I set e.g 35 as new
value, when reading the value back, something completely different comes
back, e.g; 150007660, but correct class Fixnum.
This number seems randomized, and changes upon accessing the getter for
the property more than once.

Any suggestions or alternatives welcome.
My backup plan is to generate the C# classes from Ruby dynamically
during development and saving them into a .cs file to be compiled with
exe/dll.

Regards


From: Tomas Matousek<mailto:Tomas.Matousek@microsoft.com>
Sent: Tuesday, June 14, 2011 6:05 PM
To: ironruby-core@rubyforge.org<mailto:ironruby-core@rubyforge.org>
Subject: Re: [Ironruby-core] WPF Databinding with RubyObjects,
continuation,dynamically create wrappers with strong typed properties

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@rubyforge.org
[mailto:ironruby-core-bounces@rubyforge.org] On Behalf Of Sickboy
Sent: Tuesday, June 14, 2011 8:38 AM
To: ironruby-core@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-...
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
8b1bd159f2c4a1ee252a0f9ed683b210?d=identicon&s=25 Sickboy (Guest)
on 2011-06-14 20:00
(Received via mailing list)
Thanks! Currently on my way with the backup plan.

But here’s the C# and Ruby for the dynamic implementation:

C#


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"));
                if (type != typeof(string))
                {
                    //ilGen.Emit(OpCodes.Unbox, type);
                }
                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);
                if (type == typeof(int) || type == typeof(float))
                {
                    ilGen.Emit(OpCodes.Box, type);
                }
                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:


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 = System::Int16.new(35) # !!!!! Crashes here !!!!!
puts "Assigned"
puts wrapped.test, wrapped.test.inspect, wrapped.test.class

From: Tomas Matousek
Sent: Tuesday, June 14, 2011 7:49 PM
To: ironruby-core@rubyforge.org
Subject: Re: [Ironruby-core] WPF Databinding with RubyObjects,
continuation,dynamically create wrappers with strong typed properties

Your backup plan might actually be better – emitting code at runtime is
definitely slower than loading already compiled code.



You’re still emitting some wrong instructions. Can you send your new
code?



Tomas



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



Thanks! Still new to .NET and especially this stuff.



The Box/Unbox seems to have worked, except that if I set e.g 35 as new
value, when reading the value back, something completely different comes
back, e.g; 150007660, but correct class Fixnum.

This number seems randomized, and changes upon accessing the getter for
the property more than once.



Any suggestions or alternatives welcome.

My backup plan is to generate the C# classes from Ruby dynamically
during development and saving them into a .cs file to be compiled with
exe/dll.



Regards





From: Tomas Matousek

Sent: Tuesday, June 14, 2011 6:05 PM

To: ironruby-core@rubyforge.org

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



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@rubyforge.org
[mailto:ironruby-core-bounces@rubyforge.org] On Behalf Of Sickboy
Sent: Tuesday, June 14, 2011 8:38 AM
To: ironruby-core@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-...

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


--------------------------------------------------------------------------------

_______________________________________________
Ironruby-core mailing list
Ironruby-core@rubyforge.org
http://rubyforge.org/mailman/listinfo/ironruby-core
This topic is locked and can not be replied to.