关于c#:如何通过堆栈跟踪访问本地?

How to access locals through stack trace? (Mimicking dynamic scope)

背景

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

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

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

问题

所以我的问题是(不管它是否真的有用;)是否可以通过反射来模拟.NET中的动态范围。

由于.NET为我们提供了允许我们检查调用方法的Diagnostics.StackTrace类,所以这个问题可以归结为:(如何)能够可靠地访问调用方法的局部变量?

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

这样的代码有可能吗?

1
2
3
4
5
6
7
8
void Foo() {
   int x = 42;
   Console.WriteLine(Bar());
}

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


So my question is whether it's possible to mimic dynamic scope in .NET through the use of reflection.

反射允许对程序集元数据中表示的项进行运行时操作。

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

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

(How) is it possible to reliably access the locals of calling methods?

访问调用方法的局部变量的设备称为"调试器"。所以问题的答案是"给自己写一个调试器"。

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

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

如果您想要的是一种进程内的"eval"功能,那么可以考虑jscript.net,一种专门为这种功能设计的语言。


这是不可能的。在编译的.NET代码(中间语言)中,变量简单地表示为堆栈上的索引。例如,加载变量值的ldloc指令只取unsigned int16值作为参数。对于以调试模式编译的应用程序,可能有某种方法可以做到这一点(毕竟,在调试应用程序时,Visual Studio会做到这一点),但一般来说,它不能工作。

在phalanger(我部分参与的php编译器for.net)中,必须以某种方式解决这个问题,因为php语言有eval(它不需要提供动态范围,但需要按名称访问变量)。因此,phalanger检测一个函数是否包含对eval的任何使用,如果包含,它将把所有变量存储在Dictionary中,然后将这些变量传递给eval函数(以便它可以按变量的名称读取变量)。恐怕这是唯一的办法…