我在进行 COM+ 编程时,遇见如下问题:现有两个 COM+ 组件 A 和 B,基客户(Base Client)调用 A,A 然后调用 B。在 B 的方法中抛出一个异常,并且设置其 Message 属性为自定义的消息提示。当我在 A 中截获此异常时发现其 Message 属性变成了“灾难性故障”,相信大家会经常遇见这种情况。 对于此种现象原先我也没太在意,但是后来发现还是有问题。对于 dual 接口的方法,Delphi 一般都会把它们的调用约定定义为 safecall,也就是说,如果在被调用方抛出异常的话,调用方能够捕获它(此时的异常类型为EOleException),并且能够获得该异常的详细信息,比如 Message、ErrorCode、Source、HelpFile、HelpContext 等。之所以能够如此,Delphi 在底层做了很多工作,大概过程是:在被调用方抛出异常后,Delphi 通过 CreateErrorInfo、SetErrorInfo 这两个 API 建立并且设置了 COM 的 Error Information Object,而后在调用方通过 GetErrorInfo 获得刚才的 Error Information Object,并且把它转化成 Delphi 中的 EOleException 异常。我通过调试程序发现,出现我刚才所说的问题——自定义的消息提示变成了“灾难性故障”——的原因就在于调用方通过 GetErrorInfo 并没有获得 Error Information Object! 不过,我前天做了个简单的例子,好像有时又是正常的。唉,真是头痛! 不知道各位遇见过这种情况没有,望指点!
解决方案 »
- 怪事,大虾看看~~
- 求救环境出错问题.
- 怎样把Word, Excel转换为BMP? 编码实现,不用安装虚拟打印机?
- 哈欠老大结婚了??!!Kao~~ 只前一点消息都没有,放分庆祝一下先
- 如何在StringGrid中设置其中一行的字体格式、颜色等?
- 怎么把数据库中的图像显示在 DBImage1 控件中
- 怎样实现listview在report样式下,单击列头可以使列中数据排序?
- 小问题
- 如何把QuickReport里面的QrLabel的所有字符旋转90度,并且字符串也转90度?
- 请教高手,如何在三层结构下,获知客户端与Application Sever端以及Application Sever与数据库服务器端
- Dephi读取Excel文件的问题!(急!在线等!50分!)
- 数据库编程如何更好的结合面向对象
大致如下
Server端(Com+):procedure TComPool.GetData(var Datas: OleVariant);
var RecsOut: Integer;
Options: TGetRecordOptions;
begin
Options := [grMetaData,grReset] ;
Datas := DataSetProvider1.GetRecords(-1,RecsOut,Byte(Options)); //此处共取10条 纪录
if RecsOut > 0 then
SetComplete
else
begin
raise EOleException.Create('OldError',0,'','',0);
SetAbort;
end;
.....
end;Client端:
procedure TForm1.Button1Click(Sender: TObject);
var Datas: OleVariant;
begin
UsePool := CoComPool.CreateRemote('') ;
ClientDataSet1.Active := False;
try
UsePool.GetData(Datas);
except
on E:EOleException do
ShowMessage(E.Message);
end;
......
end;我在Server端故意设了一个有关Pool的错误,错误提示为‘Distributed transaction completed.Either enlist this session in a new transaction or the NULL';
在上面情况下,我的错误被此复盖,当我将代码改为
....
if RecsOut > 11 then
SetComplete
else
begin
raise EOleException.Create('OldError',0,'','',0);
SetAbort;
end;
...
则我的错误被触发,系统错误被覆盖.
个人已为,谁先触发,后面的就会被覆盖.
你看看我对你所举例子的理解:
procedure TComPool.GetData(var Datas: OleVariant);
var RecsOut: Integer;
Options: TGetRecordOptions;
begin
……
Datas := DataSetProvider1.GetRecords(-1,RecsOut,Byte(Options));
// 假设上面的调用一共只取了 10 条纪录,是这样的吧?
if RecsOut > 0 then
SetComplete
else
// 由于 RecsOut > 0,因此 else 子句并不会执行,
// 因此自定义的 EOleException 不会抛出,是吧?
begin
raise EOleException.Create('OldError',0,'','',0);
SetAbort;
end;
.....
end;由于你在 Server 端故意设了一个有关 Pool 的错误,客户端就捕获了此 Pool 错误。
但是如果把代码改为 if RecsOut > 11 then
SetComplete
else
// 由于 RecsOut = 10 是小于 11 的,因此将执行 else 子句。
// 因此自定义的 EOleException 会被抛出,是吧?
// 而且是在 你所设的 Pool 的错误之前被抛出的
// 此时客户端应该捕获的是 else 子句所抛出的异常,而不是 Pool 错误。是吧?
begin
raise EOleException.Create('OldError',0,'','',0);
SetAbort;
end;
如果我的理解和你所想表述的相同,那么我对此毫无异议! 事实肯定是这样的!
但是我的问题却不是这样的!
// TB 实现了 IB 接口,其中有 GetData 方法procedure TB.GetData(var Datas: OleVariant);
begin
try
.....
raise EOleException.Create('OldError',0,'','',0);
.....
SetComplete;
except
SetAbort;
raise;
end;
end;// COM+ 组件 TA:
// TA 实现了 IA 接口,其中也有 GetData 方法procedure TA.GetData(var Datas: OleVariant);
var
B: IB;
begin
try
ObjectContext.CreateInstance(Class_TB, IID_IB, B);
.....
B.GetData(Datas); // 调用了 IB 接口的 GetData 方法
.....
SetComplete;
except
SetAbort;
raise;
end;
end;
// Client 端:
// Client 通过 IA 接口调用 COM+ 组件 TA 的 GetData 方法procedure TForm1.Button1Click(Sender: TObject);
var Datas: OleVariant;
A: IA;
begin
A := CoA.CreateRemote(''); // 获得 IA 接口
try
A.GetData(Datas);
except
on E:EOleException do
ShowMessage(E.Message);
// 此时的 E.Message 通常为 “灾难性故障”!
// 我调试程序后发现,其实在 TA.GetData 中调用 B.GetData(Datas) 后
// 所捕获异常的 Message 就已经变成了“灾难性故障”。
// 而按道理来说, 其 Message 应该为“OldError”!
// 这个就是我的问题所在!
// 不知道我的表述是否清楚,欢迎大家参与讨论
end;
......
end;
var
B: IB;
begin
ObjectContext.CreateInstance(Class_TB, IID_IB, B);
.....
B.GetData(Datas); // 调用了 IB 接口的 GetData 方法
.....
// 这个测试 去掉了 try..except
// 我特意跟踪了一下 B.GetData(Datas);语句之后 Delphi 生成的汇编代码,
// 发现最后执行了 RaiseException 这个 API,
// 根据 MSDN 的说明,RaiseException raises an exception in the calling thread
// 而且如果没有 异常处理代码,RaiseException 会调用 ExitProcess。
// 我调试的时候,当继续执行完 TA.GetData,客户端会出现“灾难性故障”的提示,
// 此时我正在调试的进程也自动结束,可能就是这个原因。不过我也不敢肯定。
end;测试2:procedure TA.GetData(var Datas: OleVariant);
var
B: IB;
begin
try
ObjectContext.CreateInstance(Class_TB, IID_IB, B);
.....
B.GetData(Datas); // 调用了 IB 接口的 GetData 方法
.....
SetComplete;
except
on E: Exception do
begin
SetAbort;
// 此时察看 E.Message ,其值为 TB.GetData 中的自定义消息提示“OldError”
// 这说明 GetErrorInfo 获得了 TB 生成的 Error Information Object
// 而我起初的问题是在这个地方 不能获得 TB 生成的 Error Information Object
// 为什么会出现这种矛盾情况,我也没搞清楚
// 但是在下面的 raise 之后,又出现了问题
raise;
// 我跟踪了其汇编代码,发现最后调用了 system 单元的 _RaiseAgain
// 在此之后就直接返回了客户端,客户端就出现“灾难性故障”的提示
// 我猜想,由于没有重新生成 Error Information Object,
// 客户端也就不可能获得异常的详细信息
end;
end;
end;
总的感觉,Delphi 的 safecall 调用约定,把 COM 中的错误处理和 Delphi 自身的异常处理结合了起来,编程也变得较之 VC 容易。但是其中还存在着很多的缺陷不容易克服,但如果把 safecall 改为 stdcall,也就可以像 VC 中那样自己来控制异常处理,不过这样又需要自己做大量的底层工作,很麻烦也更容易出错。唉,真是矛盾啊!
(Client不变,Server改为下).......
Options := [grMetaData,grReset] ;
try
Datas := DataSetProvider1.GetRecords(-1,RecsOut,Byte(Options));
SetComplete;
except
raise EOleException.Create('OleError',0,'','',0);
SetAbort;
// raise;
end;
..........
在调试中发现,系统先触发系统Pool错误,转至except语句由raise EOleException.Create('OleError',0,'','',0);替代原有错误(与我昨天的观点相反);Client端提示我的错误,如果去了raise注释,raise语句重新触发本次错误,Client端又提示Pool错误.
......
except
Connection.Close;
DataSet.Close;
// 在 SetAbort 以及重新抛出异常之前,需要关闭 ADOConnection 、ADODataSet
SetAbort;
raise;
end;
出现“灾难性故障”并不是 Delphi 的 safecall 异常处理的问题,我先前的判断是错误的!非常感谢大家的参与!结贴!