将对象转换为 IL 中的特定类?

发布于 2024-08-07 18:54:21 字数 1698 浏览 5 评论 0原文

我发现了我正在生成的 DynamicMethod 中出现“操作可能会破坏运行时稳定性”的原因,虽然我很容易修复了它,但它给我留下了一个看似简单的问题:

  • 如何强制转换类型为“Object”的对象引用转换为特定类型,以便我可以在对象引用上调用该类型的方法?

下面是一个示例程序。运行此方法时,它会在编译该方法时崩溃并出现“操作可能会破坏运行时的稳定性”异常。

只需更改声明为 TestClass 类型而不是 Object 的变量类型即可解决问题,但我仍然想知道如何将引用强制转换为代码中适当的类型。

在代码中,我用星号标记了一行。此时我可以发出什么代码,使堆栈上的 Object 引用变成 TestClass 引用,以便方法调用能够通过?

请注意,我知道是方法调用产生了问题,如果我完全注释掉这些行,那么变量的类型并不重要,该方法会被编译并正常执行。

这是代码。

using System;
using System.Reflection.Emit;

namespace ConsoleApplication9
{
    public class TestClass
    {
        public void TestMethod() { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Type type = typeof(TestClass);
            DynamicMethod method = new DynamicMethod("", typeof(Object), null);
            ILGenerator il = method.GetILGenerator();
            LocalBuilder variable = il.DeclareLocal(typeof(Object));

            // Construct object
            il.Emit(OpCodes.Newobj, type.GetConstructor(new Type[0]));
            il.Emit(OpCodes.Stloc, variable);

            // Call Test method
            il.Emit(OpCodes.Ldloc, variable);
            // ***************************************** what do I do here?
            il.Emit(OpCodes.Call, type.GetMethod("TestMethod"));

            // Return object
            il.Emit(OpCodes.Ldloc, variable);
            il.Emit(OpCodes.Ret);

            // Create and call delegate
            Func<Object> fn = (Func<Object>)method.CreateDelegate(
                typeof(Func<Object>));
            Object instance = fn();
        }
    }
}

I discovered the reason I was getting "Operation could destabilize the runtime" in a DynamicMethod I'm producing, and though I easily fixed it, it left me with a seemingly simple question:

  • How do I cast an object reference of type "Object" into a specific type, so that I can call methods from that type on the object reference?

Below is a sample program. When running this, it will crash with an "Operation could destabilize the runtime" exception when compiling the method.

The problem is fixed by just changing the type of the variable being declared to be of type TestClass instead of Object, but I still want to know how I can cast the reference to the appropriate type in the code.

In the code I've marked a line with asterixes. What can I emit of code at that point that will make the Object reference on the stack into a TestClass reference instead, so that the method call will go through?

Note that I know that it is the method call that produces the problem, if I comment out the lines altogether, it doesn't matter which type the variable is, the method is compiled and executes fine.

Here's the code.

using System;
using System.Reflection.Emit;

namespace ConsoleApplication9
{
    public class TestClass
    {
        public void TestMethod() { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Type type = typeof(TestClass);
            DynamicMethod method = new DynamicMethod("", typeof(Object), null);
            ILGenerator il = method.GetILGenerator();
            LocalBuilder variable = il.DeclareLocal(typeof(Object));

            // Construct object
            il.Emit(OpCodes.Newobj, type.GetConstructor(new Type[0]));
            il.Emit(OpCodes.Stloc, variable);

            // Call Test method
            il.Emit(OpCodes.Ldloc, variable);
            // ***************************************** what do I do here?
            il.Emit(OpCodes.Call, type.GetMethod("TestMethod"));

            // Return object
            il.Emit(OpCodes.Ldloc, variable);
            il.Emit(OpCodes.Ret);

            // Create and call delegate
            Func<Object> fn = (Func<Object>)method.CreateDelegate(
                typeof(Func<Object>));
            Object instance = fn();
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

夏有森光若流苏 2024-08-14 18:54:21

简短的回答:

// Call Test method
il.Emit(OpCodes.Ldloc, variable);
il.Emit(OpCodes.Castclass, type);
il.Emit(OpCodes.Call, type.GetMethod("TestMethod"));

但是如何做到这一点呢?嗯,我使用的方法是 Reflector。首先,写一个方法来完成你想做的事情。我提出了以下建议:

private static object PrecompiledTest()
{
    object variable = new TestClass();
    ((TestClass) variable).TestMethod();
    return variable;
}

现在,编译该文件,然后打开 Reflector,然后打开程序集。导航到您的函数并查看它的 MSIL。上面的函数反编译为以下内容:

.method private hidebysig static object PrecompiledTest() cil managed
{
    .maxstack 1
    .locals init (
        [0] object variable,
        [1] object CS$1$0000)
    L_0000: nop 
    L_0001: newobj instance void EmitTest.TestClass::.ctor()
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: castclass EmitTest.TestClass
    L_000d: callvirt instance void EmitTest.TestClass::TestMethod()
    L_0012: nop 
    L_0013: ldloc.0 
    L_0014: stloc.1 
    L_0015: br.s L_0017
    L_0017: ldloc.1 
    L_0018: ret 
}

上面使用 callvirt 而不是 call。我并不是很精通 IL,所以我不确定其中的区别,但 call 在您的示例中确实有效。最后一件事是我们讨论 Reflector 的话题。您可以使用 ReflectionEmitLanguage 插件来生成您的 为您发出代码。该插件为您生成以下代码:

public MethodBuilder BuildMethodPrecompiledTest(TypeBuilder type)
{
    // Declaring method builder
    // Method attributes
    System.Reflection.MethodAttributes methodAttributes = 
          System.Reflection.MethodAttributes.Private
        | System.Reflection.MethodAttributes.HideBySig
        | System.Reflection.MethodAttributes.Static;
    MethodBuilder method = type.DefineMethod("PrecompiledTest", methodAttributes);
    // Preparing Reflection instances
    ConstructorInfo ctor1 = typeof(TestClass).GetConstructor(
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 
        null, 
        new Type[]{
            }, 
        null
        );
    MethodInfo method2 = typeof(TestClass).GetMethod(
        "TestMethod", 
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 
        null, 
        new Type[]{
            }, 
        null
        );
    // Setting return type
    method.SetReturnType(typeof(Object));
    // Adding parameters
    ILGenerator gen =  method.GetILGenerator();
    // Preparing locals
    LocalBuilder variable =  gen.DeclareLocal(typeof(Object));
    LocalBuilder CS$1$0000 =  gen.DeclareLocal(typeof(Object));
    // Preparing labels
    Label label23 =  gen.DefineLabel();
    // Writing body
    gen.Emit(OpCodes.Nop);
    gen.Emit(OpCodes.Newobj,ctor1);
    gen.Emit(OpCodes.Stloc_0);
    gen.Emit(OpCodes.Ldloc_0);
    gen.Emit(OpCodes.Castclass,TestClass);
    gen.Emit(OpCodes.Callvirt,method2);
    gen.Emit(OpCodes.Nop);
    gen.Emit(OpCodes.Ldloc_0);
    gen.Emit(OpCodes.Stloc_1);
    gen.Emit(OpCodes.Br_S,label23);
    gen.MarkLabel(label23);
    gen.Emit(OpCodes.Ldloc_1);
    gen.Emit(OpCodes.Ret);
    // finished
    return method;
}

The short answer:

// Call Test method
il.Emit(OpCodes.Ldloc, variable);
il.Emit(OpCodes.Castclass, type);
il.Emit(OpCodes.Call, type.GetMethod("TestMethod"));

How to come by that though? Well, the method I used was Reflector. Firstly, write up a method that does what you want to do. I came up with the following:

private static object PrecompiledTest()
{
    object variable = new TestClass();
    ((TestClass) variable).TestMethod();
    return variable;
}

Now, compile that, and open Reflector, and open your assembly. Navigate to your function and look at the MSIL for it. The function above decompiles to the following:

.method private hidebysig static object PrecompiledTest() cil managed
{
    .maxstack 1
    .locals init (
        [0] object variable,
        [1] object CS$1$0000)
    L_0000: nop 
    L_0001: newobj instance void EmitTest.TestClass::.ctor()
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: castclass EmitTest.TestClass
    L_000d: callvirt instance void EmitTest.TestClass::TestMethod()
    L_0012: nop 
    L_0013: ldloc.0 
    L_0014: stloc.1 
    L_0015: br.s L_0017
    L_0017: ldloc.1 
    L_0018: ret 
}

The above uses callvirt instead of call. I'm not really all that proficient in IL, so I'm not sure of the difference, but call does work in your example. One last thing, while we're on the topic of Reflector. You can use the ReflectionEmitLanguage addin to great effect to generate your Emit code for you. This addin generates the following code for you:

public MethodBuilder BuildMethodPrecompiledTest(TypeBuilder type)
{
    // Declaring method builder
    // Method attributes
    System.Reflection.MethodAttributes methodAttributes = 
          System.Reflection.MethodAttributes.Private
        | System.Reflection.MethodAttributes.HideBySig
        | System.Reflection.MethodAttributes.Static;
    MethodBuilder method = type.DefineMethod("PrecompiledTest", methodAttributes);
    // Preparing Reflection instances
    ConstructorInfo ctor1 = typeof(TestClass).GetConstructor(
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 
        null, 
        new Type[]{
            }, 
        null
        );
    MethodInfo method2 = typeof(TestClass).GetMethod(
        "TestMethod", 
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 
        null, 
        new Type[]{
            }, 
        null
        );
    // Setting return type
    method.SetReturnType(typeof(Object));
    // Adding parameters
    ILGenerator gen =  method.GetILGenerator();
    // Preparing locals
    LocalBuilder variable =  gen.DeclareLocal(typeof(Object));
    LocalBuilder CS$1$0000 =  gen.DeclareLocal(typeof(Object));
    // Preparing labels
    Label label23 =  gen.DefineLabel();
    // Writing body
    gen.Emit(OpCodes.Nop);
    gen.Emit(OpCodes.Newobj,ctor1);
    gen.Emit(OpCodes.Stloc_0);
    gen.Emit(OpCodes.Ldloc_0);
    gen.Emit(OpCodes.Castclass,TestClass);
    gen.Emit(OpCodes.Callvirt,method2);
    gen.Emit(OpCodes.Nop);
    gen.Emit(OpCodes.Ldloc_0);
    gen.Emit(OpCodes.Stloc_1);
    gen.Emit(OpCodes.Br_S,label23);
    gen.MarkLabel(label23);
    gen.Emit(OpCodes.Ldloc_1);
    gen.Emit(OpCodes.Ret);
    // finished
    return method;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文