科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件实战COM编程系列之三

实战COM编程系列之三

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

由于客户和和组件实现的接口IModule、IModuleSite等都包有界面的传递,出于利用MFC的界面包装功能而都使用MFC来实现。

作者:lop5712 来源:论坛 2007年10月20日

关键字: 实战 COM 编程

  • 评论
  • 分享微博
  • 分享邮件
本文为此系列文章的重点,前面设计的接口都只是辅助性质,与COM线程模型没有一点关系。由于客户和和组件实现的接口IModule、IModuleSite等都包有界面的传递,出于利用MFC的界面包装功能而都使用MFC来实现,故全部运行在STA套间中,并可使得组件的窗口亦使用客户端主线程来派送消息。

  假设调用远程组件的方法以实现业务逻辑,由于远程及大数据量操作的关系,决定对于每次界面发起的数据操作(如查找),均发起一个线程,然后在这个线程中调用远程方法。决定使用一个类包装任务,其实现ITask接口,以提供任务管理的服务,而线程函数Task就理所当然的是ITask的实现类CTask的静态成员函数。由于CTask是一个内部对象,不需要拥有CLSID,故样例中直接通过静态成员函数CreateInstance获得其实例。

  出于示范的目的,考虑如何表现进度,在此提出两种方式。

  一. 假设远程组件方法是同步的,则调用线程将由于远程组件方法的迟迟不返而被挂起,因此无法表示进度。对于此,可以开启一个计时器(Timer),每隔固定时间就由前面的CTask通知ITaskManager增加了一定的进度,并给个上限,超出后就不再计时,进而进度不再增加直到方法返回或ITaskManager终止;如果是异步的,则上面说的发起一个线程这个工作由COM运行时期库(以后简称COM)干了,在此也就没有意义了,并且其必须在Win2000及以后版本发行的COM上才有效,这也就是说客户端必须是在Win2000及后续版本的操作系统上运行,这不是一个好提议。

  二. 假定ITask由远程业务组件实现,而并不是上面说的一个内部对象仅为提供包装使用,则在远程业务组件的方法中每过一段代码,其调用ITaskNotify以设置进度,并在方法结束时调用ITaskNotify以结束任务。

  上面的第一种情况中的CTask没有存在于MTA套间中的必要——其只会被客户主线程调用(不管是ITaskManager的实现者还是部门组件的操作界面发起的任务,都是通过客户端主线程操作的)。因此其应该是一个Apartment组件,使用MFC实现。但由于本样例主要是演示套间间的访问调用,故在此依旧将其设计成Free组件(即使没这个必要),使用ATL实现,以演示如何跨套间调用。但由于其是通过静态成员函数CreateInstance直接创建的,并没有CLSID及相关注册表项以说明是Free组件。由于在客户主线程创建它,即客户主线程获得其直接指针,因此它是存在于客户主线程相关的STA套间内(即使以Free组件的要求编写)。

  而对于发起线程(既CTask::Task静态成员函数)的终止,如果将发起线程和MTA线程关联(即线程开始时COINIT_MULTITHREADED作为参数调用CoInitializeEx),而业务组件是一个Free组件,将存在于MTA中,则非常不幸地发起线程是直接调用业务组件的方法,即发起线程也是业务组件方法的执行者(假设业务组件是本地组件)。不幸地原因就是如果业务组件的方法中有一个死循环或运行时间很长的代码,且其又不提供任何终止的方法(比如短时间等待一个事件),则终止调用的唯一方法就是强行终止线程,进而不能正确调用业务组件的Release,并导致COM的一些效率低下(CoUninitialize没有调用,COM的某些资源未能及时释放)。如果通过代理调用(而不是直接指针),则可以通过ICancelMethodCalls取消掉未决的调用——即发起线程中的对业务组件的调用。这是COM提供的一个接口,以使得客户可以取消同步调用。这是双方面的工作,如果组件端一直占着线程资源不放也依旧和前面一样,必须强行终止,但这样至少提供了一种途径使得可以回复发起线程的运行,进而调用CoUninitialize退出。其坏处和前面的异步调用一样,必须是Win2000及其以上版本的操作系统所发布的COM。本样例使用后者,故发起线程使用CoInitialize进入STA而不是MTA以获得代理对象的指针而非直接指针进而得以取消调用(有兴趣可以自己试下,将CTask::Task中的CoInitialize换成CoInitializeEx以进入MTA,任务将终止失败)。但由于此线程中没有生成任何COM组件对象,即此STA套间内不包含任何对象,因此无需编写消息循环。

  第二种情况中,假设在每次发起的调用线程中都通过调用CoCreateInstance以获得业务组件的一个全新实例,然后再调用其上的方法,则实际上其只会被发起的调用线程这一个线程调用,没有存在于MTA套间中的必要。这是非常符合MTS提供的编程模型的编写方式,通过将业务组件注册成MTS组件,并实现对象池功能和开启即时激活特性(Just-In-Time Activation)则上面由于每次调用CoCreateInstance而导致的损耗几乎等于没有,但程序的结构却非常简单,不需要复杂的逻辑。

  不过此样例并没有考虑编写为MTS组件,并且上面的好处正是本文的坏处,无法演示多线程对业务组件的调用处理。故提供一个全局的业务组件对象,使其为Free组件,并在每次发起的调用线程中都通过ITaskNotify通知正确的线程任务的进度(实际只有一个线程会被通知——客户端主线程。这里只是说明如何使用正确的代理对象进行通知,但由于业务组件是在MTA套间中,且只有一个线程会被通知,因此并没有保留ITaskNotify*的中立形式的必要,即各调用线程的代理对象就意义上是一样的了,也许实现上会有细微差别,视COM运行时期库的实现。但在此作为演示还是使用中立形式保存ITaskNotify*)。

  而对于第二种情况线程的终止(参看下面的代码),我只是让业务逻辑就是WaitForSingleObject一个事件来及时地响应终止事件和模拟工作的耗时,实际中当然不可能这样。实际中的业务代码如果是一个循环,则可以通过每次循环时短时间等待一个事件(或调用ICancelMethodCalls的方法),没有事件则继续循环工作(这种情况下最好是监视一个全局变量的值而不是等待一个事件)。如果不是循环却又是很耗时的操作,且操作没有提供任何中断的接口,则只能强行终止了。

  本样例就上面两种情况,分别实现业务组件接口中的两个方法Task1和Task2,此业务组件为Free类型的进程内组件。由于代码较多,在此仅列出第二种情况中的业务组件代码和部门组件中的发起调用的代码。
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章