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> 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