首页 > 应用开发 > IT综合 > 怪异的COM 线程模型
COM的本质是C/S,考虑线程安全的时候,也可以分别从Client和Server的角度考虑。从Client角度看,是如何安全的调用共享的Server;反之从Server角度看,是如何安全的对多线程的调用提供服务。以C++的角度看,Server就是一个C++类的实例,COM不过是很多机制帮助Client如何创建In-Process/Out Process的合适的C++实例。并在Out-Process的情况下,提供合适的Proxy/Stub来完成inter-process的调用。
[@more@]Proxy/Stub的实质是RPC/IPC,具体实现上则依赖于Windows 的MessageQ。之所以依赖于MSGQ,是因为MSGQ独特而龌龊的非标准化设计。在Linux/Unix系统上,当要等待多个外部事件,就是去select多个对应的fd。Windows也提供select函数的支持,但是Windows MSGQ并不是一个可以被select的fd可以代替的,至少微软公开的API不容许这样的使用。RPC的接口能提供一个被select的fd吗?不知道,应该不能。Windows的MessageQ,并不是一个被公开细节的Windows技术。对于拥有UI的thread,要么在等待MSGQ新消息,要么在处理消息,MSGQ成为驱动其运转的基础。外部异步的事件,一定要转换为一个Windows消息,并放在MSGQ上面才能被本线程相应。MFC异步Socket就是这样一个例子,所有的Socket事件实际上在另外一个线程里面被转换为一个Windows Message消息后被送到UI线程上(创建Socket的那个UI线程)。
对于拥有UI的thread,InterProcess的COM调用通信由RPC完成;Client或者Server与COM子系统之间则是由MSQ接口完成。Client调用InterProcess的Interface的函数,即调用Proxy对应的函数,Proxy转换调用为RPC调用,RPC到Server后,被转换为一个Windows Message,Server在MSGQ里面取消息,处理消息,并调用实际Interface的函数。返回的过程也大致相同,也即InterProcess用RPC/IPC来完成。发送方的请求(或者返回),在本地被转换为一个RPC,在对端RPC被转换为一个Windows消息,插入到对端的消息队列,当消息队列消息被处理时,请求就被执行或者返回完成。Apartment/STA/MTA概念的引入,是要用户在不了解MSGQ细节的情况下理解如何设计线程安全的COM Client和Server。
单个线程调用一个C++对象,当然是线程安全的(除非C++对象本来封装了多个线程)。这对应着一个Interface在仅仅在其被创建的线程上调用。
对于DLL.COM来说,多个线程同时调用一个C++对象,当然是要考虑线程的安全问题(除非C++对象是完全可重入的,无状态的)。要解决这样的问题,方法有二。1)STA,从Client角度,让多个线程的调用串行化是一个办法。简单来说,当非COM对象创建者线程需要调用这个对象的Interface时候,就给创建者线程发个消息,因为创建者线程上对消息处理是串行的,所以并行的调用被串行化。CoMarshalInterThreadInterfaceInStream/ CoGetInterfaceAndReleaseStream 这两个函数就是为这样的串行化做准备的。这两个函数实际上实际上要在两个线程之间创建基于RPC/IPC的Proxy/Stub关系。调用完成后,夸线程的Interface调用,实际上和跨Process的调用没有区别了。2)MTA,从Server的角度,如果不是可重入无状态的,那么需要对设计上使用同步机制,保证并发被调用的线程安全。
对于EXE.COM来说,有两种情况,即EXE.COM是否支持被并发的调用。如果支持,那么Client这边,怎么调用都没事。如果不支持,那么就在EXE.COM的消息队列里面串行化Client的并发调用,也就没事了。这个做法和DLL.COM在Client侧串行化调用是一致的。
总之一句话,要么串行化调用,要么支持被并发调用。
N年前,就看到这个话题,一直闹不明白,最近仔细看看,其实也并不复杂。感觉是微软,故意把事情搞复杂,让人不明白,其实是想为自己垃圾的MSGQ设计遮羞罢了。如果能够理解龌龊的MSGQ,也就见怪不怪了。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/21398729/viewspace-1020719/ ,如需转载,请注明出处,否则将追究法律责任。