COM+开发中有关接口类型的属性的问题,请先看一下代码:interfaceuses
  ...., TestLib_TLB;
  
type
{
  IError = interface(IDispatch)
    ['{10FE2C40-83CD-463B-9F3E-4A3D8CF18156}']
    function Get_Number: Integer; safecall;
    function Get_Source: WideString; safecall;
    function Get_Description: WideString; safecall;
    property Number: Integer read Get_Number;
    property Source: WideString read Get_Source;
    property Description: WideString read Get_Description;
  end;
  
  ITest = interface(IDispatch)
    ['{4089D564-07A2-465F-B2DC-F8145A52037A}']
    function Get_Error: IError; safecall;
    property Error: IError read Get_Error;
  end;  
}{ TError }
  
  TError = class(TAutoIntfObject, IError)
  private
    FNumber: Integer;
    FSource: WideString;
    FDescription: WideString;
  protected         
    { IError }
    function Get_Number: Integer; safecall;
    function Get_Source: WideString; safecall;
    function Get_Description: WideString; safecall;
  public
    constructor Create;
    property Number: Integer read FNumber write FNumber;
    property Source: WideString read FSource write FSource;
    property Description: WideString read FDescription write FDescription;
  end;{ TTestMtsAutoObject }  TTestMtsAutoObject = class(TMtsAutoObject, ITest)
  private
    FError: TError;
    FErrorIntf: IError; {****}
  protected
    procedure OnActivate; override;
    procedure OnDeactivate; override;
  protected
    { TTest }
    function Get_Error: IError; safecall;
  public
    property Error: TError read FError;
  end;implementation{ TError }constructor TError.Create;
var
  TestLib: ITypeLib;
begin
  OleCheck(LoadRegTypeLib(LIBID_TestLib, 1, 0, 0, TestLib));
  inherited Create(TestLib, IError);
end;function TError.Get_Number: Integer;
begin
  Result := FNumber;
end;function TError.Get_Source: WideString;
begin
  Result := FSource;
end;function TError.Get_Description: WideString;
begin
  Result := FDescription;
end;{ TTestMtsAutoObject }procedure TTestMtsAutoObject.OnActivate;
begin
  inherited OnActivate;
  FError := TError.Create;
  FErrorIntf := FError; {****}
end;procedure TTestMtsAutoObject.OnDeactivate;
begin
  inherited OnDeactivate;
  FErrorIntf := nil; {****}
  FError := nil;
end;function TTestMtsAutoObject.Get_Error: IError;
begin
  Result := FErrorIntf; {****}
  //Result := FError as IError;
end;在上面的情形下,使用了FErrorIntf变量,客户端调用是没问题的。但是如果去掉所有FErrorIntf{****}相关的内容(同时在Get_Error中使用注释的代码),
那么在客户端第一次使用Error属性是正确的,在第一次之后再使用Error属性就出现“灾难性故障”了,
调试后发现在COM+里FError对象是存在的,有正确的地址内容,而客户端得到的Error属性是nil。
为什么“FError as IError”在第一次正确,而后就不正确了呢?搞不懂为什么?

解决方案 »

  1.   

    一个 COM+ 对象的生命周期只延续一个方法调用的时间。在执行 Get_Error 的前后 会分别调用 OnActivate 和 OnDeactivate。现在你应该明白问题出在哪儿了吧?像楼主这样的设计是不恰当的。
      

  2.   

    上面我说的第一句话不太准确。 当 COM+ 对象能 Pooling 时,它的生命周期就不止一个方法调用的时间。
    但是不管如何,每一次方法调用的前后都会 Activate 和 Deactivate。
      

  3.   

    嗯,我已经发现使用OnActivate和OnDeactivate来处理是不合适的
    我已经采用了Initialize和Destroy,但情况依然存在……但是,象我上面说的采用 FErrorIntf 来存储一下,是没有问题的,
    或者在创建FError := TError.Create后调用一下FError._AddRef也可以,
    在Destroy里再调用_Release。这个问题应该和引用计数有关,在FError := TError.Create后,引用计数并不是1而是0,
    所以第一次调用属性完成后由于会调用_Release,此时RefCount 为0,自动Destroy了。
      

  4.   

    TO:  leapmars(流铭) 非常感谢你的回复。 我之所以要那么设计,是为了想实现类似ADO的形式,比如Recordset有Fields属性,Fields
    又有Item属性,每个Item是Field等等这样的形式。 不知道你有没有更好的设计
      

  5.   

    感觉楼主对 COM+ 的理解出现偏差了!〉〉我之所以要那么设计,是为了想实现类似ADO的形式,比如Recordset有Fields属性,Fields 又有Item属性,每个Item是Field等等这样的形式。COM+ 希望程序员开发无状态对象。至于为什么,不用我说吧? ^_^  但是,你看看自己的设计,能够达到要求吗?
    另外,假设你开发的 COM+ 对象能够 Pooling,它就可能会服务于多个客户。当你在 COM+ 对象中保存了状态,就很有可能影响到下一个使用该对象的客户,造成无法预测的错误。
      

  6.   

    TO: leapmars(流铭)   再次感谢!!
     我希望能当面想你请教,不知道是否可以……我的MSN是:[email protected]
      

  7.   

    关于一个 COM+ 对象的生命周期,我经过测试发现是这样的:不管对象是否支持POOLING,如果对象的方法中没有调用SetComplete或SetAbort,那么在
    Activate后一直激活,不会Deactivate,知道客户方释放接口引用。如果对象方法调用了SetComplete或SetAbort,将在调用结束后立刻被Deactivate
    书上讲了,这是应该的
      

  8.   

    楼上说得不错。的确“如果对象的方法中没有调用 SetComplete 或 SetAbort,那么在 Activate 后会一直激活,不会 Deactivate……”。我并不是说 楼主想达到的效果不能实现, 而是提出一个疑问:这种设计适不适合?!每一种技术都有其特定的应用范围。COM+ 的目标是作为企业级应用的基础服务。作为企业级应用,要求应用系统具有延展性、健壮性,能够承载大量用户……  COM+ 为了简化企业级应用程序的开发,提供了诸如对象池、分布式事务等一系列基础服务。如果楼主的程序不是企业级的应用程序、不和后台数据库打交道、不需要事务保护……,那么 你完全可以实现你想要的效果,但是 还用得着 COM+ 吗?就像 ADO 那样,直接用 COM 不就行了!如果楼主的程序需要操作数据库、需要处理数据、需要事务保护……,你能 不在 COM+ 对象方法中调用 SetComplete 或 SetAbort 吗?如果你说可以自己来进行事务处理,还是那句话,那还用得着 COM+ 吗? 那等于是你自己开发了一个“COM+”。 撇开上面的不谈,如果每个用户一直霸占着 COM+ 对象不释放(要是像楼主这样的设计,这个 COM+ 对象还会包含很多集合对象,集合对象又包含子对象),恐怕服务器会累趴下的! ^_^
      

  9.   

    leapmars(流铭) 说得很在理我是这么想的,请多多指教:我的客户端并不是霸占着COM+不释放,我只是想让COM+对象能延续多个方法的时间,在这个过程中保留一定的状态,并且想让对象在一个事务结束后(SetComplete)仍然可以使用状态信息,在一个完整的业务操作全部完成后自然肯定会释放对象……我的系统里肯定需要操作数据库,需要处理数据,需要事务保护,但我想对于数据库操作的事务保护并一定非要使用COM+的事务功能(SetComplete),完全可以用ADO Connection的事务方法来保证(因为我的系统里用的是ADO+SQL SERVER),当然如果是其他事务比如消息队列的操作,那还是需要用COM+的事务来保证。
    我想我用ADO的事务不会代表着自己开发了一个“COM+”吧?同样是M$的东西,ADO的事务不会比COM+的事务差吧?
    我是这么认为的,COM+的事务处理的好处是不管是什么事务(数据库事务,消息队列事务),只要有对应的COM+资源管理器(RM)并已经向COM+注册,那么COM+就可以处理相应类型的事务。但对于纯粹的数据库事务来讲,完全可以用ADO Connection的事务来处理,对吧?如果每个有关数据更新的方法都调用SetComplete,我测试过了,一旦SetComplete,对象就被Deactivate,被放入池中,如果业务操作只需要调用这一个方法,那很好,没有问题。但往往在实际中(我的系统中),需要连续进行很多业务操作,需要调用很多方法,这样的话
    在一次客户端调用周期中,对象被频繁Deactivate和Activate,我想肯定也会丧失一些性能吧?而且在一个调用期间保留不了状态,我觉得两者的性能比较好像不能光说了,得实际测试比较才行,我正在测试中……按我的想法去做,并不代表着没有使用COM+的Pooling等特性,一个客户端完整的业务操作的调用完成后,对象被释放,自然回到池中。假设一个COM+对象,按照上面的两种方法分别实现,并且一次业务操作都需要调用三个方法:
    那么:
    1. 用SetComplete的方式对象将被Activate和Deactivate三次,保留不了状态;2. 用我的想法,用ADO处理事务,对象只被Activate和Deactivate一次,并且可以保留状态;如果在OnActivate和OnDeactivate中的代码具有一定消耗的话,那么我想性能应该是1不如2了但是如果某个方法的执行时间比较长的话,对多用户访问来讲,性能可能还是1好---------------------------
    以上是我的一些浅浅的认识,讲得乱了些,还希望leapmars(流铭)多多指教,多谢……
      

  10.   

    还有一个办法可以实现在多个方法之间保留状态,
    比如一次完整的业务调用要进行三个方法的调用,
    在前两个方法中不使用SetComplete而是使用EnableCommit,
    最后一个方法调用才调用SetComplete,
    这样可以在三个调用期间保留状态,并实现事务控制。可这种方法对客户端调用增加了约束,方法的调用需要按照
    一定的调用顺序来进行……麻烦对于消息队列等不是数据库事务的事务来说,可以增加“协调组件”的概念
    来实现,下班了,先不多说了……
      

  11.   

    系统设计没有一个定式。如果楼主实现的系统能满足客户需求,那当然没问题。 ^_^这里,我只提一个疑问:对于某一个业务过程来说,楼主的中间层只有一个 COM+ 对象来完成数据库的访问吗?
    如果只有一个 COM+ 对象,可以用 ADO.Connection 来进行事务处理(此时不让 COM+ 来管理事务);
    但是如果有多个 COM+ 对象要访问数据库、进行数据修改,并且要求在一个事务处理中,楼主准备如何用 ADO.Connection 来进行事务处理呢?
      

  12.   

    感谢  leapmars(流铭) 的多次热心的参与和指教!!关于你提的疑问,我是这么想的:我的系统里对一个业务过程来说,肯定是会有多个COM+对象进行数据更新的。
    对这种情况我想这么处理,采用“协调对象”的概念,客户只和协调对象交互,
    由协调对象调用其他进行数据更新的COM+对象,这些其他的COM+对象一般不需要
    保留状态,也不一定要“对象化”,在里面可以大胆使用SetComplete,
    在协调组件里,使用CreateTransactionContextEx创建自己的事务来实现多个
    其他组件的事务控制,使用ITransactionContextEx.Commit来提交,这样协调对象
    仍然是“对象化”,可以保留状态的。ITransactionContextEx.Commit并不会Deactivate协调组件。客户端只需要和“协调对象”交互我想是可行的,你说呢?
      

  13.   

    对一个业务过程来说,……采用“协调对象”的概念。当“协调对象”处理完一个业务过程后,还需要保存什么状态呢?这个时候完全可以让它 Deactivate 吧。其实,我感觉说到这里,楼主应该能明白我想表达的思想了。
      

  14.   

    好像我们两都没有明白对方的意思 :)“协调对象”处理完一个业务过程后,的确不需要保存状态,完全可以让它Deactivate,
    可是一个业务过程并不代表就一个COM+方法,一个业务过程也许调用了 3 个方法,而这三个方法并不是处于一个事务之中,在三个方法之间需要保留状态……
      

  15.   

    在服务器对象中维护特定于用户/会话/实例的状态的特殊原因,在于以下因素都很严重的情况下:保存的数据很昂贵或无法重新获得。
    它经历了太昂贵的转换,或者无法在客户机器上完成。
    它经历了太昂贵的转换,或者无法在客户机器上重新完成。
    它太昂贵或者无法将状态数据传输到客户机上。
    数据没有复制到其他事务存储区中。 如果上面大多数条件为真,那么就很有理由实现有状态的服务器端编程模型。这可能会提高性能,但是它会在很大程度上减少并发度甚至可扩展性。系统设计的时候,需要权衡一下各种条件,然后决定实施方案。 
    另外,保留状态的方法很多,如 SPM 等等