B
beta
Unregistered / Unconfirmed
GUEST, unregistred user!
声明:本文乃 熊恒(beta) 原创,如要转载请保持文章完整。
如何判断程序刚才出现了异常 --- by 熊恒(beta)
我们都知道,在 try - finally 结构中,无论 try 中的代码是否出现异常,finally
中的代码都一定会执行,那么我们在 finally 部分中能否判断在刚才执行 try 中代码
的时候是否出现了异常呢?答案是肯定的。我这里有两个办法,一个是最容易想到的笨
办法,另一个是相对比较有技巧性的办法。老规矩,重点介绍的放在后面。这两个方法
(实现)都有一个共同的假设:假设你需要一个do
Log 过程来写日志,当然,你需要
在这个过程中判断在该过程调用前是否发生了异常,以便决定日志的内容。
方法一:
proceduredo
Log(const AnyException: Boolean);
begin
if not AnyException then
begin
// 没有发生异常
// ...
end else
begin
// 发生了异常
// ...
end;
end;
procedure Work;
var
AnyException: Boolean;
begin
AnyException := False;
try
// call some func
AnyException := True;
finally
do
Log(AnyException);
end;
end;
可能有人要说了,“晕,这是什么解决办法”,别急,这为什么不是解决办法?它能
解决这个问题,那它就是这个问题的一个解决办法。我觉得,通常情况下,不必把问题
复杂化,很多看似复杂的问题其实反而都有一个简单或者说是“笨”的解决办法,当你
没有其他更好的办法或寻找其他办法所需要的代价相对较大的时候,“笨”一下何妨?
OK,要是真的只有这点东西,我也不好意思专门写一篇心得贴出来,看另一个办法吧。
方法二:
受了 Another_eYes 大虾的启发,本想从堆栈里面找出蛛丝马迹,结果转来转去,蛛
丝和马迹都找到不少,想想应该可以通过一系列复杂的操作达到目的,结果发现这样搞
太复杂,差点害我掉进了沼泽。
就在快要失望放弃的时候,我又有了意外的发现(我怎么总是有意外的发现?运气真
好,呵呵)。在 try 中的最后一行代码执行完过后,finally 中第一行代码执行之前,
编译器会自动插入一段代码,类似这个样子:
xor eax, eax // 清空 eax 寄存器
pop edx // 恢复 edx 的值
pop ecx
pop ecx
mov fs:[eax], edx // 不必管它
push $00458d32 // try - finally 出口
如果在 try 中出现了异常,那么程序会从出错的地点直接跳到内部的出错处理部分,
然后再跳到我们的 finally 部分(严格来说,过程不是这样,不过你可以认为是这样,
对于处理本问题是适用的,要是有朋友想打破沙锅问到底,我再罗嗦也不迟),也就是
说,如果出现了异常,则这一段代码不会被执行到。于是,我们就可以充分利用这段代
码的特点,来实现我们的目的。
其实问题的关键就在这第一行代码,清空 eax,而其后的几行代码都没有修改 eax 寄
存器的值;而刚才我们也说到了,这段代码是插在 finally 中我们自己代码的第一行前
面的。这就是说,我们完全可以在 finally 的第一行判断 eax 的值,就可以知道刚才
是否发生异常了。
我知道,你又会问了,要是出现了异常,但是在跳转到内部出错处理部分过后,在执
行我们的 finally 部分中第一行语句之前,eax 恰好也被修改为了 0 怎么办?据我观
察,在执行我们的 finally 中第一行之前,eax 的值应该是指向堆栈中的某个位置,
而且就在栈顶附近,这是由之前的最后一次函数调用决定的,所以几乎可以说它在这种
情况下肯定不会是 0 的(要是你发现了特例,记得告诉我)。
好了,知道怎么做了,我们先看解决代码吧,其实虽然这个解决方法看起来比较复杂,
其实其解决代码比前一个方法更简单(要不然不是白搞半天,呵呵),还有一个小技巧
在里面。
proceduredo
Log(const Flag: Integer);
begin
if Flag = 0 then
begin
// 没有发生异常
// ...
end else
begin
// 发生了异常
// ...
end;
end;
procedure Work;
begin
try
// call some func
finally
asm calldo
Log end;
end;
end;
刚才不是说了要在 finally 中的第一行判断 eax 吗?怎么没有了?其实是有的,要
知道,在 Delphi 中,普通的过程调用的第一个参数就是放在 eax 中的,所以其实我
是把这个判断放到do
Log 过程中了。
那你说我在 Work 过程中为何要用汇编来调用do
Log 呢?其实,如果不这样,我就
必须显示地去传递一个参数给它,否则编译器不会放过我,但我上哪里找参数传给它?
DoLog(eax)?呵呵,这可不行。
那何不干脆让do
Log 没有参数,直接在do
Log 里面判断 eax 不也可以吗?还是不
行,DoLog 在执行第一行用户代码前,还有动作,而如果你的过程没有参数的话,这些
动作很有可能修改 eax。
哦,那还可以先将 eax 入栈,然后调用无参数的do
Log,然后在do
Log 中将刚才的
eax 弹出来,再判断。晕,这当然可以,不过这不是自找麻烦嘛。
想来想去,还是上面的这几行代码最精练。好了,我也想累了,剩下的交给其他人想吧。
附:我的调试代码
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
proceduredo
Log(const Flag: Integer);
begin
if Flag = 0 then
ShowMessage('No Except')
else
ShowMessage('Any Except');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i, j: Integer;
x:do
uble;
begin
i := 5;
j := StrToIntDef(Edit1.Text, 0);
try
x := i / j;
// 下面这行可不能少哦,不然你计算出来的 x 没有被用到,
// 就不会计算,就不会引发“被 0 除”异常了
Caption := FloatToStr(x);
finally
asm calldo
Log end;
end;
end;
如何判断程序刚才出现了异常 --- by 熊恒(beta)
我们都知道,在 try - finally 结构中,无论 try 中的代码是否出现异常,finally
中的代码都一定会执行,那么我们在 finally 部分中能否判断在刚才执行 try 中代码
的时候是否出现了异常呢?答案是肯定的。我这里有两个办法,一个是最容易想到的笨
办法,另一个是相对比较有技巧性的办法。老规矩,重点介绍的放在后面。这两个方法
(实现)都有一个共同的假设:假设你需要一个do
Log 过程来写日志,当然,你需要
在这个过程中判断在该过程调用前是否发生了异常,以便决定日志的内容。
方法一:
proceduredo
Log(const AnyException: Boolean);
begin
if not AnyException then
begin
// 没有发生异常
// ...
end else
begin
// 发生了异常
// ...
end;
end;
procedure Work;
var
AnyException: Boolean;
begin
AnyException := False;
try
// call some func
AnyException := True;
finally
do
Log(AnyException);
end;
end;
可能有人要说了,“晕,这是什么解决办法”,别急,这为什么不是解决办法?它能
解决这个问题,那它就是这个问题的一个解决办法。我觉得,通常情况下,不必把问题
复杂化,很多看似复杂的问题其实反而都有一个简单或者说是“笨”的解决办法,当你
没有其他更好的办法或寻找其他办法所需要的代价相对较大的时候,“笨”一下何妨?
OK,要是真的只有这点东西,我也不好意思专门写一篇心得贴出来,看另一个办法吧。
方法二:
受了 Another_eYes 大虾的启发,本想从堆栈里面找出蛛丝马迹,结果转来转去,蛛
丝和马迹都找到不少,想想应该可以通过一系列复杂的操作达到目的,结果发现这样搞
太复杂,差点害我掉进了沼泽。
就在快要失望放弃的时候,我又有了意外的发现(我怎么总是有意外的发现?运气真
好,呵呵)。在 try 中的最后一行代码执行完过后,finally 中第一行代码执行之前,
编译器会自动插入一段代码,类似这个样子:
xor eax, eax // 清空 eax 寄存器
pop edx // 恢复 edx 的值
pop ecx
pop ecx
mov fs:[eax], edx // 不必管它
push $00458d32 // try - finally 出口
如果在 try 中出现了异常,那么程序会从出错的地点直接跳到内部的出错处理部分,
然后再跳到我们的 finally 部分(严格来说,过程不是这样,不过你可以认为是这样,
对于处理本问题是适用的,要是有朋友想打破沙锅问到底,我再罗嗦也不迟),也就是
说,如果出现了异常,则这一段代码不会被执行到。于是,我们就可以充分利用这段代
码的特点,来实现我们的目的。
其实问题的关键就在这第一行代码,清空 eax,而其后的几行代码都没有修改 eax 寄
存器的值;而刚才我们也说到了,这段代码是插在 finally 中我们自己代码的第一行前
面的。这就是说,我们完全可以在 finally 的第一行判断 eax 的值,就可以知道刚才
是否发生异常了。
我知道,你又会问了,要是出现了异常,但是在跳转到内部出错处理部分过后,在执
行我们的 finally 部分中第一行语句之前,eax 恰好也被修改为了 0 怎么办?据我观
察,在执行我们的 finally 中第一行之前,eax 的值应该是指向堆栈中的某个位置,
而且就在栈顶附近,这是由之前的最后一次函数调用决定的,所以几乎可以说它在这种
情况下肯定不会是 0 的(要是你发现了特例,记得告诉我)。
好了,知道怎么做了,我们先看解决代码吧,其实虽然这个解决方法看起来比较复杂,
其实其解决代码比前一个方法更简单(要不然不是白搞半天,呵呵),还有一个小技巧
在里面。
proceduredo
Log(const Flag: Integer);
begin
if Flag = 0 then
begin
// 没有发生异常
// ...
end else
begin
// 发生了异常
// ...
end;
end;
procedure Work;
begin
try
// call some func
finally
asm calldo
Log end;
end;
end;
刚才不是说了要在 finally 中的第一行判断 eax 吗?怎么没有了?其实是有的,要
知道,在 Delphi 中,普通的过程调用的第一个参数就是放在 eax 中的,所以其实我
是把这个判断放到do
Log 过程中了。
那你说我在 Work 过程中为何要用汇编来调用do
Log 呢?其实,如果不这样,我就
必须显示地去传递一个参数给它,否则编译器不会放过我,但我上哪里找参数传给它?
DoLog(eax)?呵呵,这可不行。
那何不干脆让do
Log 没有参数,直接在do
Log 里面判断 eax 不也可以吗?还是不
行,DoLog 在执行第一行用户代码前,还有动作,而如果你的过程没有参数的话,这些
动作很有可能修改 eax。
哦,那还可以先将 eax 入栈,然后调用无参数的do
Log,然后在do
Log 中将刚才的
eax 弹出来,再判断。晕,这当然可以,不过这不是自找麻烦嘛。
想来想去,还是上面的这几行代码最精练。好了,我也想累了,剩下的交给其他人想吧。
附:我的调试代码
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
proceduredo
Log(const Flag: Integer);
begin
if Flag = 0 then
ShowMessage('No Except')
else
ShowMessage('Any Except');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i, j: Integer;
x:do
uble;
begin
i := 5;
j := StrToIntDef(Edit1.Text, 0);
try
x := i / j;
// 下面这行可不能少哦,不然你计算出来的 x 没有被用到,
// 就不会计算,就不会引发“被 0 除”异常了
Caption := FloatToStr(x);
finally
asm calldo
Log end;
end;
end;