如何通过堆栈跟踪访问本地? (模仿动态范围)

时间:2010-02-14 15:47:30

标签: c# .net scope dynamic-languages

背景

即使可以在运行时编译C#代码,也不可能在当前范围中包含和运行生成的代码。相反,所有变量都必须作为显式参数传递。

与Python等动态编程语言相比,人们永远无法真正复制eval的完整行为(如本例所示)。

x = 42
print(eval("x + 1")) # Prints 43

问题

所以我的问题是(无论它是否真的有用;))是否可以通过使用 reflection 在.NET中模仿动态范围

由于.NET为我们提供了允许我们检查调用方法的Diagnostics.StackTrace类,因此该问题归结为以下内容:(如何)可以可靠地访问调用方法的本地

堆栈跟踪是否为我们提供了足够的信息来计算内存偏移量,或者托管代码中是否禁止这样做?

这样的代码是否有可能?

void Foo() {
   int x = 42;
   Console.WriteLine(Bar());
}

int Bar() {
   return (int)(DynamicScope.Resolve("x")); // Will access Foo's x = 42
}

2 个答案:

答案 0 :(得分:8)

  

所以我的问题是,是否可以通过使用反射来模拟.NET中的动态范围。

Reflection支持对程序集元数据中表示的项目进行运行时操作。

局部变量不是程序集元数据中表示的项。

因此,你的问题的答案是“不”。

  

(如何)可以可靠地访问调用方法的本地?

访问调用方法的本地的设备称为“调试器”。所以你的问题的答案是“自己写一个调试器”。

请注意,调试器不会可靠地访问具有代码优化的世界中的本地人。本地可以被优化掉,抖动可以生成代码,这些代码对两个不同的本地使用相同的寄存器,堆栈帧可以在尾调用期间重复使用,等等。当然,您最好使用PDB文件告诉调试器与每个堆栈帧位置关联的名称是什么。

您的方法必须做的是将自定义调试器作为新进程启动,然后等待调试器向其发送消息。然后调试器将挂起原始进程的线程,查询堆栈帧,然后恢复进程的线程。然后它会向调试对象发送包含您发现的信息的消息。由于调试对象正处于等待状态等待消息,因此它将恢复其业务。

如果您想要的是进程内“eval”功能,请考虑JScript.NET,这是为这类事物设计的语言。

答案 1 :(得分:5)

这是不可能的。在编译的.NET代码(中间语言)中,变量简单地表示为堆栈上的索引。例如the ldloc instruction,它加载变量的值只需unsigned int16值作为参数。对于在调试模式下编译的应用程序,可能有某种方法可以执行此操作(毕竟,在调试应用程序时,Visual Studio会这样做),但它通常无法正常工作。

在Phalanger(.NET编译器,我部分参与其中),这必须以某种方式解决,因为PHP语言有eval(它不需要提供动态范围,但它需要按名称访问变量)。因此,Phalanger检测函数是否包含eval的任何使用,如果是,它将所有变量存储在Dictionary<string, object>中,然后传递给eval函数(以便它可以读取)变量按名称)。我担心这是唯一的方法......