考虑编写一个可重用的自定义函数,它在其体内创建COM对象并调用某些COM接口的方法.为了使其正常工作,必须调用CoInitializeEx匹配的CoUninitializeAPI.
调用这些COM初始化和清理API的内部函数的身体会躲在一个COM实现细节给调用者,并会删除从主叫方的负担也是如此.
但是调用CoInitializeEx和匹配的CoUninitialize内部函数的主体被认为是一个很好的编码实践吗?
在函数粒度级别调用那些COM初始化/清理函数是否意味着每个函数调用的开销过多?
这个设计还有其他缺点吗?
这是一种可怕的做法,从根本上说是错误的.重要的是第二个参数(dwCoInit)的值.它必须是COINIT_APARTMENTTHREADED,通常缩写为STA或COINIT_MULTITHREADED(MTA).这是你做出的承诺,跨越你的心 - 希望去死的风格.如果你违背承诺,那么程序将会死亡.通常是通过死锁,没有得到预期的事件或具有令人无法接受的缓慢性能.
当您选择STA时,您承诺该线程表现良好,并且可以支持非线程安全的COM组件.实现这一承诺需要线程泵送消息循环并且永远不会阻塞.例如,支持GUI的线程的常见行为.绝大多数COM组件都不是线程安全的.
当您选择MTA时,您根本不承诺任何支持.该组件现在必须自己保持自己的线程安全.通常通过让COM基础架构自己创建一个线程来自动完成,从而为组件提供安全的家.您需要注意的另一个细节是编组接口指针,需要CoMarshalInterThreadInterfaceInStream()辅助函数或更方便的IGlobalInterfaceTable接口.这可确保创建一个代理所需的线程上下文切换的代理.
MTA听起来很方便,但并非没有后果,简单的属性getter调用可以花费多达x10000的时间.线程上下文切换强加的开销以及跨堆栈帧复制任何参数和返回值的需要.并且编组接口指针很容易失败,COM组件的作者通常不提供必要的代理/存根,或者他们故意省略它,因为复制数据简直太困难或太昂贵.
关键是STA和MTA之间的选择永远不能由库进行.它不知道有关该线程的bean,它没有创建该线程.并且不可能知道线程是否泵送消息循环或阻塞.这就是完全脱离图书馆范围的所有代码.否则,COM基础结构也需要知道这一点的确切原因,它同样不能对线程做出假设.
必须通过创建和初始化线程的代码来进行选择,而不仅仅是应用程序本身.除非库创建一个线程以使呼叫安全.但随后代码的结果总是很慢.您通过返回不可避免的CO_E_NOTINITIALIZED错误代码来提醒您的库的调用者他没有做到正确.
Fwiw,这是你在.NET Framework中看到的.在线程可以执行任何托管代码之前,CLR始终调用CoInitializeEx().仍然是应用程序员,或者更典型的项目模板必须做出的选择,使用Main()上的[STAThread]属性或工作线程的Thread.SetApartmentState()调用.