我设计了个简单试验来验证com 系统如何启动不同apartment类型的进程外com服务器来响应不同的client调用 我只针对进程外服务器 com 系统启动com组件响应client的请求的?


ms的对象中间件模型com包含了很多东西,已经是够复杂的了,ms又来概念抄作,多了一堆乱七八招的shit。如apartment等。


我现在对delphi中com wizard的apartment,instance有了一丁点的认识,大家看我理解得对不对。

com编程包含几个方面:com 系统(win2000),com组件(com server),client(组件使用者)。

apartment这个概念很抽象,描述了com server和client的线程相容性。 我设计了个简单试验来验证com 系统如何启动不同apartment类型的com服务器来响应不同的client调用
com 系统启动com组件响应client的请求的方式有以下几种:(只是我的理解,请指正)
1新建一个进程创建一个com server的实例。
2在现存进程中新建一个thread,在新建的thread中创建一个com server的实例。
3在现存com server实例中排队响应client的请求
4在现存com server实例中不排队响应client的请求
com 系统 到底"怎样启动com组件响应client的请求"是由很多因素决定的:
1com组件的instance(in proc,single,multi)和thread  model(single,apartment,free,both,neutral),3*4=12种组合
2 com组件类厂的实现,???vcl已经定义
3client的请求的方式。:单/多client,单/多线程,请求单/多个相同的comserver接口)6种组合
4com server的运行环境:com?dcom?mts?com+?
试验方法

(我得beepcnt的例子是从atl的例子改的,atl的例子是在《大本营2000》中微软版的文件atlabc.chm中)
我想建立一个进程外com server,有个接口只有只有一个方法beepcnt,
beepcnt(vcnt: Integer);
client 调用接口beepcnt,com server就发出声音,次数由参数vcnt指定。
这个接口只有一个方法beepcnt,方法beepcnt中没有用全局变量,因此是thread safe的。(?)



new 一个app,存为“d6vexemod”。new一个automation object,coclassname取为“d6beep4cnt”,
instance中选为“multi instance”。thread model先选为“apartment”。
(instance指定com 系统 可以"怎样启动com组件响应client的请求"。
thread model指明com server的线程安全性。) 接口中新建方法,beepcnt
方法beepcnt的代码,叫一声,停一下
    for i:=1 to vcnt do
        begin        
            messagebeep(0);             
            sleep(1000);//挂起时间1秒
        end;
我重载 TAutoObject的Initialize与 Destroy方法,把com server被实例化的次数显示在app的主窗体上。
(可能有问题,最好在类厂创建com server时下钩子,但 TAutoObjectFactory没有createinstance方法,只有
TComponentFactory 才有createinstance方法,不解)  Td6beep4cnt = class(TAutoObject, Id6beep4cnt)
  public
    procedure Initialize; override;
    destructor Destroy; override;
  protected
    procedure beepcnt(vcnt: Integer); safecall;
    { Protected declarations }
  end;
destructor Td6beep4cnt.Destroy;
begin
  FCriticalSection.Enter;//进入临界区访问公用vcl资源,有必要吗?
  form1.Label2.Caption := inttostr(strtoint(form1.Label2.Caption) - 1);
  FCriticalSection.Leave;
  inherited;
end;procedure Td6beep4cnt.Initialize;
begin
  inherited;
  FCriticalSection.Enter;
  form1.Label2.Caption := inttostr(strtoint(form1.Label2.Caption) + 1);
  FCriticalSection.Leave;end;
com server建立完毕,编译,运行一遍来注册。建立一个client app,import library,加一个button
声明变量mycom: Id6beep4cnt;
按钮按下时
procedure TForm3.Button1Click(Sender: TObject);
begin
  CoInitializeEx(nil, 0);//进入mta
  mycom := Cod6beep4cnt.Create();
  mycom.beepcnt(3);
  mycom := nil;
  CoUninitialize;
end;
同时运行两个client,快速按下两个按钮,com  server的窗体上计数值为1,花了6秒响6声,
结论1:delphi6中
com server  instance中选为“multi instance”。thread model先选为“apartment“
client有且只有一个线程请求一个 接口,
com server 只被实例化一次,排队响应所有请求。
再在client的窗体上加入一个button,单击事件代码:CoInitializeEx(nil, 0);//进入mta
   mycom3 := Cod6beep4cnt.Create;//请求一个接口
  for i := 1 to 10 do
  begin
    d6th4t.Create(mycom3, false);//mta中的10个线程共用一个接口,共叫30次,创建就执行
    sleep(100);//线程与线程间相隔0.1秒  end;
//d6th4t是Thread 的子类
 d6th4t = class(TThread)
  private
    { Private declarations }
     mp:   Id6beep4cnt;
  protected
    procedure Execute; override;
    public
    constructor create(lmp: Id6beep4cnt; suspended: boolean);
  end;constructor d6th4t.create(lmp: Id6beep4cnt; suspended: boolean);
begin
        mp:=lmp  ;//保存接口到私有变量。
inherited create(suspended);
end;procedure d6th4t.Execute;
begin
  CoInitializeEx(nil, 0);
  mp.beepcnt(3);//响3次
  CoUninitialize;
end;运行一个client,按下第二个按钮,com  server的窗体上计数值为1,花了30秒响30声,
结论2
com server  instance中选为“multi instance”。thread model先选为“apartment“
client的mta中有10个线程使用一个接口,
com server 只被实例化一次,排队响应所有请求。
把com server的实现单元的initialization小节些改一下
initialization
  TAutoObjectFactory.Create(ComServer, Td6beep4cnt, Class_d6beep4cnt,
 //  ciMultiInstance, tmApartment);
   ciMultiInstance, tmFree);
   
目的是把thread model改为“free“,(vcl很方便)编译运行server,
同时运行两个client,快速按下两个按钮,com  server的窗体上计数值为2,花了3秒多响6声,
结论3:delphi6中
com server  instance中选为“multi instance”。thread model先选为“free“
client有且只有一个线程请求一个 接口,
com server被实例化的次数和client的个数相同 ,各个com server响应各自的请求,不相互堵塞。
运行一个client,按下第二个按钮,com  server的窗体上计数值为1,花了4秒响30声,(象机关炮)
结论4
com server  instance中选为“multi instance”。thread model先选为“apartment“
client的mta中有10个线程使用一个接口,
com server 只被实例化一次,不排队响应所有请求。(verry cool)运行二个client,快速按下两个client的第二个按钮,com  server的窗体上计数值为2,花了4秒响60声,
(象火神机关炮)
结论4
com server  instance中选为“multi instance”。thread model先选为“apartment“
client的mta中有10个线程使用一个接口,两个client
com server 只被实例化两次,各个com server响应各自的请求,不相互堵塞。不排队响应所有请求。
(very cool)
还可以把com server 实现单元的initialization节中 的TAutoObjectFactory换成TComponentFactory ,把
Td6beep4cnt = class(TAutoObject, Id6beep4cnt)换成Td6beep4cnt = class(Tcomponent, Id6beep4cnt)
去掉initialize方法(Tcomponent没有initialize,在TComponentFactory的creatinstance中下钩子监视实例化次数 )
同时运行两个client,快速按下两个按钮,花了3秒多响6声,
com server  instance中选为“multi instance”。thread model先选为“apartment“
client有且只有一个线程请求一个 接口,
com server被实例化的次数和client的个数相同 ,各个com server响应各自client的请求,不相互堵塞。我不清楚为什么borland对于TAutoObjectFactory和TComponentFactory 采用不同的实现?
“multi instance”和“apartmentthread model“的com server,表现却不一样???
有谁能告诉我吗??为了兼容吗????
难道ms没有对“multi instance”和“apartmentthread model“的com server的行为作规定吗?
com server到底是如何被实例化的??这个问题好复杂
涉及到com规范的制定者,application framework的提供者,com server的开发者,com组件的使用者作为com组件的使用者,只用
1进入套间
2请求接口
3在某个套间中使用接口(mashall?)
4释放接口
5退出套间但是com组件的开发者要考虑很多因素,线程,鲁棒性,。必须清楚com server的生死。如delphi的demo中的midas的pooler例子,一个com模块中定义了两种com objectclient 连接到MultiInstance&FreeThread的com组件Tpooler,(Tpooler是out of process server)Tpooler把client的请求委托给PoolManager管理的TpooledRDM组件.TpooledRDM组件是InternalInstance&ApartmentThread,进程内部使用,stateless。这个例子很不错,还差垃圾收集,涉及到线程同步,pooler,值得一看.也许按照照微软的一整套方法才能,理解com,但我只想把简单的事情弄清楚,不想听ms的故弄玄虚,概念抄作。
有谁知道ms没有对“multi instance”和“apartmentthread model“的com server的行为作了什么规定吗?我想知道对于这种类型的com server,TAutoObjectFactory和TComponentFactory 的实现那一个是”非法的“。

解决方案 »

  1.   

    佩服楼主的钻研精神,Delphi掩盖了很多东东,方便的让我不想知道细节,Delphi让我变懒啦:)
    楼主学COM是从VC开始的吧。
      

  2.   

    实践是检验真理的唯一标准,佩服楼主。不过TAutoObjectFactory和TComponentFactory各有用处,谈不上哪个是“非法的”,哪个是“合法的”
      

  3.   

    com的对象模型有点不爽
    对于mta的com server,com会为每个client建立一个object。我做不到一个mta object 服务多个mta的client。最多是一个mta的server 服务一个client中的mta apartment中的多个线程。
    不解。
      

  4.   

    佩服楼主。TO:eastphoenix(红苹果)(名可名,非常名),只同意你说的一半,人容易变懒:)
    VCL开放的源码可以让人对COM的理解更本质化一些,不同层次的抽象面对不同层次的使用
    者,VC这方面做得越差,但它依赖了操作系统对COM的强大支持,并且提供了相当多的帮助,
    但容易同操作系统的一些东西缠杂不清。这样下来,用VC写COM的程序员,更深入了解操作系统相关的组件细节;用DELPHI写COM的程序,可能较长于用COM分布式企业应用。
      

  5.   

    对于“multi instance”,COM 中没有这种标准的说法,这只是 Delphi 为了简化 COM 组件的开发而提出的。 所谓“multi instance”,其实说的是编写进程外服务器(EXE)的一种情形。在这种情况下,如果有多个客户需要服务,并不会开辟多个进程而是只有一个进程,在这个进程中会有多个线程,每个线程都会创建相应的 COM 对象,给相应的客户提供服务。 这也就是说,这个进程中有多个 STA,每个 STA 有一个套间线程,其上有 COM 对象为某一个特定客户提供服务。多个客户进行的操作彼此之间不需要排队,是并发执行。 当编写 MIDAS 三层结构时,我们常用这种架构编写应用程序服务器。  这种功能在 Delphi 中由 TComponentFactory 实现。当然在 VC 中也能方便的实现,ATL 中的 CComAutoThreadModule 可以实现类似功能,它可以提供一个线程池供客户使用。
      

  6.   

    to leapmars(流铭)
    com 中 能不能做到一个服务器进程的mta套间中的一个free object 服务多个client?
    也就是把client的请求的interface绑定到同一个object,(而不是绑定到新建的object)好像client只用考虑interface,不用管是哪个object,但我就是想搞清楚。好像调用coXXX.create就新建一个object
    怎样把interface绑定到已经存在的com server 的mta套间中已经建立的object上?程序员》2001年12期82页李维的“never ending story”上举了例子
    com plus 是a object per client,
    而ejb 是a object for all client
      

  7.   

    http://bdn.borland.com/article/0,1410,27860,00.html
    这里面用图示讲了一下几种线程模型。。很有些价值。
      

  8.   

    halfdream(哈欠)说的这篇文章讲的还可以,楼主可以看看
      

  9.   

    谢谢各位,
    http://bdn.borland.com/article/0,1410,27860,00.html
    的确是好文章