我在进行 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! 不过,我前天做了个简单的例子,好像有时又是正常的。唉,真是头痛! 不知道各位遇见过这种情况没有,望指点!

解决方案 »

  1.   

    作了个例子,希望对你有帮助;
    大致如下
    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;
    ...
    则我的错误被触发,系统错误被覆盖.
    个人已为,谁先触发,后面的就会被覆盖.
      

  2.   

    To sz1008 兄:    谢谢你的回复!不过你所说的问题和我想问的好像并不一样!
        
        你看看我对你所举例子的理解:
        
    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;
    如果我的理解和你所想表述的相同,那么我对此毫无异议! 事实肯定是这样的!
    但是我的问题却不是这样的!
      

  3.   

    我举个简单例子:// COM+ 组件 TB:
    // 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;
      

  4.   

    我刚才又把程序作了点改动,发现有很多地方都有问题。以下的测试都是改动了上面例子的 procedure TA.GetData(var Datas: OleVariant);测试1: procedure TA.GetData(var Datas: OleVariant);
    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 中那样自己来控制异常处理,不过这样又需要自己做大量的底层工作,很麻烦也更容易出错。唉,真是矛盾啊!
      

  5.   

    看了你的代码后,试了一下try...except..end格式。
    (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错误.
      

  6.   

    漏了一句,应为:去了raise注释同时,注释掉raise EOleException.Create('OleError',0,'','',0);Client端又提示Pool错误.
      

  7.   

    我不是搞COM的 hehe^^ 比较菜 来这里学习学习 不要笑我哦
      

  8.   

    今天上午我又研究了一下,终于找到了问题的真正原因:我的 TB 组件用 ADO 进行了数据库访问,代码改成以下形式就不会有问题了  try
        ......
      except
        Connection.Close;
        DataSet.Close;
        // 在 SetAbort 以及重新抛出异常之前,需要关闭 ADOConnection 、ADODataSet
        SetAbort;
        raise;
      end;
      出现“灾难性故障”并不是 Delphi 的 safecall 异常处理的问题,我先前的判断是错误的!非常感谢大家的参与!结贴!