Place an object on top of stack in ILGenerator

4.4k views Asked by At

I have to pass a function an instance of an object, so obviously all the information to be taken as argument is to be loaded onto the evaluation stack Here is the code that i am looking for

someClass SomeObject = new someClass();

il.Emit(OpCodes.LoadObject, SomeObject);
il.Emit(OpCodes.CallVirt, MethodInfo Function);


public void Function(Object obj)
{
       Type type = typeof(obj);
       //do something w.r.t to the type
}

I dont require any information stored in the class just the type and i cannot use any of the primitive types to take my decision on

Last i read that i can use a pointer to load the type using some opcodes ... but i am completely lost here, any help or pointers to the right direction would be great :)

[UPDATE]

Well i found an answer to my own question, tried it and it works don't know if it is the correct way or not but i can successfully create and load an object into stack and pass it to a function

ConstructorInfo ci = typeof(SomeClass).GetConstructor(System.Type.EmptyTypes);
IL.Emit(OpCodes.Newobj, ci);
IL.Emit(OpCodes.Call, SomeFunctionMethodInfo);

SomeFunctionMethodInfo is a function that takes Object as an argument, i successfully have passed the object into the function and can manipulate it also and return back the class as an object.

Nowhere i could find the reference to this example, just figured it out through MSDN, am i doing anything wrong or is there any downside to it ? Experts please if you could correct it or provide a better answer

4

There are 4 answers

4
Marc Gravell On BEST ANSWER

You can't pluck a reference out of thin air in IL, unless you code the reference as an IntPtr literal, in which case:
        a. don't do it
        b. you'd need to pin, and
        c. don't do it.

The best approach depends on the signature of the method you are writing. If it is static and takes no arguments... well, that is a bit tricky. Personally I'd be inclined to pass an object into the generated method, and have the delegate fetch any external data it needs from there. But another approach is to instead generate a class, and write the method as an instance method that accesses fields on the types.

The difference (hence my preference) is that the first requires (at most) an object[] parameter on the method—and you can use DynamicMethod; the second requires MethodBuilder, TypeBuilder, ModuleBuilder, AssemblyBuilder, etc., and are thus more work.

The reason I mention object[] is that generally you want a common signature over the generated methods, even if they require different inputs. This lets you bind to a fixed delegate type and use the faster Invoke execution (DynamicInvoke is slow).

For example:

class SomeType { }
delegate void SomeDelegateType(params object[] args);
public class Program
{
    public static void Main()
    {
        var dn = new DynamicMethod("foo", (Type)null, new[] {typeof(object[])});
        var il = dn.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldc_I4_0);
        il.Emit(OpCodes.Ldelem_Ref);
        il.EmitCall(OpCodes.Call, typeof(Program).GetMethod("Function"), null);
        il.Emit(OpCodes.Ret);
        var action = (SomeDelegateType)dn.CreateDelegate(typeof(SomeDelegateType));

        var obj = new SomeType();
        action(obj);
    }
    public static void Function(object obj)
    {
        Type type = obj.GetType();
        Console.WriteLine(type);
    }
}

If you can't have an input argument, then you'll have to use fields on a type you create—which is actually exactly what the compiler does if you write (for example)

object someObj = ...
Action action = () => Function(someObj);

This is created as:

class <>somehorriblename {
    public object someObj;
    public void SomeGeneratedName() { Function(someObj); }
}
...
var captureClass = new <>somehorriblename();
captureClass.someObj = ...
Action action = captureClass.SomeGeneratedName;
3
jsa On

One easy method I used was obtaining the GCHandle, then obtaining its IntPtr (via static method GCHandle.ToIntPtr) and then converting that to a long or integer (using either ToPointer or ToInt64).

That way I was able call ILGenerator.Emit(OpCodes.Ldc_I8, ptr).

0
Glenn Slayden On

Here is a complete implementation of the solution outlined by others on this page here and here. This code allows you to import, or "hard-code" any live object reference you are able supply into the IL stream of a DynamicMethod as a permanently burned-in 32- or 64-bit "literal" reference.

Please note that this is obviously n̲o̲t̲ a recommended technique, and is shown here for educational and/or experimental purposes only

As noted in one of the comments, you don't need to pin the GCHandle; it's perfectly fine for GC to move the object around normally since the numeric value of the handle won't change so long as the handle remains alive. The real reason you might need to hold a GCHandle here is that the completed DynamicMethod instance will not be holding a reference to (nor in fact have any knowledge of) the handle embedded within itself. Without the GCHandle, the instance could be collected when/if all other references to it go out of scope.

This code below takes an overly cautious approach by intentionally abandoning the GCHandle struct (i.e., by not freeing it) after using it to extract the object reference. This means that the handle--and thus also the target instance--will never be collected. If your application has other ways or means of keeping the handle alive, feel free to rely on those, as long as you can guarantee that the the handle survives the lifetime of the DynamicMethod it's emitted into via this technique; in this case you would free the GCHandle (code shown commented out) after using it to obtain the handle value.

/// <summary>
/// Burn a reference to the specified runtime object instance into the DynamicMethod
/// </summary>
public static void Emit_LdInst<TInst>(this ILGenerator il, TInst inst)
    where TInst : class
{
    var gch = GCHandle.Alloc(inst);

    var ptr = GCHandle.ToIntPtr(gch);

    if (IntPtr.Size == 4)
        il.Emit(OpCodes.Ldc_I4, ptr.ToInt32());
    else
        il.Emit(OpCodes.Ldc_I8, ptr.ToInt64());

    il.Emit(OpCodes.Ldobj, typeof(TInst));

    // Do the following only if you can elsewhere ensure that 'inst'
    // outlives this DynamicMethod
    // gch.Free();
}

A contribution of this answer not mentioned by others is that you should use the Opcodes.Ldobj instruction to coerce the proper runtime Type onto the newly hard-coded literal, as shown above. It's easy to verify that this is a good practice with the following test sequence. It produces a bool indicating whether the System.Type of the freshly-imported instance is what we expect to be, and it only returns true when the Opcodes.Ldobj is instruction is present in the extension method shown above.

TInst _inst = new MyObject();

// ...
il.Emit_LdInst(_inst);                  //  <-- the function shown above
il.Emit(OpCodes.Isinst, typeof(TInst));
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Xor);

What doesn't seem to be necessary after our rude Ldc_I4 / Ldc_I8 shove is Conv_I, and this seems to be true even if we drop the IntPtr.Size checking and just use Ldc_I8 to always load a long, even on x86. This is again thanks to Opcodes.Ldobj smoothing over such misdeeds.

Here's another use example. This one checks for reference equality between the embedded instance (imported at DynamicMethod-creation time) and whatever reference-type objects might variously be supplied when calling that method any time in the future. It only returns true when its long-lost progenitor shows up. (One wonders how the reunion might go...)

il.Emit_LdInst(cmp);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ceq);

Finally, a remark about the where TInst : class constraint on the extension method shown at the top. For one thing, there's no reason to import a value type in this manner, since you can just import its fields as literals instead. You may have noticed however, that the crucial Opcodes.Ldobj which makes the import work more reliably is documented as being intended for value-types, and not reference types as we are doing here. The simple key to this is to remember that really, an object reference is a handle which is itself just a pattern of 32- or 64- bits which is always copied by-value. In other words, basically a ValueType.

I've tested all of this pretty widely on both x86 and x64, debug and release, and it works great with no problems as of yet.

0
Glenn Slayden On

Another possibility that hasn't been mentioned (by either of the excellent answers posted thus far) is to store the runtime object reference somewhere in one of your own instances, and emit your customized IL code to access it where you know that you will have placed it.

This is easiest if the (foreign) object instance in question happens to be a singleton per AppDomain, because you can establish a well-known (to you) static field in one of your own singletons from which your IL will of course be guaranteed to find it.

If you instead need to accommodate an unknown number of arbitrary instances of the foreign type at runtime, or if you can't preclude their arbitrary delay—either situation seems to entail some arrangement for keeping them all straight—you could still publish them globally, in this case into an array of Object[] (or other type), and in some pre-established manner that is understood by the IL code.

As noted, there would presumably have to be some way to coordinate the publishing activity (enacted by some relevant 'management module' in your system) with the later consumption (by the customized IL, also yours, but presumably subject to method signature constraints), so that the IL will be able to distinguish and select the appropriate instance from the published array based solely on the arguments—or whatever other evidence—it does in fact have access to, or actually does receive in its (presumably constrained) argument list.

Depending on the situation, you might need to choose how the singleton list of published instances is designed. In all cases, it is expected that the IL consumer never alters the published entries, but one factor to consider is whether you require multiple publishers or not. Options include:

  • Pre-populate a readonly array with all expected foreign instances just once (i.e., during initialization), ensuring that this is prior to the possiblity of access by any of your custom IL. Obviously, this is the simplest plan.
  • Use monotonically growing array (foreign instances are only added, never removed during the AppDomain lifetime). This simplifies coordination with the IL because indices into the array, once issued, will never expire or change. This also allows for IL to be produced as "set-and-forget", meaning that upon creation, each DynamicMethod instance can have its relevant array index directly burned in. Publishing in perpetuity accrues benefits to both parties: the DynamicMethod at runtime can't in principle be of its burned-in IL, favoring that customization mode wherever possible, while the monotonicity of the list means the publisher/manager needn't retain neither the created DynamicMethod identities, nor anything about which are associated with which of the foreign object instances it publishes.
  • The simplicity of the "set-and-forget" strategy is especially effective in scenarios where each DynamicMethod is issued a private or distinct instance of its own.
  • If thread safety is needed for any of the dynamic scenarios listed here (i.e., due to there being multiple managers or publishing sources), it is trivially achieved via the lock-free concurrency technique of each publisher always using Interlocked.Exchange (with protective SpinWait) to swap-in a new, but strictly-extended, version of the previous published array.
  • If the foreign instances are truly and necessarily numerous and ephemeral then you may be forced into a more complex approach, where your published list of them adjusts dynamically. In this case, instances of the foreign object exist in your list only on a transient basis and coordination with the IL code may need to use more sophisticated methods of signalling or communication.
  • Note that, even if when not fundamentally necessary, you might still choose a more sophisticated scheme of "on-the-fly" management if the foreign instances are resource-expensive, and if retaining expired instances in your array is the sole GC reference that's keeping such resources from being released and recovered.