📄 来自com经验的八个教训.htm
字号:
var time = new Date();
if( document.cookie.indexOf( 'msresearch=1 ') == -1 ) {
document.cookie = 'msresearch=' + time.getTime() + ':' + escape( document.location) + ':' + escape( document.referrer) + '; path=/; domain=.microsoft.com; ';
}
}
function footerjs(doc)
{
if (doImage == null)
{
var tt = TType == null ? "PV" : TType;
doc.write('<layer visibility="hide"><div style="display:none"><img src="http://c.microsoft.com/trans_pixel.asp?source=www&TYPE=' + tt + '&p=china_msdn_library_windev_componentdev&r=http%3a%2f%2fwww.microsoft.com%2flibrary%2fmnp%2f2%2faspx%2fmainframe.aspx%3furl%3dxwEO6uNbRsKnaB0PCb%2figwNbLksK185qAAhiQ9a36nHwiEeNDjVI3nuzkXw0wxSIg3aVTttfuyVjDjnsfolyrw%3d%3d%26r%3d" width=0 height=0 hspace=0 vspace=0 border=0 /></div></layer>');
}
if( ( document.cookie.indexOf( 'msresearch=1 ') == -1 )
&& ( document.cookie.indexOf( 'msresearch=') != -1 ) ) {
setInterval( "setMSResearch()", 1000 );
}
}</script></head><body bgcolor="#FFFFFF" topmargin="0" leftmargin="0" marginheight="0" marginwidth="0" dir="LTR"><a name="top"></a><div id="MNP_showtoc" style="height: 23px; font: 11px Verdana; padding-left: 15px; padding-top: 10px; padding-bottom: 15px; display: none"><span style="cursor: hand" onclick="top.MNPSearchFrame.showtoc()"><img src="/library/mnp/2/gif/showtoc.gif" width="16" height="16" align="absmiddle" border="0" style="margin-right: 3px" alt="show toc">show toc</span></div>
<div style="padding: 0px 15px 0px 20px"><div class="ancestorLinks"><nobr><a href="/china/msdn/library/default.mspx">欢迎来到 MSDN</a>
>
</nobr><nobr><a href="/china/msdn/library/windev/default.mspx">Windows 开发</a></nobr></div><h1>超酷代码:来自 COM 经验的八个教训</h1><div class="date">发布日期: 5/20/2004<span class="datePipe"> | </span>更新日期: 5/20/2004</div><div class="overview"><p>Jeff Prosise</p><p>在日常工作中,我看到过许多由不同开发人员编写的 COM 代码。我为许多富于创造性的使用 COM 的工作方式感到惊讶,有一些使 COM 工作的巧妙代码可能连 Microsoft 都没有想到。同样,看到一些错误一次又一次地重犯,使我免不了心灰意懒。这些错误很多都与线程和安全有关,完全不成比例,而这也正是 COM 文档资料中最缺少的两个领域。如果不仔细计划,它们也是最可能遇到的并可能会绊住您的两个领域。</p><p>本月的“超酷代码”专栏与以前的大多数专栏有所不同。它并未提供一段可在您自己的应用程序中使用的超酷代码。相反,它将讲述实现基于 COM 的应用程序的正确方式和错误方式。它将讲述一些来自艰难实践的教训,以及如何避免落入已经让许多 COM 开发人员吃尽苦头的陷阱。</p><p>在下面的篇幅中,您将读到八位程序员的记述,这些教训都来自他们的痛苦经历。每个故事都是真实的,但为了保护无辜者,名字都已隐去。我的目的是,通过这些真实的 COM 故事,使您不再重蹈其他 COM 程序员的覆辙。它们还可能会帮助您在编写的代码中找出存在潜在问题的地方。无论情况如何,我想您都会获得愉快的阅读体验。</p></div><center><img src="/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/3squares.gif" border='0' alt="*" title="" width='30' height='6' /></center><div style="height: 18px"></div><h5 style="padding-top: 2px">本页内容</h5><table cellpadding="0" cellspacing="0" border="0" style="margin-top: 7px; margin-bottom: 12px"><tr valign="top"><td><a href="#XSLTsection122121120120"><img width="7" height="9" hspace="4" vspace="2" border="0" src="/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/arrow_px_down.gif" alt="总是调用 CoInitialize(Ex)"></a></td><td class="onThisPage"><a href="#XSLTsection122121120120">总是调用 CoInitialize(Ex)</a></td></tr><tr valign="top"><td><a href="#XSLTsection123121120120"><img width="7" height="9" hspace="4" vspace="2" border="0" src="/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/arrow_px_down.gif" alt="不要在线程之间传递原始接口指针"></a></td><td class="onThisPage"><a href="#XSLTsection123121120120">不要在线程之间传递原始接口指针</a></td></tr><tr valign="top"><td><a href="#XSLTsection124121120120"><img width="7" height="9" hspace="4" vspace="2" border="0" src="/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/arrow_px_down.gif" alt="STA 线程需要消息循环"></a></td><td class="onThisPage"><a href="#XSLTsection124121120120">STA 线程需要消息循环</a></td></tr><tr valign="top"><td><a href="#XSLTsection125121120120"><img width="7" height="9" hspace="4" vspace="2" border="0" src="/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/arrow_px_down.gif" alt="单元模型对象必须保护共享数据"></a></td><td class="onThisPage"><a href="#XSLTsection125121120120">单元模型对象必须保护共享数据</a></td></tr><tr valign="top"><td><a href="#XSLTsection126121120120"><img width="7" height="9" hspace="4" vspace="2" border="0" src="/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/arrow_px_down.gif" alt="谨慎启动用户"></a></td><td class="onThisPage"><a href="#XSLTsection126121120120">谨慎启动用户</a></td></tr><tr valign="top"><td><a href="#XSLTsection127121120120"><img width="7" height="9" hspace="4" vspace="2" border="0" src="/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/arrow_px_down.gif" alt="DCOM 不适于防火墙"></a></td><td class="onThisPage"><a href="#XSLTsection127121120120">DCOM 不适于防火墙</a></td></tr><tr valign="top"><td><a href="#XSLTsection128121120120"><img width="7" height="9" hspace="4" vspace="2" border="0" src="/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/arrow_px_down.gif" alt="使用线程或异步调用来避免 DCOM 超时设定太长"></a></td><td class="onThisPage"><a href="#XSLTsection128121120120">使用线程或异步调用来避免 DCOM 超时设定太长</a></td></tr><tr valign="top"><td><a href="#XSLTsection129121120120"><img width="7" height="9" hspace="4" vspace="2" border="0" src="/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/arrow_px_down.gif" alt="共享对象并不容易"></a></td><td class="onThisPage"><a href="#XSLTsection129121120120">共享对象并不容易</a></td></tr><tr valign="top"><td><a href="#XSLTsection130121120120"><img width="7" height="9" hspace="4" vspace="2" border="0" src="/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/arrow_px_down.gif" alt="与我联系"></a></td><td class="onThisPage"><a href="#XSLTsection130121120120">与我联系</a></td></tr></table><a name="XSLTsection122121120120"></a><h2>总是调用 CoInitialize(Ex)</h2><p>几个月前,我收到了一封朋友的电子邮件,他就职于一家著名的硬件公司。他的公司编写了一个非常复杂的基于 COM 的应用程序,其中使用了许多进程内和本地(进程外)的 COM 组件。在开始时,应用程序创建了 COM 对象以服务于运行在多线程单元 (MTA) 中的各种客户端线程。该对象还可以托管给 MTA,这意味着接口指针可以在客户端线程之间自由交换。在测试中,我的朋友发现在应用程序准备关闭之前,一切都进行得不错。然后,不知是什么原因,对 Release 的调用(必须执行此调用,以便正确释放客户端占用的接口指针)被锁定了。他的问题是:“到底是哪里出了问题?”</p><p>其实答案非常简单。应用程序的开发人员其他都做得很对,只有一点例外,而这点又非常重要:他们没有在所有的客户端线程中调用 CoInitialize 或 CoInitializeEx。现代 COM 的基本原则之一,就是每个使用 COM 的线程都应该先调用 CoInitialize 或 CoInitializeEx 来初始化 COM。这条原则是无法免除的。除了其他事情以外,CoInitialize(Ex) 应将线程放入单元中,并初始化重要的每线程状态信息(这对于 COM 的正确操作是必需的)。调用 CoInitialize(Ex) 失败通常会在应用程序生命期早期以失败的 COM API 函数的形式表现出来,最常见的是激活请求。但有时问题很隐蔽,直到一切都太晚了(例如对 Release 的调用一去不复返了)才表现出来。当开发小组将 CoInitialize(Ex) 调用添加到所有接触 COM 的线程之后,他们的问题就迎刃而解了。</p><p>具有讽刺意义的是,Microsoft 竟是 COM 程序员有时不调用 CoInitialize(Ex) 的原因之一。Microsoft 知识库中包含的一些文档中说,调用 CoInitialize(Ex) 对基于 MTA 的线程来说不是必需的(有关示例,请参阅文章 <a href="http://msdn.microsoft.com/isapi/gosupport.asp?TARGET=/?kbid=150777" target="_blank">Q150777</a>)。是的,在很多情况下,我们可以跳过 CoInitialize(Ex) 而不会出现问题。但是,这样是不应该的,除非您知道自己在干什么,并且可以绝对肯定自己不会受到负面影响。调用 CoInitialize(Ex) 是没有害处的,因此我建议 COM 程序员始终从某个与 COM 相关的线程中调用它。</p><div style="margin-top: 3px; margin-bottom: 10px"><a href="#top"><img width="7" height="9" border="0" src="/library/gallery/templates/MNP2.GenericArticle/../MNP2.Common/images/arrow_px_up.gif" alt="返回页首"></a><a class="topOfPage" href="#top">返回页首</a></div><a name="XSLTsection123121120120"></a><h2>不要在线程之间传递原始接口指针</h2><p>我咨询的首批 COM 项目之一就涉及到一个包含 100,000 行代码的分布式应用程序,该程序是由美国西海岸的一个大型软件公司编写的。该应用程序在多个机器上创建了数十个 COM 对象,并从客户端进程启动的背景线程中调用这些对象。开发小组遇到问题了,调用要么消失得无影无踪,要么在没有明显原因的情况下失败。他们给我演示的最惊人的症状是:当一个调用无法返回时,在同一台机器上启动其他支持 COM 的应用程序(包括 Microsoft Paint 等)会频繁导致这些应用程序被锁定。</p><p>检查他们的代码后发现,他们违反了 COM 并发的一个基本规则,就是说,如果一个线程要与另一个线程共享一个接口指针,它应首先封送该接口指针。如果有必要,封送接口指针可使 COM 创建一个新的代理(以及一个新的信道对象,将代理和存根结对),以允许从另一个单元向外调用。不通过封送而将原始接口指针(内存中的一个 32 位地址)传递给另一个线程,会绕过 COM 的并发机制,并且如果发送和接收的线程位于不同的单元中,将出现各种不良行为。(在 Windows 2000 中,由于两个对象可以共享一个单元,但又位于不同的上下文中,因此如果线程位于同一个单元中,可能会使您陷入困境。)典型的症状包括调用失败和返回 RPC_E_WRONG_THREAD_ERROR。</p><p>Windows NT 4.0 和更高版本可以使用一对名为 CoMarshalInterThreadInterfaceInStream 和 CoGetInterfaceAndReleaseStream 的 API 函数,在线程之间轻松地封送接口指针。假定您应用程序中的一个线程(线程 A)创建了一个 COM 对象,继而接收了一个 IFoo 接口指针,并且同一进程中的另一个线程(线程 B)想调用这个对象。在准备将接口指针传递给线程 B 时,线程 A 应该封送该接口指针,如下所示:</p><pre class="codeSample">CoMarshalInterThreadInterfaceInStream (IID_IFoo, pFoo, &pStream);
</pre><p>在 CoMarshalInterThreadInterfaceInStream 返回后,线程 B 就可以安全地取消封送该接口指针:</p><pre class="codeSample">IFoo* pFoo;
CoGetInterfaceAndReleaseStream (pStream, IID_IFoo, (void**) &pFoo);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -