转载 COM+编程研究之对象池、JITA

你有没有在开发COM+组件?你明白COM与COM+的区别么?你有没有在使用COM+的对象池,你有没有碰到过不能远程调试的问题?或者当你在一个COM+组件内部缓存了另一个组件接口指针时,你有没有遇到一些奇怪的现象?
       我正在开发COM+组件,我以前常常遇到一些奇怪的现象(当我运用对象池和JITA时),我查阅了一些书籍,找到了原因,但是现有的书籍总是不能详细的指导编程。所以,我今天打算将自己的所学和所用作个总结。
 
1) 何谓COM?
COM全名为组件对象模型,其核心是将对象实现的细节封装起来,客户程序只能通过接口调用组件提供的方法,这是面向接口的编程,大大简化了面向对象中的编程负担,你不需要再去了解你使用的类是从多少父类层层嵌套过来的,你也不需要察看任何源代码。寥寥数语,不能讲清楚博大精深的COM本质,还是去看COM本质论吧。
2) 何谓COM+?
COM+是什么?是微软提出的一种创建分布式应用程序的解决方法。分布式应用程序的特点是巨大,非常的复杂。但是任何分布式应用程序都需要解决一些共性问题,比如:安全、并发、网络传输的可靠性等。但是这些共性问题都是需要耗费极大的人力、物力、财力,而且最关键的是,绝大多数的项目开发队伍是不具备这种专业素质的。如何能够将项目开发人员的负担减少到最轻,使他们只需要考虑与业务相关的编码?很多大公司进行了这些探索,微软的解决方案就是COM+。
3) COM和COM+的关系?
我们可以编写包含业务逻辑的中间件(以COM的DLL形式),然后将它安装到COM+管理器中,进行简单的设置,就可将它作为分布式系统的服务器程序。
4) COM+的对象池
为了应付成千上万的互联网用户(通过WEB方式请求服务的客户),COM+提供了对象池。对象池的意义在于一个组件实例被创建之后,就可以保存到对象池中,如果需要,就从池中激活使用,这样就避免了重复创建的开销。对象池也可以设置容量大小,超过了对象池能够承受的容量,客户的请求就会被拒绝,这样避免了无限制的创建对象导致服务器崩溃。
5) COM+的JITA
JITA全称为即时激活。通常Intranet中的客户一旦连接上服务端,无论调用还是不调用方法都会一直保持连接,这样很耗费资源。JITA将在调用时创建对象,提供服务,调用完后立刻摧毁对象。但是客户是感觉不到的,客户程序仍然认为和服务器的连接存在,甚至可以缓存接口指针。
JITA和对象池也可以合作。但是对象池和JITA的使用者必须清楚内部的激活、钝化机制,并不是简单的配置一下COM+管理器就可以了。下文将创建一个测试程序,分析对象池和JITA的工作流程。
 
1)创建FBTEST项目,选择支持COM+1.0,取消属性化
2)在添加类向导中,添加COM+1.0组件TEST
3)选择支持IObjectControl,其余可以不选,因为我们只讨论IObjectControl接口。
 
 
接口简介:
该接口由你来实现,由com+调用,也就是客户代码不应该调用该接口的任何方法。
当对象被激活时,com+将调用Activate方法;
当对象被钝化时,com+将调用Deactivate方法;
对象可以控制自己是否应被放入对象池中,可以在CanBePooled方法中返回TRUE或者FALSE。
 
 
是如何激活/钝化TEST对象的:
首先我们要创建一个客户程序TestClient,如何创建我这里就不介绍了,毕竟是太初级的东西。在组件实现类CTest类的构造函数等函数中添加如下代码:
       CTEST()
     {
         ofstream file("c:\\test.txt",std::ios::app);
         file<<"正在执行构造函数"<<endl;
     }
     HRESULT FinalConstruct()
     {
         ofstream file("c:\\test.txt",std::ios::app);
         file<<"正在执行FinalConstruct"<<endl;
         return S_OK;
     }
    
     void FinalRelease()
     {
         ofstream file("c:\\test.txt",std::ios::app);
         file<<"正在执行FinalRelease"<<endl;
     }
    
HRESULT CTEST::Activate()
{
          ofstream file("c:\\test.txt",std::ios::app);
          file<<"正在执行Activate"<<endl;
          HRESULT hr = GetObjectContext(&m_spObjectContext);
          if (SUCCEEDED(hr))
              return S_OK;
          return hr;
}
 
BOOL CTEST::CanBePooled()
{
          ofstream file("c:\\test.txt",std::ios::app);
          file<<"正在执行CanBePooled"<<endl;
          return FALSE;
}
 
void CTEST::Deactivate()
{
          ofstream file("c:\\test.txt",std::ios::app);
          file<<"正在执行Deactivate"<<endl;
          m_spObjectContext.Release();
}
      
com组件方式:
现在我们运行客户程序,创建并销毁组件,注意,这里我们还没有使用COM+管理TEST组件。结果如下:
正在执行构造函数
正在执行FinalConstruct
正在执行FinalRelease
 
表明IObjectControl接口中的方法还没有被调用。
 
对象池调用方式:
我在WIN XP的组件服务管理器上创建服务器应用程序TEST,并将FBTEST组件加载进来,然后设置激活方式如下:
然后我们再运行客户程序。结果如下:
正在执行构造函数
正在执行FinalConstruct
正在执行Activate
正在执行Deactivate
正在执行CanBePooled
正在执行FinalRelease
 
可以看见IObjectControl接口的三个方法被com+依次调用,出人意料的是CanBePolled是在Deactivate方法之后调用的。如果CanBePooled函数返回S_OK,就不会执行FinalRelease函数。
调用方式:
如果我们取消对象池设置,只选择实时激活方式,我们观察到如下结果:
正在执行构造函数
正在执行FinalConstruct
正在执行FinalRelease
为什么COM+没有调用Activate方法?想想JITA机制是用于方法调用的,如果没有调用方法,当然使用JITA没有任何影响。现在我们给TEST加上调用方法F(void )的代码。再次调用之后,我们发现结果变了:
正在执行构造函数
正在执行FinalConstruct
正在执行Activate
正在执行方法F()
正在执行Deactivate
正在执行FinalRelease
 
这里有一件奇怪的事情,在COM+1.5中,无论怎么样,方法调用后都会调用Deactivate方法,尽管我们在方法F最后加了如下代码
IContextState* pContextState;
     ::CoGetObjectContext(IID_IContextState,(void**)&pContextState);
     pContextState->SetDeactivateOnReturn(FALSE);
pContextState->Release();
但是没有奏效,而且也不管COM+组件方法属性上是否选中了“此方法返回时自动停用此对象“选项。结果都是会调用Deactivate方法。我不知道COM+1.0是否如此,但是我已经不打算使用COM+1.0了。我们姑且认为JITA启用时一定会调用Deactivate方法。
 
JITA结合的调用方式
       现在我们同时使用这两种方式,再来观察结果:
正在执行构造函数
正在执行FinalConstruct
正在执行Activate
正在执行方法F()
正在执行Deactivate
正在执行CanBePooled
正在执行FinalRelease
这里多了调用CanBePooled,问是否要放入池中,如果返回TRUE,则对象不会被销毁,而是放入池中,FinalRelease方法就不会被执行,否则就执行FianlRelease方法销毁对象。
 
当同时时用了对象池和JITA之后,我们多次调用客户端,结果如下
正在执行构造函数
正在执行FinalConstruct
正在执行Activate
正在执行方法F()
正在执行Deactivate
正在执行CanBePooled
正在执行Activate
正在执行方法F()
正在执行Deactivate
正在执行CanBePooled
正在执行Activate
正在执行方法F()
正在执行Deactivate
正在执行CanBePooled
正在执行Activate
正在执行方法F()
正在执行Deactivate
正在执行CanBePooled
 
构造函数和FinalConstruct函数自从第一次被调用之后,再也没有被调用过,如果我们很大的初始化的开销,放在这两个函数中只做一次,将是非常好的选择。
1) 在创建COM+组件时----(这里的COM+组件,我指的是创建一个COM组件,该组件将安装在COM+管理器上作为分布式应用的中间件)我们应该实现IObjectControl接口,并且支持对象池(通过让CanBePooled返回TRUE)。
2) 为了适应最特殊的情况,我们应该将组件的激活方式设置为同时支持对象池和JITA两种情况。
3) 我们要将与特定客户相关的初始化动作放在Activate方法中,将清除动作放到Deactivate中。如果初始化的动作对于所有的客户调用一样,则可以放到构造函数中,这样对象从池中激活时就省去了初始化这些通用状态的步骤,提高了效率。
4) 如果对象内部有指向其他组件接口的指针成员变量,该成员变量要么在Activate中创建,要么在GIT中注册,在Activate中获取。
5) 所有上面的结论只是为了使初学者不易出错,一旦你发现你能够深入地了解对象池和JITA,你可以为了效率而定制你的代码和COM+配置,优点是获得了更高的效率,缺点是通用性不强。
 
6) 对象池不会销毁对象,所以对象的状态(即成员变量)将会被保存,下次从池中激活时仍然有效,唯独接口指针成员变量由于切换了上下文而不能正确散集,所以必须采用4)中所述方法。
7) 如果你在Activate方法中初始化,那么对象必须至少采用对象池和JITA中的一种激活方式,否则初始化不能正确执行。
posted @ 2010-06-29 10:44  sosopop  阅读(421)  评论(0编辑  收藏  举报