Delphi 如何判断哪个程序调用了另一个程序?

k0pti3hp  于 2023-01-20  发布在  其他
关注(0)|答案(5)|浏览(194)

如何确定谁调用了Showme过程?

procedure Showme(str:string);
begin
  ShowMessage(str);
end;

procedure btnclick(sender:TObject);
begin
  Showme("test");
end;

procedure btn2click(sender:TObject);
begin
  Showme("test");
end;

编辑:混乱

Showme(654, '654'); // procedure name, string
Showme(654, '564');
yb3bgrhw

yb3bgrhw1#

一个过程没有内置的方法来知道哪个过程调用了它。如果你真的需要知道,你可以使用堆栈跟踪,但是这类数据实际上只在调试时需要。对于实际执行,重要的是传递给例程的数据。而不是它来自哪里。这是结构化编程的基本原则之一。如果两个不同的例程使用相同的数据调用同一个过程,它应该对它们一视同仁。
你到底在做什么,你需要能够区分?可能有一个更简单的方法来完成它。

6qqygrtg

6qqygrtg2#

你为什么不传递一个不同的常量,甚至是调用它的过程的名称作为Showme的参数呢?为了确定调用函数的名称,可以分析堆栈,但这要复杂得多,你需要添加关于链接(Map文件)的信息。Jedi的JCL有一些函数可以帮助你,但我会使用一个额外的参数。

s8vozzvw

s8vozzvw3#

我假设您正在调试器中运行应用程序,因此您可以在Showme过程中放置一个断点,并在程序停止时激活堆栈视图(查看/调试窗口/调用堆栈)。
它将显示调用它的人,实际上,如果双击任何堆栈条目,IDE将引导您找到进行调用的确切代码行(以防每个调用例程多次调用showme)。
顺便说一句:我认为你必须更努力地表达你的问题。

svgewumm

svgewumm4#

TVirtualMethodInterceptor允许在实际方法之前和之后执行代码,并且有一个包含方法名称的Method参数:
示例代码:

type
  TFoo = class
  public
    procedure Bar; virtual;
  end;

procedure InterceptorBefore(Instance: TObject; Method: TRttiMethod;
  const Args: TArray<TValue>; out DoInvoke: Boolean; out Result: TValue);
begin
  ShowMessage('Before: ' + Instance.ClassName + '.' + Method.Name);
end;

procedure InterceptorAfter(Instance: TObject; Method: TRttiMethod;
  const Args: TArray<TValue>; var Result: TValue);
begin
  ShowMessage('After: ' + Instance.ClassName + '.' + Method.Name);
end;

{ TFoo }

procedure TFoo.Bar;
begin
  ShowMessage('TFoo.Bar');
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  interceptor: TVirtualMethodInterceptor;
  foo: TFoo;
begin
  foo := TFoo.Create;

  interceptor := TVirtualMethodInterceptor.Create(TFoo);
  interceptor.Proxify(foo);
  interceptor.OnBefore := InterceptorBefore;
  interceptor.OnAfter := InterceptorAfter;

  foo.Bar;

end;

注意这是Delphi XE中的新特性,所以我不能在我的系统上测试和验证它。

yvgpqqbh

yvgpqqbh5#

好吧,如果你在modern Delphi上:

procedure Showme(str:string);
var
  Caller: Pointer;
begin
  // This will save address of the NEXT CPU opcode AFTER the call into your function
  Caller := ReturnAddress;  
  // Shift the address, so it will be in the middle of the call into your function
  // It is kinda a hack: the proper code would move address back to the size of the call opcode itself - which is not a trivial thing to do
  // It is optional: you can skip it, if you are OK with the address of next opcode after the call
  Caller := PAnsiChar(Caller) - 1;

  ShowMessage(str);
end;

一旦你获得了指向函数调用指令的指针,你需要把地址转换成可读的字符串。默认情况下这是不可能的,因为编译后的可执行文件不包含函数名,只包含RAW地址(也有例外,比如RTTI,但这不是一般情况)。
但是,如果将调试信息附加到可执行文件,则可以读取它以将地址转换为名称。例如,如果您有EurekaLog:

uses
  EDebugInfo;

procedure Showme(str:string);
var
  Caller: Pointer;
  FunctionName: String;
begin
  Caller := ReturnAddress;  
  Caller := PAnsiChar(Caller) - 1;
  FunctionName := GetLocationInfoStr(Caller);
  ShowMessage(FunctionName + ': ' + str);
end;

然而:
1.只有在使用一些调试信息(以及corresponding debug info provider is enabled)进行编译时,它才能工作:

  • EurekaLog有自己的调试信息格式,可以选择压缩(实际上不推荐压缩,因为这会花费更多的内存和CPU)。
  • 它还支持JCL/JEDI、Synopse/MAB以及. map、. tds/TD32、. dbg和. pdb。

1.它不是一个常量。名字将被动态查找,所以它有一些运行时间开销。
P.S.请注意,你只应该为了日志记录/调试的目的这样做。你的应用逻辑不应该这样实现。你应该使用类,虚函数,RTTI,调度表,或脚本引擎-这取决于你想做什么。

相关问题