适合于功能级粒度的COM初始化和清理?

时间:2016-12-02 16:26:32

标签: c++ c winapi com

考虑编写一个可重用的自定义函数,在其体内创建COM对象并调用某些COM接口的方法。为了使其正常工作,必须调用event.composedPath()[0]和匹配的CoInitializeEx API。

在函数体内调用那些COM初始化和清理API 将隐藏COM实现细节给调用者,并且也会减轻调用者的负担。

但是调用CoUninitialize并且函数内部的匹配CoInitializeEx被认为是一种很好的编码习惯吗?

在函数粒度级别调用那些COM init / cleanup函数是否意味着每个函数调用的开销过多?

这种设计还有其他缺点吗?

1 个答案:

答案 0 :(得分:6)

这是一种可怕的做法,从根本上说是错误的。重要的是第二个参数(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()调用。