0

I'm trying to adapt this workaround to my project. Instead of a string I want to give an object (of type RevitFamily) to the base constructor.

Unfortunately, I can't find out how to load an object or object reference onto the stack using Reflection.Emit. Can Anyone help me?

So far my code looks like this, but I can't pass object to gen.Emit(opcode, _):

TypeBuilder tb = mb.DefineType(newtypename, new TypeAttributes(), typeof(LoadFamily));
var ci = typeof(LoadFamily).GetConstructor(new[] { typeof(RevitFamily) });
var constructorBuilder = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[0]);
var gen = constructorBuilder.GetILGenerator();
gen.DeclareLocal(typeof(RevitFamily));

Label ilLabel = gen.DefineLabel();
gen.Emit(OpCodes.Ldarg_0);                // Load "this" onto eval stack
// Here I should some how get he object f in the stack....
gen.Emit(OpCodes.Ldloca, f);              // Load pointer to RevitFamily on stack
gen.Emit(OpCodes.Call, ci);               // call base constructor (consumes "this" and the string)
gen.Emit(OpCodes.Nop);                    // Fill some space - this is how it is generated for equivalent C# code
gen.Emit(OpCodes.Nop);
gen.Emit(OpCodes.Nop);
gen.Emit(OpCodes.Ret);                    // return from constructor
t = tb.CreateType();

It is supposed to create a dynamic type with a constructor similar to this:

public class LoadconcreteFamily : LoadFamily
{
    public LoadconcreteFamily() : base(f)
    { }
}
Yennefer
  • 5,704
  • 7
  • 31
  • 44
  • Do you already have an object of the desired type somewhere or do you want to create a new one, to pass it then? – thehennyy Sep 19 '16 at 10:14
  • That code wouldn't compile on its own since `f` isn't declared anywhere. You're having trouble emitting IL for the same reason. You can't load a reference to a local object created *outside* the code you're generating on the stack. Either also generate the code to create the object, or load a reference from a field, or from a parameter that you pass to a generated method. Once you have the object stored in a field, local, or parameter somewhere, the usual `ldarg`/`ldfld` opcodes apply. – Jeroen Mostert Sep 19 '16 at 11:17
  • @ thehennyy: yes, f is an object, given to the function with this code segment as parameter. – s.heinekamp Sep 20 '16 at 20:50
  • @JeroenMostert: Thank you .... I think, I understand, why what I want to achieve can't be done. – s.heinekamp Sep 20 '16 at 20:55

1 Answers1

0

In order to (almost) achieve what you ask, you have to load the object from somewhere else (a field that could be in a singleton etc...) as suggested in the comments.

Why?

Because what you are trying to do in IL cannot be done in C# too. What you ask

public class LoadconcreteFamily : LoadFamily
{
    public LoadconcreteFamily() : base(f)
    { }
}

is not a valid C# code because f does not come from anywhere. In fact, f is a literal and not a reference to a method, property or field. Constructors can only see static instances or arguments passed to their caller.

The reason why the grammar is defined in this way is that the stack is a runtime concept which relates to how your code runs, while the class definition is a design-time concept that describes the elements to solve a particular problem. Of course they are related, but the main concept here is that classes themselves are an abstraction that do not require a stack[1].

Therefore first you have to fix your high-level code. I see two approaches here:

1st approach Add the argument to the constructor:

public class LoadConcreteFamily : LoadFamily
{
    public LoadConcreteFamily(RevitFamily f) : base(f)
    { }
}

accordingly your type definition will include the new argument and the signature is clearly different from what you ask, but the code is self contained and does not use a global context.

2nd approach Have a global instance holding your data:

public static class FamilyContainer
{
    public static RevitFamily SharedFamily { get; private set; }

    public static void InitializeFamily(RevitFamily family)
    {
        if (family == null)
            throw new ArgumentNullException(nameof(family));

        SharedFamily = family;
    }
}

public class LoadConcreteFamily : LoadFamily
{
    public LoadConcreteFamily() : base(FamilyContainer.SharedFamily)
    {

    }
}

which should be (untested):

TypeBuilder tb = mb.DefineType(newTypeName, new TypeAttributes(), typeof(LoadFamily));
var ci = typeof(LoadFamily).GetConstructor(new[] { typeof(RevitFamily) });
var constructorBuilder = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[0]);
var gen = constructorBuilder.GetILGenerator();
gen.DeclareLocal(typeof(RevitFamily));

gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Call, typeof(FamilyContainer).GetProperty(nameof(FamilyContainer.SharedFamily), BindingFlags.Public | BindingFlags.Static).GetGetMethod());
gen.Emit(OpCodes.Call, ci);
gen.Emit(OpCodes.Ret);

t = tb.CreateType();

IMHO, The introduction of a static instance should be carefully evaluated as global variables introduce unintended and unforseenable dependencies to your code making it more complex to be maintained and debugged.

You might benefit reading this SO question Are global variables bad? even if not C# specific.

For this reason, I would go with the first approach.

[1] there are several details that I omitted for brevity, and probably the oversimplification could be more difficult of the concept itself

Yennefer
  • 5,704
  • 7
  • 31
  • 44