<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>COM</title><link>http://blog.vckbase.com/teacheryang/category/114.html</link><description>COM</description><managingEditor>杨老师的茅屋</managingEditor><dc:language>zh-chs</dc:language><generator>.Text Version 0.958.2004.214</generator><item><dc:creator>杨老师的茅屋</dc:creator><title>如何显示 ActiveX 控件的属性窗</title><link>http://blog.vckbase.com/teacheryang/archive/2007/11/18/30719.html</link><pubDate>Sun, 18 Nov 2007 13:24:00 GMT</pubDate><guid>http://blog.vckbase.com/teacheryang/archive/2007/11/18/30719.html</guid><wfw:comment>http://blog.vckbase.com/teacheryang/comments/30719.html</wfw:comment><comments>http://blog.vckbase.com/teacheryang/archive/2007/11/18/30719.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://blog.vckbase.com/teacheryang/comments/commentRss/30719.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/teacheryang/services/trackbacks/30719.html</trackback:ping><description>&lt;DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid"&gt;
&lt;DIV&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&lt;SPAN style="COLOR: #000000"&gt;CComQIPtr&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;ISpecifyPropertyPages&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;spSpecify(&amp;nbsp;m_ax.GetControlUnknown()&amp;nbsp;);&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;if&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;(&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;!&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;spSpecify&amp;nbsp;)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;return&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;;&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;CAUUID&amp;nbsp;pages;&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;HRESULT&amp;nbsp;hResult&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;=&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;spSpecify&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;-&amp;gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;GetPages(&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;amp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;pages&amp;nbsp;);&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;if&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;(&amp;nbsp;FAILED(&amp;nbsp;hResult&amp;nbsp;)&amp;nbsp;)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;return&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;;&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;CLSID&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;*&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;pclsidPages&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;=&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;(CLSID&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;*&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;)_alloca(&amp;nbsp;pages.cElems&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;*&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;sizeof&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;(&amp;nbsp;CLSID&amp;nbsp;)&amp;nbsp;);&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;for&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;(&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;i&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;=&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;0&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;;&amp;nbsp;i&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;int&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;)pages.cElems;&amp;nbsp;i&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;++&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;)&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pclsidPages[i]&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;=&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;pages.pElems[i];&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;::CoTaskMemFree(&amp;nbsp;pages.pElems&amp;nbsp;);&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;IUnknown&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;*&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;pObject&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;=&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;m_ax.GetControlUnknown();&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;OleCreatePropertyFrame(&amp;nbsp;m_hWnd,&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;0&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;,&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;0&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;,&amp;nbsp;L&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;"&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;属性&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;"&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;,&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;1&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;,&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;amp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;pObject,&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pages.cElems,&amp;nbsp;pclsidPages,&lt;BR&gt;&lt;IMG src="/Images/OutliningIndicators/None.gif" align=top&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;GetUserDefaultLCID(),&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;0&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;,&amp;nbsp;NULL&amp;nbsp;);&lt;/SPAN&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;img src ="http://blog.vckbase.com/teacheryang/aggbug/30719.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>杨老师的茅屋</dc:creator><title>《COM 组件设计与应用 十六 连接点 for vc.net》(原文发表在vckbase)</title><link>http://blog.vckbase.com/teacheryang/archive/2005/09/21/12225.html</link><pubDate>Tue, 20 Sep 2005 17:14:00 GMT</pubDate><guid>http://blog.vckbase.com/teacheryang/archive/2005/09/21/12225.html</guid><wfw:comment>http://blog.vckbase.com/teacheryang/comments/12225.html</wfw:comment><comments>http://blog.vckbase.com/teacheryang/archive/2005/09/21/12225.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://blog.vckbase.com/teacheryang/comments/commentRss/12225.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/teacheryang/services/trackbacks/12225.html</trackback:ping><description>&lt;P align=center&gt;&lt;B&gt;COM组件设计与应用（十六）&lt;BR&gt;连接点(vc.net)&lt;BR&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;&lt;A href="http://www.vckbase.com/code/downcode.asp?id=2776"&gt;下载源代码&lt;/A&gt;&lt;B&gt;&lt;BR&gt;&lt;BR&gt;一、前言&lt;BR&gt;&lt;/B&gt;&lt;BR&gt;　　&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1526"&gt;上回书&lt;/A&gt;介绍了回调接口，在此基础上，我们理解连接点就容易多了。&lt;BR&gt;&lt;B&gt;&lt;BR&gt;二、原理&lt;BR&gt;&lt;BR&gt;&lt;IMG height=272 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut16pic01.jpg" width=426 border=0&gt;&lt;BR&gt;&lt;/B&gt;图一、连接点组件原理图。左侧为客户端，右侧为服务端（组件对象）&lt;BR&gt;&lt;BR&gt;　　看着好复杂呀......呵呵，其实简单的紧：（注1）&lt;BR&gt;1、一个 COM 组件，允许有多个连接点对象(IConnectionPoint)。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 也就是说可以有多个发生&amp;#8220;事件&amp;#8221;的源头。上图就有3个连接点；&lt;BR&gt;2、管理这些连接点的接口叫&amp;#8220;连接点容器&amp;#8221;(IConnectionPointContainer)。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 连接点容器接口特别简单，因为只有2个函数，一个是 FindConnectionPoint()，表示查找你想要的连接点；另一个是 EnumConnectionPoints()，表示列出所有的连接点，然后你去选择使用哪个。在实际的应用中，查找法使用最多，占90%，而枚举法使用只占 10%，一般在支持第三方的插件(Plug in)时才使用。（你想写个 IE 的插件吗？我们后面就要讲到啦）&lt;BR&gt;3、每一个连接点，可以被多个客户端的接收器(Sink)连接；&lt;BR&gt;&amp;nbsp;&amp;nbsp; 这个我们已经熟悉啦，还记得我们在&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1526" target=_blank&gt;上回书&lt;/A&gt;中为了管理多个回调接口，使用了 cookie 的方式进行区别吗？！&lt;BR&gt;&lt;BR&gt;&lt;B&gt;三、实现组件（一）&lt;BR&gt;&lt;/B&gt;&lt;BR&gt;1、建立一个空白解决方案。&lt;BR&gt;2、在解决方案中，新增 ATL 项目。示例程序中项目名称叫 Simple16， 注意不要选择&amp;#8220;属性化编程&amp;#8221;方式。&lt;BR&gt;3、添加 ATL 类。选择 &amp;#8220;ATL 的简单对象&amp;#8221;。&lt;BR&gt;4、名称卡片中，输入组件名称。示例程序中是 DispConnect。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut16pic02.jpg" width=615 border=0&gt;&lt;BR&gt;&lt;BR&gt;5、选项卡片中，接口类型选双接口。注意一定要选择&amp;#8220;连接点&amp;#8221;。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut16pic03.jpg" width=615 border=0&gt;&lt;BR&gt;&lt;BR&gt;6、增加接口函数。和上回书的程序一样，增加一个方法计算整数加法， 而通过连接点返回计算结果。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=369 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut16pic04.jpg" width=419 border=0&gt;&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut16pic05.jpg" width=615 border=0&gt;&lt;BR&gt;&lt;BR&gt;7、下面该增加&amp;#8220;事件&amp;#8221;函数了。选择事件接口(_IDispConnectEvents)，添加函数。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=426 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut16pic06.jpg" width=469 border=0&gt;&lt;BR&gt;&lt;BR&gt;8、该函数用来返回 Add() 函数的计算结果。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut16pic07.jpg" width=615 border=0&gt;&lt;BR&gt;&lt;BR&gt;9、生成事件代理类程序代码。选择组件类对象(CDispConnect)，执行鼠标右键菜单&amp;#8220;添加连接点&amp;#8221;&lt;BR&gt;&lt;BR&gt;&lt;IMG height=318 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut16pic08.jpg" width=428 border=0&gt;&lt;BR&gt;&lt;BR&gt;10、选择你要让 IDE 帮你生成哪个连接点的代理程序代码。我们这个组件只有一个连接点，那只好选择它了。 （在示例二的程序中，我们实现了两个连接点，那么你就要选择两个接口啦）&lt;BR&gt;&lt;BR&gt;&lt;IMG height=451 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut16pic09.jpg" width=616 border=0&gt;&lt;BR&gt;&lt;BR&gt;11、到此，VC 的 IDE 终于帮咱们完成了所有的框架，下面该咱们自己写真正的任务代码啦。&lt;/P&gt;&lt;PRE&gt;STDMETHODIMP CDispConnect::Add(long n1, long n2)
{
	long nVal = n1 + n2;
	Fire_Result( nVal );	// 调用IDE帮我们生成的代理函数代码，发出事件

	return S_OK;
}&lt;/PRE&gt;&lt;B&gt;四、实现调用者（一）&lt;/B&gt;&lt;BR&gt;&lt;BR&gt;1、建立一个 MFC 项目。示例程序中的名称叫 Use。&lt;BR&gt;2、按照咱们以前所学的知识，添加 #import、AfxOleInit()、......不多浪费口条了。如果你还不会，那么请重新从&amp;#8220;&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1493"&gt;第四回&lt;/A&gt;&amp;#8221;再次阅读。 （注2）&lt;BR&gt;3、这里只介绍一下重点部分。我们需要在调用者工程中，增加&amp;#8220;接收器&amp;#8221;对象。还记得&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1526"&gt;上回书&lt;/A&gt;中的增加&amp;#8220;回调接收器&amp;#8221;对象的方法吗？上回中，我们的回调接口是从 IUnknown 继承下来的。本回中，由于我们的组件是双接口(Dual)的，连接点也是双接口的，因此这次我们的接收器要从 IDispatch 派生啦。&lt;BR&gt;&lt;IMG height=418 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut16pic10.jpg" width=531 border=0&gt;&lt;BR&gt;&lt;BR&gt;&lt;IMG height=226 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut16pic11.jpg" width=617 border=0&gt;&lt;BR&gt;&lt;BR&gt;4、完成 CSink 类的接口函数（虚函数）&lt;PRE&gt;STDMETHODIMP CSink::QueryInterface(const struct _GUID &amp;amp;iid,void ** ppv)
{
	*ppv=this;
	return S_OK;
}

ULONG __stdcall CSink::AddRef(void)
{	return 1;	}	// 做个假的就可以，因为反正这个对象在程序结束前是不会退出的

ULONG __stdcall CSink::Release(void)
{	return 0;	}	// 做个假的就可以，因为反正这个对象在程序结束前是不会退出的

STDMETHODIMP CSink::GetTypeInfoCount(unsigned int *)
{	return E_NOTIMPL;	}	// 不用实现，反正也不用

STDMETHODIMP CSink::GetTypeInfo(unsigned int,unsigned long,struct ITypeInfo ** )
{	return E_NOTIMPL;	}	// 不用实现，反正也不用

STDMETHODIMP CSink::GetIDsOfNames(&lt;FONT color=#0000ff size=2&gt;const&lt;/FONT&gt;&lt;FONT size=2&gt; IID &amp;amp;,LPOLESTR *,UINT,LCID,DISPID *&lt;/FONT&gt;)
{	return E_NOTIMPL;	}	// 不用实现，反正也不用

STDMETHODIMP CSink::Invoke(
	long dispID,
	const struct _GUID &amp;amp;,
	unsigned long,
	unsigned short,
	struct tagDISPPARAMS * pParams,
	struct tagVARIANT *,
	struct tagEXCEPINFO *,
	unsigned int *)
{		// 只需要实现这个就足够啦
	switch(dispID)	// 根据不同的dispID,完成不同的回调函数
	{
	case 1:
		......	// 这里就能接收到 COM 发出的事件啦
		break;
	case 2:
		......	// 事件的代号 dispID 其实就是 IDL 文件中的连接点函数的id(n)的号码
		break;
	default:	break;
	}
	return S_OK;
}&lt;/PRE&gt;&lt;B&gt;五&lt;/B&gt;&lt;B&gt;、示例（二）&lt;BR&gt;&lt;BR&gt;&lt;/B&gt;　　示例程序中的第2个组件(MultConnect)，我们再增加一个连接点( _IDispConnectEvents2 )。这个接口对象负责完成一个时钟，每间隔一定的豪秒就向调用者发出&amp;#8220;时钟事件&amp;#8221;。增加第二个连接点的方法是要手工修改 IDL 文件&lt;PRE&gt;......
library MultConnectLib
{
	importlib("stdole2.tlb");
	...... // 第一个连接点。是 ATL 帮我们生成的
	&lt;B&gt;[   // 第2个连接点，需要我们手工添加
		uuid(E3330AE1-2B1D-42E6-A8E0-A9CB0D1AC74C), // CLSID 可以用 GUIDGEN.EXE 产生
		helpstring("_IDispConnect事件接口")
	]
	dispinterface _IDispConnectEvents2
	{
		properties:
		methods:
	};&lt;/B&gt;
	[
		uuid(4B0FDB44-BAF2-4F25-A2B0-B5ECD5CD440E), // 这是示例程序的类型库ID，肯定和你产生是不同的
		helpstring("DispConnect Class")
	]
	coclass DispConnect
	{
		[default] interface IDispConnect;
		[default, source] dispinterface _IDispConnectEvents;
		&lt;B&gt;[source] dispinterface _IDispConnectEvents2; // 别忘了，这还有一行&lt;/B&gt;
	};
};&lt;/PRE&gt;　　好了，和前面的方式一样，增加接口函数、让IDE帮我们实现代理类代码、输入程序代码、修改框架代码中的BUG。在示例中，我们的事件函数叫 HRESULT Timer([in] VARIANT varData)，varData 中传递一个时间类型(VT_DATA）的信息（注3）。下面我们来看一下代理类代码中的错误：&lt;PRE&gt;HRESULT Fire_Timer( VARIANT  varDate)
{
　　HRESULT hr = S_OK;
　　T * pThis = static_cast&lt;T *&gt;(this);
　　int cConnections = m_vec.GetSize();

　　for (int iConnection = 0; iConnection &amp;lt; cConnections; iConnection++)
　　{
　　　　pThis-&amp;gt;Lock();
　　　　CComPtr&lt;IUNKNOWN&gt; punkConnection = m_vec.GetAt(iConnection);
　　　　pThis-&amp;gt;Unlock();

　　　　IDispatch * pConnection = static_cast&lt;IDISPATCH *&gt;(punkConnection.p);
　　　　if (pConnection)
　　　　{
　　　　　　CComVariant avarParams[1];
　　　　　　&lt;B&gt;// 原始为：avarParams[0] = varDate;	avarParams[0].vt = VT_VARIANT;
　　　　  // 但可惜这是错误的，因为 avarParams[0] = varDate; 就已经正确地完成了赋值
　　　　  // 再对 avarParams[0].vt 赋值，是引用方式才能这么操作的。
　　　　  avarParams[0] = varDate; // 这才是正确的操作&lt;/B&gt;
　　　　   CComVariant varResult;

　　　　   DISPPARAMS params = { avarParams, NULL, 1, 0 };
　　　　   hr = pConnection-&amp;gt;Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &amp;#182;ms, &amp;amp;varResult, NULL, NULL);
　　　　 }
　　}
　　return hr;
}&lt;/PRE&gt;　　在编写调用者客户端代码方面，如果你需要接收时钟事件，那么可以仿照示例一再从 IDispatch 派生一个时钟接收器。大家下载事例程序代码，里面有丰富的注释信息。&lt;BR&gt;&lt;BR&gt;&lt;B&gt;六、小结&lt;BR&gt;&lt;/B&gt;&lt;BR&gt;　　连接点，尤其是双接口的连接点，在远程（DCOM）环境上运行效率是比较低的。如果你只想完成简单的&amp;#8220;通知&amp;#8221;功能，那么前一回中的&amp;#8220;回调接口&amp;#8221;是一个明智的方案，并且可以运行在DCOM环境上。连接点方案当然也很重要，因为微软的许多应用程序(IE、Office......)都支持连接点，并且 ActiveX 只能通过连接点接口提供&amp;#8220;事件&amp;#8221;功能。所以，咱们还是都掌握为善吧。&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;善哉 、&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;善哉&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;......&lt;BR&gt;
&lt;HR&gt;
注1：金庸老先生的武侠小说里，总是用&amp;#8220;XX 紧&amp;#8221;来表示&amp;#8220;很 XX&amp;#8221;。我也学一学，嘿嘿。&lt;BR&gt;注2：如果看了好几遍，您老人家还不会的话，那只好......先别学了。5555&lt;BR&gt;注3：DATA 类型就是是8字节的double，它的整数部分表示从 1899年12月30日开始的总天数，小数部分表示当天的时间已经渡过了一天的多少分之一。这个时间类型，用VARIANT表示，就是VT_DATE类型，MFC 中用 COleDateTime 表示。示例程序中有对该类型的操作示范。&lt;BR&gt;&lt;img src ="http://blog.vckbase.com/teacheryang/aggbug/12225.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>杨老师的茅屋</dc:creator><title>《COM 组件设计与应用 十五 连接点》(原文发表与vckbase)</title><link>http://blog.vckbase.com/teacheryang/archive/2005/09/21/12224.html</link><pubDate>Tue, 20 Sep 2005 17:13:00 GMT</pubDate><guid>http://blog.vckbase.com/teacheryang/archive/2005/09/21/12224.html</guid><wfw:comment>http://blog.vckbase.com/teacheryang/comments/12224.html</wfw:comment><comments>http://blog.vckbase.com/teacheryang/archive/2005/09/21/12224.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://blog.vckbase.com/teacheryang/comments/commentRss/12224.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/teacheryang/services/trackbacks/12224.html</trackback:ping><description>&lt;P align=center&gt;&lt;B&gt;COM组件设计与应用（十五）&lt;BR&gt;连接点(vc6.0)&lt;BR&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;&lt;A href="http://www.vckbase.com/code/downcode.asp?id=2775"&gt;下载源代码&lt;/A&gt;&lt;BR&gt;&lt;BR&gt;&lt;B&gt;一、前言&lt;BR&gt;&lt;/B&gt;&lt;BR&gt;　　&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1525" target=_blank&gt;上回书&lt;/A&gt;介绍了回调接口，在此基础上，我们理解连接点就容易多了。&lt;BR&gt;&lt;B&gt;&lt;BR&gt;二、原理&lt;BR&gt;&lt;BR&gt;&lt;IMG height=272 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut15pic01.jpg" width=426 border=0&gt;&lt;BR&gt;&lt;/B&gt;图一、连接点组件原理图。左侧为客户端，右侧为服务端（组件对象）&lt;BR&gt;&lt;BR&gt;　　看着好复杂呀......呵呵，其实简单的紧：（注1）&lt;BR&gt;1、一个 COM 组件，允许有多个连接点对象(IConnectionPoint)。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 也就是说可以有多个发生&amp;#8220;事件&amp;#8221;的源头。上图就有3个连接点；&lt;BR&gt;2、管理这些连接点的接口叫&amp;#8220;连接点容器&amp;#8221;(IConnectionPointContainer)。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 连接点容器接口特别简单，因为只有2个函数，一个是 FindConnectionPoint()，表示查找你想要的连接点；另一个是 EnumConnectionPoints()，表示列出所有的连接点，然后你去选择使用哪个。在实际的应用中，查找法使用最多，占90%，而枚举法使用只占 10%，一般在支持第三方的插件(Plug in)时才使用。（你想写个 IE 的插件吗？我们后面就要讲到啦）&lt;BR&gt;3、每一个连接点，可以被多个客户端的接收器(Sink)连接；&lt;BR&gt;&amp;nbsp;&amp;nbsp; 这个我们已经熟悉啦，还记得我们在&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1525" target=_blank&gt;上回书&lt;/A&gt;中为了管理多个回调接口，使用了 cookie 的方式进行区别吗？！&lt;BR&gt;&lt;BR&gt;&lt;B&gt;三、实现组件（一）&lt;/B&gt;&lt;BR&gt;&lt;BR&gt;1、建立一个工作区(WorkSpace)&lt;BR&gt;2、在工作区中，建立一个 ATL 工程(Project)。示例程序中工程名称叫 Simple15，接受全部默认选项。&lt;BR&gt;3、ClassView 中，执行鼠标右键菜单命令 New Atl Object...，添加 ALT 类。&lt;BR&gt;4、左侧分类 Category 选择 Objects，右侧 Objects 选择 SimpleObject（其实就是默认项目）。&lt;BR&gt;5、名称 Name 卡片中，输入组件名称。示例程序中是 DispConnect。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=260 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut15pic02.jpg" width=421 border=0&gt;&lt;BR&gt;&lt;BR&gt;6、属性 Attributes 卡片中，接口类型选 Dual 双接口。注意一定要选择 Support Connection Points 来支持连接点。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=260 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut15pic03.jpg" width=421 border=0&gt;&lt;BR&gt;&lt;BR&gt;7、ClassView 中，选择接口（IDispConnect），鼠标右键菜单添加函数 Add Method...&lt;BR&gt;&lt;BR&gt;&lt;IMG height=244 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut15pic04.jpg" width=261 border=0&gt;&lt;BR&gt;&lt;BR&gt;8、增加函数。和上回书的程序一样，增加一个接口函数计算加法，但通过连接点接口返回计算结果。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=311 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut15pic05.jpg" width=486 border=0&gt;&lt;BR&gt;&lt;BR&gt;9、下面该增加&amp;#8220;事件&amp;#8221;函数了。选择事件接口(_IDispConnectEvents)，添加函数。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=213 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut15pic06.jpg" width=338 border=0&gt;&lt;BR&gt;&lt;BR&gt;10、该函数用来返回 Add() 函数的计算结果。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=311 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut15pic07.jpg" width=486 border=0&gt;&lt;BR&gt;&lt;BR&gt;11、切换到 FileView 卡片，编译IDL文件。当然你也可以直接编译全部工程。其实编译的目的是为了从IDL文件产生TLB文件，因为 VC 的 IDE 环境只有知道了 TLB 后，才能生成下面的&amp;#8220;事件代理类的程序代码&amp;#8221;。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=292 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut15pic08.jpg" width=321 border=0&gt;&lt;BR&gt;&lt;BR&gt;12、生成事件代理类程序代码。选择组件类对象(CDispConnect)，执行鼠标右键菜单&amp;#8220;实现连接点&amp;#8221;&lt;BR&gt;&lt;BR&gt;&lt;IMG height=288 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut15pic09.jpg" width=359 border=0&gt;&lt;BR&gt;&lt;BR&gt;13、选择你要让 IDE 帮你生成哪个连接点的代理程序代码。我们这个组件只有一个连接点，那只好选择它了。 （在示例二中，我们需要实现两个连接点，那个时候，你就要选择两个了）&lt;BR&gt;&lt;BR&gt;&lt;IMG height=337 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut15pic10.jpg" width=425 border=0&gt;&lt;BR&gt;&lt;BR&gt;14、到此，VC 的 IDE 终于帮咱们完成了所有的框架，下面该咱们自己写真正的任务代码啦。&lt;/P&gt;&lt;PRE&gt;STDMETHODIMP CDispConnect::Add(long n1, long n2)
{
	long nVal = n1 + n2;
	Fire_Result( nVal );	// 调用IDE帮我们生成的代理函数代码，发出事件

	return S_OK;
}&lt;/PRE&gt;15、修正 IDE 产生的代码中的错误。你不用死记硬背错误点，只要编译一下就会报出错误了。一般 VC6 帮我们生成的代码中，有2个地方可能会有BUG。一是打开头文件，找到连接点影射宏，修改如下：&lt;PRE&gt;BEGIN_CONNECTION_POINT_MAP(CDispConnect)
	&lt;B&gt;CONNECTION_POINT_ENTRY(DIID__IDispConnectEvents)&lt;/B&gt;	// 修改 IID_XXXX 为 DIID_XXXX
END_CONNECTION_POINT_MAP()&lt;/PRE&gt;　　这个错误简直可恨，既然我们使用的是双接口连接点，它生成的代码居然不会判断吗？另一个可能的错误可能发生在代理类中的 Fire_xxxx() 函数中。在示例程序中的 Fire_Result() 函数代码，大家自己去阅读，简单说就是循环地取得每个和自己连接对象（每个cookie表示的对象）的接口指针，（如果是自动化接口，则再取得 IDispatch 接口指针），然后调用事件函数。你不理解它现在没有太大的关系，不过在后面的示例二中，它给我们产生的代码是有错误的，我们需要进行修改。这是后话，待会儿再说。&lt;BR&gt;&lt;BR&gt;&lt;B&gt;四、实现调用者（一）&lt;/B&gt;&lt;BR&gt;&lt;BR&gt;1、建立一个 MFC 工程（Project）。示例程序中的工程名称叫 Use。&lt;BR&gt;2、按照咱们以前所学的知识，添加 #import、AfxOleInit()、......不多浪费口条了。如果你还不会，那么请重新从&amp;#8220;&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1493" target=_blank&gt;第四回&lt;/A&gt;&amp;#8221;再次阅读。 （注2）&lt;BR&gt;3、这里只介绍一下重点部分。我们需要在调用者工程中，增加&amp;#8220;接收器&amp;#8221;对象。还记得&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1525" target=_blank&gt;上回书&lt;/A&gt;中的增加&amp;#8220;回调接收器&amp;#8221;对象的方法吗？上回中，我们的回调接口是从 IUnknown 继承下来的。本回中，由于我们的组件是双接口(Dual)的，连接点也是双接口的，因此这次我们的接收器要从 IDispatch 派生啦。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=270 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut15pic11.jpg" width=537 border=0&gt;&lt;BR&gt;&lt;BR&gt;4、完成 CSink 类的接口函数（虚函数）&lt;PRE&gt;STDMETHODIMP CSink::QueryInterface(const struct _GUID &amp;amp;iid,void ** ppv)
{
	*ppv=this;
	return S_OK;
}

ULONG __stdcall CSink::AddRef(void)
{	return 1;	}	// 做个假的就可以，因为反正这个对象在程序结束前是不会退出的

ULONG __stdcall CSink::Release(void)
{	return 0;	}	// 做个假的就可以，因为反正这个对象在程序结束前是不会退出的

STDMETHODIMP CSink::GetTypeInfoCount(unsigned int *)
{	return E_NOTIMPL;	}	// 不用实现，反正也不用

STDMETHODIMP CSink::GetTypeInfo(unsigned int,unsigned long,struct ITypeInfo ** )
{	return E_NOTIMPL;	}	// 不用实现，反正也不用

STDMETHODIMP CSink::GetIDsOfNames(const struct _GUID &amp;amp;,unsigned short ** ,unsigned int,unsigned long,long *)
{	return E_NOTIMPL;	}	// 不用实现，反正也不用

STDMETHODIMP CSink::Invoke(
	long dispID,
	const struct _GUID &amp;amp;,
	unsigned long,
	unsigned short,
	struct tagDISPPARAMS * pParams,
	struct tagVARIANT *,
	struct tagEXCEPINFO *,
	unsigned int *)
{		// 只需要实现这个就足够啦
	switch(dispID)	// 根据不同的dispID,完成不同的回调函数
	{
	case 1:
		......	// 这里就能接收到 COM 发出的事件啦
		break;
	case 2:
		......	// 事件的代号 dispID 其实就是 IDL 文件中的连接点函数的id(n)的号码
		break;
	default:	break;
	}
	return S_OK;
}&lt;/PRE&gt;&lt;B&gt;五&lt;/B&gt;&lt;B&gt;、示例（二）&lt;BR&gt;&lt;BR&gt;&lt;/B&gt;　　示例程序中的第2个组件(MultConnect)，我们再增加一个连接点( _IDispConnectEvents2 )。这个接口对象负责完成一个时钟，每间隔一定的毫秒就向调用者发出&amp;#8220;时钟事件&amp;#8221;。增加第二个连接点的方法是要手工修改 IDL 文件&lt;PRE&gt;......
library MULTCONNECTLib
{
	importlib("stdole32.tlb");
	importlib("stdole2.tlb");

	...... // 第一个，ATL 框架默认给我们生成的连接点接口描述
&lt;B&gt;	[ // 需要手工增加第二个或更多个连接点
		uuid(F81DB93F-4F63-4A55-8114-A32BC78466D3), // CLSID 可以用 GUIDGEN.EXE 来产生
		helpstring("_IDispConnectEvents2 Interface")
	]
	dispinterface _IDispConnectEvents2
	{
		properties:
		methods:
	};&lt;/B&gt;
	[
		uuid(9461BE82-0D64-4E3B-B0DB-2306D1BFE3F0), // 这是示例程序的类型库ID，肯定和你生成的不一样的啦
		helpstring("DispConnect Class")
	]
	coclass DispConnect
	{
		[default] interface IDispConnect;
		[default, source] dispinterface _IDispConnectEvents;
		&lt;B&gt;[source] dispinterface _IDispConnectEvents2; // 别忘了，这里还有一行呢&lt;/B&gt;
	};
};&lt;/PRE&gt;　　好了，和前面的方式一样，增加接口函数、编译IDL文件、让IDE帮我们实现代理类代码、输入程序代码、修改框架代码中的BUG。在示例中，我们的事件函数叫 HRESULT Timer([in] VARIANT varData)，varData 中传递一个时间类型(VT_DATA）的信息（注3）。下面我们来看一下代理类代码中的错误：&lt;PRE&gt;HRESULT Fire_Timer(VARIANT varDate)
{
   CComVariant varResult;
   T* pT = static_cast&lt;T*&gt;(this);
   int nConnectionIndex;
   CComVariant* pvars = new CComVariant[1];
   int nConnections = m_vec.GetSize();
		
   for (nConnectionIndex = 0; nConnectionIndex &amp;lt; nConnections; nConnectionIndex++)
   {
　　  &lt;T*&gt;pT-&amp;gt;Lock();
　　  CComPtr&lt;IUNKNOWN&gt; sp = m_vec.GetAt(nConnectionIndex);
　　  pT-&amp;gt;Unlock();
　　  IDispatch* pDispatch = reinterpret_cast&lt;IDISPATCH*&gt;(sp.p);
　　  if (pDispatch != NULL)
　　  {
　　　　 VariantClear(&amp;amp;varResult);
　　　　 &lt;B&gt;// 原始代码，这里居然是 pvars[0]=&amp;amp;varData？愚蠢之极！只好你自己修改啦
　　　　pvars[0] = varDate;&lt;/B&gt;
　　　　 DISPPARAMS disp = { pvars, NULL, 1, 0 };
　　　　 pDispatch-&amp;gt;Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &amp;amp;disp, &amp;amp;varResult, NULL, NULL);
　　  }
   }
   delete[] pvars;
   return varResult.scode;
}&lt;/PRE&gt;　　在编写调用者客户端代码方面，如果你需要接收时钟事件，那么可以仿照示例一再从 IDispatch 派生一个时钟接收器。大家下载事例程序代码，里面有丰富的注释信息。&lt;BR&gt;&lt;BR&gt;&lt;B&gt;六、小结&lt;/B&gt;&lt;BR&gt;&lt;BR&gt;　　连接点，尤其是双接口的连接点，在远程（DCOM）环境上运行效率是比较低的。如果你只想完成简单的&amp;#8220;通知&amp;#8221;功能，那么&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1525" target=_blank&gt;前一回&lt;/A&gt;中的&amp;#8220;回调接口&amp;#8221;是一个明智的方案，并且可以运行在DCOM环境上。连接点方案当然也很重要，因为微软的许多应用程序(IE、Office......)都支持连接点，并且 ActiveX 只能通过连接点接口提供&amp;#8220;事件&amp;#8221;功能。所以，咱们还是都掌握为善吧。&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;善哉 、&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;善哉&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;......&lt;BR&gt;
&lt;HR&gt;
注1：金庸老先生的武侠小说里，总是用&amp;#8220;XX 紧&amp;#8221;来表示&amp;#8220;很 XX&amp;#8221;。我也学一学，嘿嘿。&lt;BR&gt;注2：如果看了好几遍，您老人家还不会的话，那只好......先别学了。5555&lt;BR&gt;&lt;T *&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH *&gt;&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;&lt;T*&gt;&lt;IUNKNOWN&gt;&lt;IDISPATCH*&gt;注3：DATA 类型就是是8字节的double，它的整数部分表示从 1899年12月30日开始的总天数，小数部分表示当天的时间已经渡过了一天的多少分之一。这个时间类型，用VARIANT表示，就是VT_DATE类型，MFC 中用 COleDateTime 表示。示例程序中有对该类型的操作示范。&lt;BR&gt;&lt;img src ="http://blog.vckbase.com/teacheryang/aggbug/12224.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>杨老师的茅屋</dc:creator><title>《COM组件设计与应用（十四）---事件和通知(vc.net)》(原文发表在vckbase中)</title><link>http://blog.vckbase.com/teacheryang/archive/2005/09/01/11757.html</link><pubDate>Thu, 01 Sep 2005 12:44:00 GMT</pubDate><guid>http://blog.vckbase.com/teacheryang/archive/2005/09/01/11757.html</guid><wfw:comment>http://blog.vckbase.com/teacheryang/comments/11757.html</wfw:comment><comments>http://blog.vckbase.com/teacheryang/archive/2005/09/01/11757.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://blog.vckbase.com/teacheryang/comments/commentRss/11757.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/teacheryang/services/trackbacks/11757.html</trackback:ping><description>&lt;P align=center&gt;&lt;B&gt;COM组件设计与应用（十四）&lt;BR&gt;事件和通知(vc.net)&lt;BR&gt;&lt;/B&gt;&lt;BR&gt;&lt;A href="http://www.vckbase.com/code/downcode.asp?id=2753"&gt;下载源代码&lt;/A&gt;&lt;B&gt;&lt;BR&gt;&lt;BR&gt;一、前言&lt;/B&gt;&lt;BR&gt;　　我的 COM 组件运行时产生一个窗口，当用户双击该窗口的时候，我需要通知调用者；&lt;BR&gt;　　我的 COM 组件用线程方式下载网络上的一个文件，当我完成任务后，需要通知调用者；&lt;BR&gt;　　我的 COM 组件完成一个钟表的功能，当预定时间到达的时候，我需要通知调用者；&lt;BR&gt;　　... ... ... ...&lt;BR&gt;　　本回书开始话说 COM 的事件、通知、连接点......这些内容比较多，我分两次（共四回）来介绍。&lt;BR&gt;&lt;B&gt;&lt;BR&gt;二、通知的方法&lt;BR&gt;&lt;/B&gt;　　当程序甲方内部发生了某个事件的时候，需要通知乙方，无非使用几个方法：&lt;BR&gt;　 
&lt;TABLE cellSpacing=1 width="100%" border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD align=middle width="35%" colSpan=2&gt;&lt;B&gt;通知方式&lt;/B&gt;&lt;/TD&gt;
&lt;TD align=middle width="39%"&gt;&lt;B&gt;简单说明&lt;/B&gt;&lt;/TD&gt;
&lt;TD align=middle width="59%"&gt;&lt;B&gt;评论&lt;/B&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="9%" rowSpan=3&gt;直接消息&lt;/TD&gt;
&lt;TD width="26%"&gt;PostMessage()&lt;BR&gt;PostThreadMessage()&lt;/TD&gt;
&lt;TD width="39%"&gt;向窗口或线程发个消息&lt;/TD&gt;
&lt;TD width="59%"&gt;你什么时候执行我就不管啦&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="26%"&gt;SendMessage()&lt;/TD&gt;
&lt;TD width="39%"&gt;马上执行消息响应函数&lt;/TD&gt;
&lt;TD width="59%"&gt;不执行完消息处理函数不会返回&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="26%"&gt;SendMessage(WM_COPYDATA...)&lt;/TD&gt;
&lt;TD width="39%"&gt;发消息的同时，还可以带过去一些自定义的数据&lt;/TD&gt;
&lt;TD width="59%"&gt;比较常用，所以单独列了出来&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="9%"&gt;间接消息&lt;/TD&gt;
&lt;TD width="26%"&gt;InvalidateRect()&lt;BR&gt;SetTimer()&lt;BR&gt;......&lt;/TD&gt;
&lt;TD width="39%"&gt;被调用的函数会发送相关的一些消息&lt;/TD&gt;
&lt;TD width="59%"&gt;这样的函数太多了&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="9%"&gt;回调函数&lt;/TD&gt;
&lt;TD width="26%"&gt;GetOpenFileName()......&lt;/TD&gt;
&lt;TD width="39%"&gt;当用户改变文件选择的时候，执行回调函数&lt;/TD&gt;
&lt;TD width="59%"&gt;嗨！哥们，这是我的电话，有事就言语一声。&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;/P&gt;
&lt;P&gt;　　在 COM 的时代，以上这些方法就基本上不能玩转了，因为...您想呀&lt;B&gt; COM 组件是运行在分布式环境中的&lt;/B&gt;，地球另一边计算机上运行的组件，怎么可能给你的窗口发消息那？当然不能！（但话又说回来，对于 ActiveX 这样只能在本地运行的组件，当然也可以发送窗口消息的啦。）&lt;BR&gt;　　回调函数的方式，是设计 COM 通知方法的基础。回调函数，本质上是预先把某一函数的指针告诉我，当我有必要的时候，就直接呼叫该函数了，而这个回调函数做了什么，怎么做的，我是根本不关心的。好了，问你个问题：啥是 COM 的接口？接口其实就是一组相关函数的集合（这个定义不严谨，但你可以这么理解哈）。因此，在COM中不使用&amp;#8220;回调函数&amp;#8221;而是使用&amp;#8220;回调接口&amp;#8221;（说的再清楚一些，就是使用一大堆包装好的&amp;#8220;回调函数&amp;#8221;集） ，回调接口，我们也叫&amp;#8220;接收器接口&amp;#8221;。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=174 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut14pic1.jpg" width=528 border=0&gt;&lt;BR&gt;图一、客户端传递接收器接口指针给COM。当发生事件时，COM调用接收器接口函数完成通知&lt;BR&gt;&lt;BR&gt;本回示例程序完成的功能是：&lt;BR&gt;　　客户端启动组件(Simple11.IEvent1.1)并得到接口指针 IEvent1 *；&lt;BR&gt;　　调用接口方法 IEvent1::Advise() 把客户端内部的一个接收器(sink)接口指针(ICallBack *)传递到组件服务器中；&lt;BR&gt;　　调用 IEvent1::Add() 去计算两个整数的和；&lt;BR&gt;　　但是计算结果并不通过该函数返回，而是通过 ICallBack::Fire_Result() 返回给客户端；&lt;BR&gt;　　当客户端不再需要接受事件的时候，调用 IEvent1::Unadvise() 断开和组件的联系。&lt;BR&gt;&lt;BR&gt;&lt;B&gt;三、组件实现步骤&lt;/B&gt;&lt;BR&gt;1、建立一个解决方案&lt;BR&gt;2、在解决方案中，建立一个 ATL 项目。示例程序中项目名称叫 Simple12，取消&amp;#8220;属性化&amp;#8221;，其它接受默认选项。&lt;BR&gt;3、选择项目，执行鼠标右键菜单命令&amp;#8220;添加\添加类&amp;#8221;。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 3-1、左侧分类选择 ATL，右侧模板选择 Atl 简单对象&lt;BR&gt;&amp;nbsp;&amp;nbsp; 3-2、名称卡片中，输入组件名称。示例程序中是 Event1（注1）&lt;BR&gt;&amp;nbsp;&amp;nbsp; 3-3、选项卡片中，修改接口类型&amp;#8220;自定义&amp;#8221;（注2）&lt;BR&gt;4、选择 IEnvent1 接口，鼠标右键菜单&amp;#8220;添加\添加方法&amp;#8221;&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut14pic2.jpg" width=615 border=0&gt;&lt;BR&gt;图二、增加接口函数 Add([in] long n1,[in] long n2)&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut14pic3.jpg" width=615 border=0&gt;&lt;BR&gt;图三、增加接口函数 Advise([in] ICallBack *pCallBack,[out] long *pdwCookie)&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut14pic4.jpg" width=615 border=0&gt;&lt;BR&gt;图四、增加接口函数 Unadvise([in] long dwCookie)&lt;BR&gt;&lt;BR&gt;　　你应该注意到了，在Add()函数中，并没有[out]、[retval] 这样的 IDL 属性，嘿嘿，因为我们本来就不打算通过 Add() 函数直接得到计算结果。不然怎么演示回调接口呀:-) 另外，在函数 Advise()中，需要返回一个整数 dwCookie，这是干什么？道理很简单，因为我们的组件想同时支持多个对象的回调连接。因此当客户端传递一个接口给我们组件的时候，我返回给它唯一的一个 cookie 号码来表示身份，将来断开连接的时候 Unadvise()，它需要把这个 cookie 身份号再给我，这样我就知道是谁想断开了。&lt;BR&gt;5、增加回调接口 ICallBack 的 IDL 定义。打开 IDL 文件并手工输入（黑体字部分为手工输入的） ，然后保存：&lt;/P&gt;&lt;PRE&gt;import "oaidl.idl";
import "ocidl.idl";
&lt;B&gt;[
	object,
	uuid(DB72DF86-70E9-4ABC-B2F8-5E04062D3B2E),	// 这个 IID 可以用 GUDIGEN.EXE 产生
	helpstring("ICallBack 接口"),
	pointer_default(unique)
]
interface ICallBack : IUnknown
{

};
&lt;/B&gt;
[
	object,		// 以下内容同示例程序，当然如果是你自己生成的程序就肯定有差别的啦
	uuid(DB72DF85-70E9-4ABC-B2F8-5E04062D3B2E),
	
	helpstring("IEvent1 Interface"),
	pointer_default(unique)
]
interface IEvent1 : IUnknown
{
	[helpstring("method Add")] HRESULT Add([in] long n1, [in] long n2);
	[helpstring("method Advise")] HRESULT Advise([in] ICallBack * pCallBack, [out] long * pdwCookie);
	[helpstring("method Unadvise")] HRESULT Unadvise([in] long dwCookie);
};

[
	uuid(FBA1E0F0-49CD-4B77-B9B1-4DC066AF8A8E),
	version(1.0),
	helpstring("Simple12 1.0 类型库")
]
library SIMPLE11Lib
{
	importlib("stdole32.tlb");
	importlib("stdole2.tlb");

	[
		uuid(53E00126-B1A0-4510-B9BC-75ED87CE2DB7),
		helpstring("Event1 Class")
	]
	coclass Event1
	{
		[default] interface IEvent1;
		// 需要手工输入，据说 VB 使用的话，不能有 [source,default] 属性
		&lt;B&gt;[source, default] interface ICallBack;&lt;/B&gt;	
	};
};&lt;/PRE&gt;6、增加回调接口函数&lt;BR&gt;&lt;BR&gt;&lt;IMG height=354 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut14pic5.jpg" width=409 border=0&gt;&lt;BR&gt;图五、增加回调接口函数&lt;BR&gt;&lt;BR&gt;　　其实和以前的方法一样，只要注意别选错了接口就好。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut14pic6.jpg" width=615 border=0&gt;&lt;BR&gt;图六、增加接口函数 Fire_Result([in] long nResult)&lt;BR&gt;&lt;BR&gt;　　我们计算整数和，得到结果后，就是要靠这个回调接口函数去反馈给客户端呀。&lt;BR&gt;&lt;BR&gt;7、添加组件内部保存回调接口指针的数组&lt;BR&gt;　　刚才已经说过，我们这个组件打算支持多个对象的回调连接，因此我们要使用一个数组来保存。由于 vc.net 无法用向导来添加数组形式的成员变量，我们还是打开 CEvent1 类的头文件，手工输入吧：&lt;PRE&gt;......
private:
	ICallBack * m_pCallBack[10];
......&lt;/PRE&gt;　　保存一个数组可以有多种方式。示例程序比较简单，定义了一个10个元素空间的成员数组变量。如果你已经学会了使用 STL，那么你也可以用 vector 等容器来实现。&lt;B&gt;注意！注意！注意！在构造函数中别忘了初始化数组元素为 NULL&lt;/B&gt;。&lt;BR&gt;&lt;BR&gt;8、好了，下面开始完成所有代码&lt;PRE&gt;STDMETHODIMP CEvent1::Add(long n1, long n2)
{
	long nResult = n1 + n2;
	for( int i=0; i&amp;lt;10; i++)
	{
		if( m_pCallBack[i] )　　// 如果回调接口有效
			m_pCallBack[i]-&amp;gt;Fire_Result( nResult );	// 则发出事件/通知
	}

	return S_OK;
}

STDMETHODIMP CEvent1::Advise(ICallBack *pCallBack, long *pdwCookie)
{
	if( NULL == pCallBack )	// 居然给我一个空指针？！
		return E_INVALIDARG;

	for( int i=0; i&amp;lt;10; i++)	// 寻找一个保存该接口指针的位置
	{
		if( NULL == m_pCallBack[i] )	// 找到了
		{
			m_pCallBack[i] = pCallBack;	// 保存到数组中
			m_pCallBack[i]-&amp;gt;AddRef();	// 指针计数器 +1

			*pdwCookie = i + 1;	// cookie 就是数组下标
　　		// +1 的目的是避免使用0，因为0表示无效

			return S_OK;
		}
	}
	return E_OUTOFMEMORY;	// 超过10个连接，内存不够用啦
}

STDMETHODIMP CEvent1::Unadvise(long dwCookie)
{
	if( dwCookie&amp;lt;1 || dwCookie&amp;gt;10 )	// 这是谁干的呀？乱给参数
		return E_INVALIDARG;

	if( NULL == m_pCallBack[ dwCookie - 1 ] )	// 参数错误，或该接口指针已经无效了
		return E_INVALIDARG;

	m_pCallBack[ dwCookie -1 ]-&amp;gt;Release();	// 指针计数器 -1
	m_pCallBack[ dwCookie -1 ] = NULL;		// 空出该下标的数组元素

	return S_OK;
}
&lt;/PRE&gt;&lt;B&gt;四、客户端实现步骤&lt;/B&gt;&lt;BR&gt;　　大家下载示例程序后，去浏览客户端的实现程序吧。这里我只说明一下关于接收器是如何构造的：&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut14pic7.jpg" width=615 border=0&gt;&lt;BR&gt;图七、从 ICallBack 派生接收器类 CSink&lt;BR&gt;&lt;BR&gt;　　这里 ICallBack 是 COM 接口，因此 CSink 是不能事例化的，如果你去编译，会得到一坨一坨（注3）的错误，报告说你没有实现 virtual 函数。然后，我们可以按照错误报告，去实现所有的虚函数：&lt;PRE&gt;// STDMETHODIMP 是宏，等价于 long __stdcall
STDMETHODIMP CSink::QueryInterface(const struct _GUID &amp;amp;iid,void ** ppv)
{
	*ppv=this;	// 不管想得到什么接口，其实都是对象本身
	return S_OK;
}

ULONG __stdcall CSink::AddRef(void)
{	return 1;	}// 做个假的就可以，因为反正这个对象在程序结束前是不会退出的

ULONG __stdcall CSink::Release(void)
{	return 0;	}// 做个假的就可以，因为反正这个对象在程序结束前是不会退出的

STDMETHODIMP CSink::raw_Fire_Result(long nResult)
{
	... ...	// 把计算结果显示在窗口中
	return S_OK;
}
&lt;/PRE&gt;&lt;B&gt;五&lt;/B&gt;&lt;B&gt;、小结&lt;BR&gt;&lt;/B&gt;　　COM 组件实现事件、通知这样的功能有两个基本方法。今天介绍的回调接口方式非常好，速度快、结构清晰、实现也不复杂；下回书介绍连接点方式(Support Connection Points)，连接点方法其实并不太好，速度慢（如果是远程DCOM方式，要谨慎选择它）、结构复杂、唯一的好处就是 ATL 对它进行了包装，所以实现起来反而比较简单。不介绍又不行，因为微软绝大数支持事件的组件都是用连接点实现的，咳......讨厌的微软(注4)。 
&lt;HR&gt;
注1：本来设想多举几个例子，因此第一个叫 Event1，可写完后，感觉程序已经比较复杂了，就没继续再做了。&lt;BR&gt;注2：当然，你选择使用双接口 Dual 也没有问题。但要注意到在下面的步骤，增加回调接口修改 IDL 文件的时候，我们是要使用 Custom(从IUnknown派生，而不是从IDispatch派生)的。&lt;BR&gt;注3：一坨一坨经常用来形容一堆一堆的狗屎。&lt;BR&gt;注4：微软的同志们，玩笑话不要当真呀！我还靠着你来吃饭那。&lt;BR&gt;&lt;img src ="http://blog.vckbase.com/teacheryang/aggbug/11757.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>杨老师的茅屋</dc:creator><title>《COM组件设计与应用（十三）--事件和通知(VC6.0)》（原文发表在vckbase上）</title><link>http://blog.vckbase.com/teacheryang/archive/2005/09/01/11756.html</link><pubDate>Thu, 01 Sep 2005 12:42:00 GMT</pubDate><guid>http://blog.vckbase.com/teacheryang/archive/2005/09/01/11756.html</guid><wfw:comment>http://blog.vckbase.com/teacheryang/comments/11756.html</wfw:comment><comments>http://blog.vckbase.com/teacheryang/archive/2005/09/01/11756.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://blog.vckbase.com/teacheryang/comments/commentRss/11756.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/teacheryang/services/trackbacks/11756.html</trackback:ping><description>&lt;P align=center&gt;&lt;B&gt;COM组件设计与应用（十三）&lt;BR&gt;事件和通知(VC6.0)&lt;BR&gt;&lt;/B&gt;&lt;BR&gt;&lt;A href="http://www.vckbase.com/code/downcode.asp?id=2752"&gt;下载源代码&lt;/A&gt;&lt;B&gt;&lt;BR&gt;&lt;BR&gt;一、前言&lt;/B&gt;&lt;BR&gt;　　我的 COM 组件运行时产生一个窗口，当用户双击该窗口的时候，我需要通知调用者；&lt;BR&gt;　　我的 COM 组件用线程方式下载网络上的一个文件，当我完成任务后，需要通知调用者；&lt;BR&gt;　　我的 COM 组件完成一个钟表的功能，当预定时间到达的时候，我需要通知调用者；&lt;BR&gt;　　... ... ... ...&lt;BR&gt;　　本回书开始话说 COM 的事件、通知、连接点......这些内容比较多，我分两次（共四回）来介绍。&lt;BR&gt;&lt;B&gt;&lt;BR&gt;二、通知的方法&lt;BR&gt;&lt;/B&gt;　　当程序甲方内部发生了某个事件的时候，需要通知乙方，无非使用几个方法：&lt;BR&gt;　 
&lt;TABLE cellSpacing=1 width="99%" border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD align=middle width="35%" colSpan=2&gt;&lt;B&gt;通知方式&lt;/B&gt;&lt;/TD&gt;
&lt;TD align=middle width="39%"&gt;&lt;B&gt;简单说明&lt;/B&gt;&lt;/TD&gt;
&lt;TD align=middle width="19%"&gt;&lt;B&gt;评论&lt;/B&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="9%" rowSpan=3&gt;直接消息&lt;/TD&gt;
&lt;TD width="26%"&gt;PostMessage()&lt;BR&gt;PostThreadMessage()&lt;/TD&gt;
&lt;TD width="39%"&gt;向窗口或线程发个消息&lt;/TD&gt;
&lt;TD width="19%"&gt;你什么时候执行我就不管啦&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="26%"&gt;SendMessage()&lt;/TD&gt;
&lt;TD width="39%"&gt;马上执行消息响应函数&lt;/TD&gt;
&lt;TD width="19%"&gt;不执行完消息处理函数不会返回&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="26%"&gt;SendMessage(WM_COPYDATA...)&lt;/TD&gt;
&lt;TD width="39%"&gt;发消息的同时，还可以带过去一些自定义的数据&lt;/TD&gt;
&lt;TD width="19%"&gt;比较常用，所以单独列了出来&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="9%"&gt;间接消息&lt;/TD&gt;
&lt;TD width="26%"&gt;InvalidateRect()&lt;BR&gt;SetTimer()&lt;BR&gt;......&lt;/TD&gt;
&lt;TD width="39%"&gt;被调用的函数会发送相关的一些消息&lt;/TD&gt;
&lt;TD width="19%"&gt;这样的函数太多了&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="9%"&gt;回调函数&lt;/TD&gt;
&lt;TD width="26%"&gt;GetOpenFileName()......&lt;/TD&gt;
&lt;TD width="39%"&gt;当用户改变文件选择的时候，执行回调函数&lt;/TD&gt;
&lt;TD width="19%"&gt;嗨！哥们，这是我的电话，有事就言语一声。&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;/P&gt;
&lt;P&gt;　　在 COM 的时代，以上这些方法就基本上不能玩转了，因为...您想呀&lt;B&gt; COM 组件是运行在分布式环境中的&lt;/B&gt;，地球另一边计算机上运行的组件，怎么可能给你的窗口发消息那？当然不能！（但话又说回来，对于 ActiveX 这样只能在本地运行的组件，当然也可以发送窗口消息的啦。）&lt;BR&gt;　　回调函数的方式，是设计 COM 通知方法的基础。回调函数，本质上是预先把某一函数的指针告诉我，当我有必要的时候，就直接呼叫该函数了，而这个回调函数做了什么，怎么做的，我是根本不关心的。好了，问你个问题：啥是 COM 的接口？接口其实就是一组相关函数的集合（这个定义不严谨，但你可以这么理解哈）。因此，在COM中不使用&amp;#8220;回调函数&amp;#8221;而是使用&amp;#8220;回调接口&amp;#8221;（说的再清楚一些，就是使用一大堆包装好的&amp;#8220;回调函数&amp;#8221;集） ，回调接口，我们也叫&amp;#8220;接收器接口&amp;#8221;。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=174 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut13pic1.jpg" width=528 border=0&gt;&lt;BR&gt;图一、客户端传递接收器接口指针给COM。当发生事件时，COM调用接收器接口函数完成通知&lt;BR&gt;&lt;BR&gt;本回示例程序完成的功能是：&lt;BR&gt;　　客户端启动组件(Simple11.IEvent1.1)并得到接口指针 IEvent1 *；&lt;BR&gt;　　调用接口方法 IEvent1::Advise() 把客户端内部的一个接收器(sink)接口指针(ICallBack *)传递到组件服务器中；&lt;BR&gt;　　调用 IEvent1::Add() 去计算两个整数的和；&lt;BR&gt;　　但是计算结果并不通过该函数返回，而是通过 ICallBack::Fire_Result() 返回给客户端；&lt;BR&gt;　　当客户端不再需要接受事件的时候，调用 IEvent1::Unadvise() 断开和组件的联系。&lt;BR&gt;&lt;BR&gt;&lt;B&gt;三、组件实现步骤&lt;/B&gt;&lt;BR&gt;1、建立一个工作区(WorkSpace)&lt;BR&gt;2、在工作区中，建立一个 ATL 工程(Project)。示例程序中工程名称叫 Simple11，接受全部默认选项。&lt;BR&gt;3、ClassView 中，执行鼠标右键菜单命令 New Atl Object...，添加 ALT 类。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 3-1、左侧分类 Category 选择 Objects，右侧 Objects 选择 SimpleObject（其实就是默认项目）&lt;BR&gt;&amp;nbsp;&amp;nbsp; 3-2、名称 Name 卡片中，输入组件名称。示例程序中是 Event1（注1）&lt;BR&gt;&amp;nbsp;&amp;nbsp; 3-3、属性 Attributes 卡片中，修改接口类型 Interface 为定制的 Custom（注2）&lt;BR&gt;4、ClassView 中，选择接口（IEvent1），鼠标右键菜单添加函数 Add Method...&lt;BR&gt;&lt;BR&gt;&lt;IMG height=311 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut13pic2.jpg" width=486 border=0&gt;&lt;BR&gt;图二、增加接口函数 Add([in] long n1,[in] long n2)&lt;BR&gt;&lt;BR&gt;&lt;IMG height=311 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut13pic3.jpg" width=486 border=0&gt;&lt;BR&gt;图三、增加接口函数 Advise([in] ICallBack *pCallBack,[out] long *pdwCookie)&lt;BR&gt;&lt;BR&gt;&lt;IMG height=311 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut13pic4.jpg" width=486 border=0&gt;&lt;BR&gt;图四、增加接口函数 Unadvise([in] long dwCookie)&lt;BR&gt;&lt;BR&gt;　　你应该注意到了，在Add()函数中，并没有[out]、[retval] 这样的 IDL 属性，嘿嘿，因为我们本来就不打算通过 Add() 函数直接得到计算结果。不然怎么演示回调接口呀:-) 另外，在函数 Advise()中，需要返回一个整数 dwCookie，这是干什么？道理很简单，因为我们的组件想同时支持多个对象的回调连接。因此当客户端传递一个接口给我们组件的时候，我返回给它唯一的一个 cookie 号码来表示身份，将来断开连接的时候 Unadvise()，它需要把这个 cookie 身份号再给我，这样我就知道是谁想断开了。&lt;BR&gt;5、增加回调接口 ICallBack 的 IDL 定义。打开 IDL 文件并手工输入（黑体字部分为手工输入的） ，然后保存：&lt;/P&gt;&lt;PRE&gt;import "oaidl.idl";
import "ocidl.idl";
&lt;B&gt;[
	object,
	uuid(7E659BB1-FB79-4188-9661-65CA22B6A3E6),	// 这个 IID 可以用 GUDIGEN.EXE 产生
	
	helpstring("ICallBack Interface"),
	pointer_default(unique)
]
interface ICallBack : IUnknown
{

};
&lt;/B&gt;
[
	object,		// 以下内容同示例程序，当然如果是你自己生成的程序就肯定有差别的啦
	uuid(7E659BB0-FB79-4188-9661-65CA22B6A3E6),
	
	helpstring("IEvent1 Interface"),
	pointer_default(unique)
]
interface IEvent1 : IUnknown
{
	[helpstring("method Add")] HRESULT Add([in] long n1, [in] long n2);
	[helpstring("method Advise")] HRESULT Advise([in] ICallBack * pCallBack, [out] long * pdwCookie);
	[helpstring("method Unadvise")] HRESULT Unadvise([in] long dwCookie);
};

[
	uuid(695C9BB2-2AE9-4232-8225-17AB8BD3BABC),
	version(1.0),
	helpstring("Simple11 1.0 Type Library")
]
library SIMPLE11Lib
{
	importlib("stdole32.tlb");
	importlib("stdole2.tlb");

	[
		uuid(6FCF997C-C811-49DB-9D16-46FAF8D24822),
		helpstring("Event1 Class")
	]
	coclass Event1
	{
		[default] interface IEvent1;
		// 需要手工输入，据说 VB 使用的话，不能有 [source,default] 属性
		&lt;B&gt;[source, default] interface ICallBack;&lt;/B&gt;	
	};
};&lt;/PRE&gt;6、增加回调接口函数&lt;BR&gt;&lt;BR&gt;&lt;IMG height=317 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut13pic5.jpg" width=258 border=0&gt;&lt;BR&gt;图五、增加回调接口函数&lt;BR&gt;&lt;BR&gt;其实和以前的方法一样，只要注意别选错了接口就好。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=311 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut13pic6.jpg" width=486 border=0&gt;&lt;BR&gt;图六、增加接口函数 Fire_Result([in] long nResult)&lt;BR&gt;&lt;BR&gt;我们计算整数和，得到结果后，就是要靠这个回调接口函数去反馈给客户端呀。&lt;BR&gt;&lt;BR&gt;7、添加组件内部保存回调接口指针的数组&lt;BR&gt;　　刚才已经说过，我们这个组件打算支持多个对象的回调连接，因此我们要使用一个数组来保存。在 ClassView 中，选择 CEvent1 类，增加成员变量 Add Member Variable...&lt;BR&gt;&lt;BR&gt;&lt;IMG height=209 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut13pic7.jpg" width=486 border=0&gt;&lt;BR&gt;图七、增加保存 ICallBack * 的数组&lt;BR&gt;&lt;BR&gt;　　当然，保存一个数组可以有多种方式。示例程序比较简单，定义了一个10个元素空间的成员数组变量。如果你已经学会了使用 STL，那么你也可以用 vector 等容器来实现。&lt;B&gt;注意！注意！注意！在构造函数中别忘了初始化数组元素为 NULL&lt;/B&gt;。&lt;BR&gt;&lt;BR&gt;8、好了，下面开始完成所有代码&lt;PRE&gt;STDMETHODIMP CEvent1::Add(long n1, long n2)
{
	long nResult = n1 + n2;
	for( int i=0; i&amp;lt;10; i++)
	{
		if( m_pCallBack[i] )　　// 如果回调接口有效
			m_pCallBack[i]-&amp;gt;Fire_Result( nResult );	// 则发出事件/通知
	}

	return S_OK;
}

STDMETHODIMP CEvent1::Advise(ICallBack *pCallBack, long *pdwCookie)
{
	if( NULL == pCallBack )	// 居然给我一个空指针？！
		return E_INVALIDARG;

	for( int i=0; i&amp;lt;10; i++)	// 寻找一个保存该接口指针的位置
	{
		if( NULL == m_pCallBack[i] )	// 找到了
		{
			m_pCallBack[i] = pCallBack;	// 保存到数组中
			m_pCallBack[i]-&amp;gt;AddRef();	// 指针计数器 +1

			*pdwCookie = i + 1;	// cookie 就是数组下标
　　		// +1 的目的是避免使用0，因为0表示无效

			return S_OK;
		}
	}
	return E_OUTOFMEMORY;	// 超过10个连接，内存不够用啦
}

STDMETHODIMP CEvent1::Unadvise(long dwCookie)
{
	if( dwCookie&amp;lt;1 || dwCookie&amp;gt;10 )	// 这是谁干的呀？乱给参数
		return E_INVALIDARG;

	if( NULL == m_pCallBack[ dwCookie - 1 ] )	// 参数错误，或该接口指针已经无效了
		return E_INVALIDARG;

	m_pCallBack[ dwCookie -1 ]-&amp;gt;Release();	// 指针计数器 -1
	m_pCallBack[ dwCookie -1 ] = NULL;		// 空出该下标的数组元素

	return S_OK;
}
&lt;/PRE&gt;&lt;B&gt;四、客户端实现步骤&lt;/B&gt;&lt;BR&gt;　　大家下载示例程序后，去浏览客户端的实现程序吧。这里我只说明一下关于接收器是如何构造的：&lt;BR&gt;&lt;BR&gt;&lt;IMG height=481 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut13pic8.jpg" width=536 border=0&gt;&lt;BR&gt;图八、从 ICallBack 派生接收器类 CSink&lt;BR&gt;&lt;BR&gt;　　从 ICallBack 派生一个类 CSink。确认后 IDE 会有一个警告，说它找不到 ICallBack 的头文件，不用理它，因为只有当编译的时候，#import 才会为我们生成 xxxx.tlh、xxxx.tli 文件，这些文件就有 ICallBack 的声明啦。&lt;BR&gt;　　这里 ICallBack 是 COM 接口，因此 CSink 是不能事例化的，如果你去编译，会得到一坨一坨（注3）的错误，报告说你没有实现 virtual 函数。然后，我们可以按照错误报告，去实现所有的虚函数：&lt;PRE&gt;// STDMETHODIMP 是宏，等价于 long __stdcall
STDMETHODIMP CSink::QueryInterface(const struct _GUID &amp;amp;iid,void ** ppv)
{
	*ppv=this;	// 不管想得到什么接口，其实都是对象本身
	return S_OK;
}

ULONG __stdcall CSink::AddRef(void)
{	return 1;	}// 做个假的就可以，因为反正这个对象在程序结束前是不会退出的

ULONG __stdcall CSink::Release(void)
{	return 0;	}// 做个假的就可以，因为反正这个对象在程序结束前是不会退出的

STDMETHODIMP CSink::raw_Fire_Result(long nResult)
{
	... ...	// 把计算结果显示在窗口中
	return S_OK;
}
&lt;/PRE&gt;&lt;B&gt;五&lt;/B&gt;&lt;B&gt;、小结&lt;BR&gt;&lt;/B&gt;　　COM 组件实现事件、通知这样的功能有两个基本方法。今天介绍的回调接口方式非常好，速度快、结构清晰、实现也不复杂；下回书介绍连接点方式(Support Connection Points)，连接点方法其实并不太好，速度慢（如果是远程DCOM方式，要谨慎选择它）、结构复杂、唯一的好处就是 ATL 对它进行了包装，所以实现起来反而比较简单。不介绍又不行，因为微软绝大数支持事件的组件都是用连接点实现的，咳......讨厌的微软(注4)。 
&lt;HR&gt;
注1：本来设想多举几个例子，因此第一个叫 Event1，可写完后，感觉程序已经比较复杂了，就没继续再做了。&lt;BR&gt;注2：当然，你选择使用双接口 Dual 也没有问题。但要注意到在下面的步骤，增加回调接口修改 IDL 文件的时候，我们是要使用 Custom(从IUnknown派生，而不是从IDispatch派生)的。&lt;BR&gt;注3：一坨一坨经常用来形容一堆一堆的狗屎。&lt;BR&gt;注4：微软的同志们，玩笑话不要当真呀！我还靠着你来吃饭那。&lt;BR&gt;&lt;img src ="http://blog.vckbase.com/teacheryang/aggbug/11756.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>杨老师的茅屋</dc:creator><title>《COM 组件设计与应用（十二）---错误异常处理》(原文发表在vckbase上)</title><link>http://blog.vckbase.com/teacheryang/archive/2005/08/26/11559.html</link><pubDate>Fri, 26 Aug 2005 14:14:00 GMT</pubDate><guid>http://blog.vckbase.com/teacheryang/archive/2005/08/26/11559.html</guid><wfw:comment>http://blog.vckbase.com/teacheryang/comments/11559.html</wfw:comment><comments>http://blog.vckbase.com/teacheryang/archive/2005/08/26/11559.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://blog.vckbase.com/teacheryang/comments/commentRss/11559.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/teacheryang/services/trackbacks/11559.html</trackback:ping><description>&lt;P align=center&gt;&lt;B&gt;COM组件设计与应用（十二）&lt;BR&gt;错误与异常处理&lt;BR&gt;&lt;/B&gt;&lt;BR&gt;&lt;A href="http://www.vckbase.com/code/downcode.asp?id=2748"&gt;下载源代码&lt;/A&gt;&lt;B&gt;&lt;BR&gt;&lt;BR&gt;一、前言&lt;/B&gt;&lt;BR&gt;　　程序设计中，错误处理必不可少，而且通常要占用很大的篇幅。本回书着落在 COM 中的错误（异常）的处理方法。&lt;BR&gt;　　在组件程序中，如果遇到错误，一般有两个方式进行处理。&lt;BR&gt;&lt;BR&gt;&lt;B&gt;二、简单返回&lt;/B&gt;&lt;BR&gt;　　对于比较简单的错误，直接返回表示错误原因的 HRESULT。比如下面几个就是常见的错误值：&lt;BR&gt;　 
&lt;TABLE cellSpacing=1 width="100%" border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="19%"&gt;E_INVALIDARG&lt;/TD&gt;
&lt;TD align=middle width="13%"&gt;0x80070057&lt;/TD&gt;
&lt;TD width="68%"&gt;参数错误&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="19%"&gt;E_OUTOFMEMORY&lt;/TD&gt;
&lt;TD align=middle width="13%"&gt;0x8007000E&lt;/TD&gt;
&lt;TD width="68%"&gt;内存错误&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="19%"&gt;E_NOTIMPL&lt;/TD&gt;
&lt;TD align=middle width="13%"&gt;0x80004001&lt;/TD&gt;
&lt;TD width="68%"&gt;未实现&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="19%"&gt;E_POINTER&lt;/TD&gt;
&lt;TD align=middle width="13%"&gt;0x80004003&lt;/TD&gt;
&lt;TD width="68%"&gt;无效指针&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="19%"&gt;E_HANDLE&lt;/TD&gt;
&lt;TD align=middle width="13%"&gt;0x80070006&lt;/TD&gt;
&lt;TD width="68%"&gt;无效句柄&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="19%"&gt;E_ABORT&lt;/TD&gt;
&lt;TD align=middle width="13%"&gt;0x80004004&lt;/TD&gt;
&lt;TD width="68%"&gt;终止操作&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="19%"&gt;E_ACCESSDENIED&lt;/TD&gt;
&lt;TD align=middle width="13%"&gt;0x80070005&lt;/TD&gt;
&lt;TD width="68%"&gt;拒绝访问&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="19%"&gt;E_NOINTERFACE&lt;/TD&gt;
&lt;TD align=middle width="13%"&gt;0x80004002&lt;/TD&gt;
&lt;TD width="68%"&gt;不支持接口&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;/P&gt;
&lt;P&gt;　　另外，你还可以返回自己构造 HRESULT 错误值。方法是使用宏 MAKE_HRESULT(sev,fac,code)&lt;BR&gt;　 
&lt;TABLE cellSpacing=1 width="100%" border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD align=middle width="33%"&gt;&lt;B&gt;参数&lt;/B&gt;&lt;/TD&gt;
&lt;TD align=middle width="33%"&gt;&lt;B&gt;含义&lt;/B&gt;&lt;/TD&gt;
&lt;TD align=middle width="34%"&gt;&lt;B&gt;值（二进制）&lt;/B&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%" rowSpan=4&gt;
&lt;P align=center&gt;sev 严重程度&lt;/P&gt;&lt;/TD&gt;
&lt;TD width="33%"&gt;成功&lt;/TD&gt;
&lt;TD width="34%"&gt;00&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;成功，但有一些报告信息&lt;/TD&gt;
&lt;TD width="34%"&gt;01&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;警告&lt;/TD&gt;
&lt;TD width="34%"&gt;10&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;错误&lt;/TD&gt;
&lt;TD width="34%"&gt;11&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%" rowSpan=27&gt;
&lt;P align=center&gt;fac 设备信息&lt;/P&gt;&lt;/TD&gt;
&lt;TD width="33%"&gt;FACILITY_AAF&lt;/TD&gt;
&lt;TD width="34%"&gt;00000010010&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_ACS&lt;/TD&gt;
&lt;TD width="34%"&gt;00000010100&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_BACKGROUNDCOPY&lt;/TD&gt;
&lt;TD width="34%"&gt;00000100000&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_CERT&lt;/TD&gt;
&lt;TD width="34%"&gt;00000001011&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_COMPLUS&lt;/TD&gt;
&lt;TD width="34%"&gt;00000010001&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_CONFIGURATION&lt;/TD&gt;
&lt;TD width="34%"&gt;00000100001&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_CONTROL&lt;/TD&gt;
&lt;TD width="34%"&gt;00000001010&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_DISPATCH&lt;/TD&gt;
&lt;TD width="34%"&gt;00000000010&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_DPLAY&lt;/TD&gt;
&lt;TD width="34%"&gt;00000010101&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_HTTP&lt;/TD&gt;
&lt;TD width="34%"&gt;00000011001&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_INTERNET&lt;/TD&gt;
&lt;TD width="34%"&gt;00000001100&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_ITF&lt;/TD&gt;
&lt;TD width="34%"&gt;00000000100&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_MEDIASERVER&lt;/TD&gt;
&lt;TD width="34%"&gt;00000001101&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_MSMQ&lt;/TD&gt;
&lt;TD width="34%"&gt;00000001110&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_NULL&lt;/TD&gt;
&lt;TD width="34%"&gt;00000000000&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_RPC&lt;/TD&gt;
&lt;TD width="34%"&gt;00000000001&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_SCARD&lt;/TD&gt;
&lt;TD width="34%"&gt;00000010000&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_SECURITY&lt;/TD&gt;
&lt;TD width="34%"&gt;00000001001&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_SETUPAPI&lt;/TD&gt;
&lt;TD width="34%"&gt;00000001111&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_SSPI&lt;/TD&gt;
&lt;TD width="34%"&gt;00000001001&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_STORAGE&lt;/TD&gt;
&lt;TD width="34%"&gt;00000000011&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_SXS&lt;/TD&gt;
&lt;TD width="34%"&gt;00000010111&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_UMI&lt;/TD&gt;
&lt;TD width="34%"&gt;00000010110&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_URT&lt;/TD&gt;
&lt;TD width="34%"&gt;00000010011&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_WIN32&lt;/TD&gt;
&lt;TD width="34%"&gt;00000000111&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_WINDOWS&lt;/TD&gt;
&lt;TD width="34%"&gt;00000001000&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;FACILITY_WINDOWS_CE&lt;/TD&gt;
&lt;TD width="34%"&gt;00000011000&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="33%"&gt;
&lt;P align=center&gt;code 唯一错误码&lt;/P&gt;&lt;/TD&gt;
&lt;TD width="33%"&gt;16位(bit) 你自己定义去吧&lt;/TD&gt;
&lt;TD width="34%"&gt;　&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P&gt;　　调用者得到返回的 HRESULT 值后，也可以使用宏 HRESULT_SEVERITY()、HRESULT_FACILITY()、HRESULT_CODE() 来取得sev错误程度、fac设备信息和 code 错误代码。&lt;BR&gt;&lt;BR&gt;&lt;BR&gt;&lt;B&gt;三、错误信息接口&lt;/B&gt;&lt;BR&gt;　　既然 COM 是靠各种各样的接口来提供服务的，于是很自然地就会想到，是否有一个接口能够提供更丰富的错误信息报告那？答案是：ISupportErrorInfo。下面这段代码是使用 ISupportErrorInfo 的一般方法：&lt;/P&gt;&lt;PRE&gt;STDMETHODIMP Cxxx::fun()
{
	... ... ... ...

	CComQIPtr&amp;lt; ICreateErrorInfo&amp;gt; spCEI;
	::CreateErrorInfo( &amp;amp;spCEI );

	spCEI-&amp;gt;SetGUID( IID_Ixxx );		// 发生错误的接口IID
		
	spCEI-&amp;gt;SetSource( L"xxx.xxx" );	// ProgID

	// 如果你的组件同时提供了帮助文件，那么就可以：
	spCEI-&amp;gt;SetHelpContext( 0 );		// 设置帮助文件的主题号
	spCEI-&amp;gt;SetHelpFile( L"xxx.hlp" );	// 设置帮助文件的文件名

	spCEI-&amp;gt;SetDescription( L"错误描述信息" );

	CComQIPtr &amp;lt; IErrorInfo &amp;gt; spErrInfo = spCEI;
	if( spErrInfo )
	  ::SetErrorInfo( 0, spErrInfo );	// 这时调用者就可以得到错误信息了

	return E_FAIL;
}&lt;/PRE&gt;　　上面是原理性代码，在我们写的程序中，不用这么麻烦。因为 ATL 已经把上述的代码给我们包装成 CComCoClass::Error() 的6个重载函数了。如此，我们可以非常简单的改写为：&lt;PRE&gt;STDMETHODIMP Cxxx::fun()
{
	... ... ... ...

	return Error( L"错误描述信息" );
}&lt;/PRE&gt;&lt;B&gt;四、关于&lt;/B&gt;&lt;B&gt; try/catch&lt;/B&gt;&lt;BR&gt;　　学习了 C++ 后，很多人都喜欢使用 try/catch 的异常处理结构。如果你使用 vc6.0 的ATL，编译器默认是不支持异常处理的，编译后会报告&amp;#8220;warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify -GX&amp;#8221;，解决方法是手工加上编译开关：&lt;BR&gt;&lt;BR&gt;&lt;IMG height=446 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut12pic1.jpg" width=677 border=0&gt;&lt;BR&gt;图一、加上编译开关，支持C++的异常处理结构&lt;BR&gt;&lt;BR&gt;　　在vc.net 2003 中，编译器默认是支持异常处理结构的，所以不用特别进行设置。如果想减小目标文件的尺寸，你也可以决定不使用 C++ 异常处理，那么在项目属性中&lt;BR&gt;&lt;BR&gt;&lt;IMG height=453 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut12pic2.jpg" width=642 border=0&gt;&lt;BR&gt;图二、在vc.net中修改是否支持C++异常结构的编译开关&lt;BR&gt;&lt;BR&gt;&lt;B&gt;&lt;BR&gt;五、客户端接收组件的错误信息&lt;/B&gt;&lt;BR&gt;　　1、如果使用 API 方式调用组件，接收错误的方法是：&lt;PRE&gt;HRESULT hr = spXXX-&amp;gt;fun()	// 调用组件功能
if( FAILED( hr ) )	// 如果发生了错误
{
	CComQIPtr &amp;lt; ISupportErrorInfo &amp;gt; spSEI = spXXX;	// 组件是否提供了 ISupportErrorInfo 接口？
	if( spSEI )	// 如果支持，那么
	{
		hr = spSEI-&amp;gt;InterfaceSupportsErrorInfo( IID_Ixxx );	// 是否支持 Ixxx 接口的错误处理？
		if( SUCCEEDED( hr ) )
		{	// 支持，太好了。取出错误信息
			CComQIPtr &amp;lt; IErrorInfo &amp;gt; spErrInfo;		// 声明 IErrorInfo 接口
			hr = ::GetErrorInfo( 0, &amp;amp;spErrInfo );	// 取得接口
			if( SUCCEEDED( hr ) )
			{
				CComBSTR bstrDes;
				spErrInfo-&amp;gt;GetDescription( &amp;amp;bstrDes );	// 取得错误描述
				......	// 还可以取得其它的信息
			}
		}
	}
}&lt;/PRE&gt;　　2、如果使用 #import 等包装方式调用组件，接收错误的方法是：&lt;PRE&gt;try
{
	......	// 调用组件功能
}
catch( _com_error &amp;amp;e )
{
	e.Description();	// 取得错误描述信息
	......	// 还可以调用 _com_error 函数取得其它信息
}&lt;/PRE&gt;&lt;B&gt;六、编写支持错误处理的组件程序&lt;/B&gt;&lt;BR&gt;　　非常简单，只要在增加 ATL 组件对象的时候选中 ISupportErrorInfo 即可。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=260 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut12pic3.jpg" width=421 border=0&gt;&lt;BR&gt;图三、vc6.0 中，选中组件支持错误处理接口&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut12pic4.jpg" width=615 border=0&gt;&lt;BR&gt;图四、vc.net 2003 中，选中组件支持错误处理接口&lt;BR&gt;&lt;BR&gt;&lt;B&gt;七、小结&lt;BR&gt;&lt;/B&gt;　　阅读文章后，请下载本回的示例程序。示例程序中演示了三种错误处理方法和三种接收错误的方法，同时程序中也有比较详细的注释。&lt;img src ="http://blog.vckbase.com/teacheryang/aggbug/11559.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>杨老师的茅屋</dc:creator><title>《COM 组件设计与应用（十一）---IDispatch的调用》(原文发表在vckbase上)</title><link>http://blog.vckbase.com/teacheryang/archive/2005/08/26/11558.html</link><pubDate>Fri, 26 Aug 2005 14:12:00 GMT</pubDate><guid>http://blog.vckbase.com/teacheryang/archive/2005/08/26/11558.html</guid><wfw:comment>http://blog.vckbase.com/teacheryang/comments/11558.html</wfw:comment><comments>http://blog.vckbase.com/teacheryang/archive/2005/08/26/11558.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://blog.vckbase.com/teacheryang/comments/commentRss/11558.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/teacheryang/services/trackbacks/11558.html</trackback:ping><description>&lt;P align=center&gt;&lt;B&gt;COM 组件设计与应用（&lt;SPAN lang=zh-cn&gt;十一&lt;/SPAN&gt;）&lt;BR&gt;&lt;SPAN lang=en-us&gt;IDispatch &lt;/SPAN&gt;&lt;SPAN lang=zh-cn&gt;及双&lt;/SPAN&gt;接口&lt;SPAN lang=zh-cn&gt;的调用&lt;BR&gt;&lt;/SPAN&gt;&lt;/B&gt;&lt;BR&gt;&lt;A href="http://www.vckbase.com/code/downcode.asp?id=2744"&gt;下载源代码&lt;/A&gt;&lt;BR&gt;&lt;BR&gt;&lt;B&gt;一、前言&lt;/B&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;SPAN lang=zh-cn&gt;前段时间，由于工作比较忙，没有能及时地写作。其间收到了很多网友的来信询问和鼓励，在此一并表示感谢。咳&lt;/SPAN&gt;......&lt;SPAN lang=zh-cn&gt;我也需要工作来养家糊口呀&lt;/SPAN&gt;......&lt;SPAN lang=zh-cn&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 上回书介绍了两种方法来写自动化&lt;/SPAN&gt;(IDispatch)&lt;SPAN lang=zh-cn&gt;接口的组件程序，一是用 MFC 方式编写&amp;#8220;纯粹&amp;#8221;的 &lt;/SPAN&gt;IDispatch&lt;SPAN lang=zh-cn&gt; 接口；二是用 ATL 方式编写&amp;#8220;双接口&amp;#8221;的组件。&lt;/SPAN&gt; &lt;/P&gt;
&lt;P&gt;&lt;B&gt;二、&lt;SPAN lang=en-us&gt;IDispatch &lt;/SPAN&gt;接口&lt;SPAN lang=zh-cn&gt;和双接口&lt;/SPAN&gt;&lt;BR&gt;&lt;/B&gt;&lt;SPAN lang=en-us&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN lang=zh-cn&gt;使用者要想调用普通的 COM 组件功能，必须要加载这个组件的类型库&lt;/SPAN&gt;(Type library)&lt;SPAN lang=zh-cn&gt;文件 &lt;/SPAN&gt;tlb(&lt;SPAN lang=zh-cn&gt;比如在 VC 中使用 &lt;/SPAN&gt;#import)&lt;SPAN lang=zh-cn&gt;。然而，在脚本程序中，由于脚本是被解释执行的，所以无法使用加载类型库的方式进行预编译。那么脚本解释器如何使用 COM 组件那？这就是自动化&lt;/SPAN&gt;(IDispatch)&lt;SPAN lang=zh-cn&gt;组件大显身手的地方了。&lt;/SPAN&gt;&lt;SPAN lang=en-us&gt;IDispatch &lt;/SPAN&gt;&lt;SPAN lang=zh-cn&gt;接口需要实现4个函数，调用者只通过这4个函数，就能实现调用自动化组件中所有的函数。这4个函数功能如下：&lt;BR&gt;　&lt;/SPAN&gt; 
&lt;TABLE cellSpacing=1 width="100%" border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width="43%"&gt;HRESULT GetTypeInfoCount(&lt;BR&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;[out] UINT * pctinfo)&lt;/TD&gt;
&lt;TD width="57%"&gt;&lt;SPAN lang=zh-cn&gt;组件中提供几个类型库？当然一般都是一个啦。&lt;BR&gt;但如果你在一个组件中实现了多个 &lt;/SPAN&gt;IDispatch &lt;SPAN lang=zh-cn&gt;接口，那就不一定啦（注1）&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="43%"&gt;HRESULT GetTypeInfo(&lt;BR&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;[in] UINT iTInfo,&lt;BR&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;[in] LCID lcid,&lt;BR&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;[out] ITypeInfo ** ppTInfo)&lt;/TD&gt;
&lt;TD width="57%"&gt;&lt;SPAN lang=zh-cn&gt;调用者通过该函数取得他想要的类型库。&lt;BR&gt;幸好，在 99% 的情况下，我们都不用关心这两个函数的实现，因为 MFC/ATL 都帮我们完成了默认的一个实现，如果是自己完成函数代码，甚至可以直接返回 E_NOTIMPL 表示没有实现。（注2）&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="43%"&gt;HRESULT GetIDsOfNames(&lt;BR&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;[in] REFIID riid,&lt;BR&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;[in,size_is(cNames)] LPOLESTR * rgszNames,&lt;SPAN lang=zh-cn&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [&lt;/SPAN&gt;in] UINT cNames,&lt;BR&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;[in] LCID lcid,&lt;BR&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;[out,size_is(cNames)] DISPID * rgDispId)&lt;/TD&gt;
&lt;TD width="57%"&gt;&lt;SPAN lang=zh-cn&gt;根据函数名称取得函数序号，为调用 &lt;/SPAN&gt;Invoke()&lt;SPAN lang=zh-cn&gt; 做准备。&lt;BR&gt;所谓函数序号，大家去观察双接口 IDL 文件和 MFC 的 ODL 文件，每一个函数和属性都会有&lt;/SPAN&gt; [id(&lt;SPAN lang=zh-cn&gt;序号&lt;/SPAN&gt;)....] &lt;SPAN lang=zh-cn&gt;这样的描述。&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="43%"&gt;HRESULT Invoke(&lt;BR&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;[in] DISPID dispIdMember,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [in] REFIID riid,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [in] LCID lcid,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [in] WORD wFlags,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [in,out] DISPPARAMS * pDispParams,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [out] VARIANT * pVarResult,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [out] EXCEPINFO * pExcepInfo,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [out] UINT * puArgErr)&lt;/TD&gt;
&lt;TD width="57%"&gt;&lt;SPAN lang=zh-cn&gt;根据序号，执行函数。&lt;BR&gt;使用 MFC/ATL 写的组件程序，我们也不必关心这个函数的实现。如果是自己写代码，则该函数类似如下实现：&lt;BR&gt;&lt;/SPAN&gt;switch(dispIdMember)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; case 1: .....; break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; case 2: .....; break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ....&lt;BR&gt;}&lt;BR&gt;&lt;SPAN lang=zh-cn&gt;其实，就是根据序号进行分支调用啦。&lt;/SPAN&gt;(&lt;SPAN lang=zh-cn&gt;注3&lt;/SPAN&gt;)&lt;BR&gt;　&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 从 &lt;/SPAN&gt;Invoke() &lt;SPAN lang=zh-cn&gt;函数的实现就可以看出，使用&lt;/SPAN&gt; IDispatch &lt;SPAN lang=zh-cn&gt;接口的程序，其执行效率是比较低的。ATL 从效率出发，实现了一种叫&amp;#8220;双接口&lt;/SPAN&gt;(dual)&lt;SPAN lang=zh-cn&gt;&amp;#8221;的接口模式。下面我们来看看，到底什么是双接口：&lt;BR&gt;&lt;BR&gt;&lt;IMG height=272 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut11pic1.jpg" width=589 border=0&gt;&lt;BR&gt;图一、双接口&lt;/SPAN&gt;(dual)&lt;SPAN lang=zh-cn&gt; 结构示意图&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 从上图中可以看出，所谓双接口，其实是在一个 VTAB 的虚函数表中容纳了三个接口（因为任何接口都是从 &lt;/SPAN&gt;IUnknown &lt;SPAN lang=zh-cn&gt;派生的，所以就不强调&lt;/SPAN&gt; IUnknown &lt;SPAN lang=zh-cn&gt;了，叫做双接口）。我们如果从任意一个接口中调用&lt;/SPAN&gt; QueryInterface()&lt;SPAN lang=zh-cn&gt;得到另外的接口指针的话，其实，得到的指针地址都是同一个。双接口有什么好处那？答：好呀，多好呀，特别好呀&lt;/SPAN&gt;......&lt;SPAN lang=zh-cn&gt;&lt;BR&gt;　&lt;/SPAN&gt; 
&lt;TABLE cellSpacing=1 width="100%" border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD align=middle width="22%"&gt;&lt;B&gt;&lt;SPAN lang=zh-cn&gt;使用方式&lt;/SPAN&gt;&lt;/B&gt;&lt;/TD&gt;
&lt;TD align=middle width="44%"&gt;&lt;B&gt;&lt;SPAN lang=zh-cn&gt;因为&lt;/SPAN&gt;&lt;/B&gt;&lt;/TD&gt;
&lt;TD align=middle width="34%"&gt;&lt;B&gt;&lt;SPAN lang=zh-cn&gt;所以&lt;/SPAN&gt;&lt;/B&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="22%"&gt;&lt;SPAN lang=zh-cn&gt;脚本语言使用组件&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="44%"&gt;&lt;SPAN lang=zh-cn&gt;解释器只认识&lt;/SPAN&gt; IDispatch&lt;SPAN lang=zh-cn&gt; 接口&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="34%"&gt;&lt;SPAN lang=zh-cn&gt;可以调用，但执行效率最低&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="22%"&gt;&lt;SPAN lang=zh-cn&gt;编译型语言使用组件&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="44%"&gt;&lt;SPAN lang=zh-cn&gt;它认识 &lt;/SPAN&gt;IDispatch&lt;SPAN lang=zh-cn&gt; 接口&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="34%"&gt;&lt;SPAN lang=zh-cn&gt;可以调用，执行效率比较低&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="22%"&gt;&lt;SPAN lang=zh-cn&gt;编译型语言使用组件&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="44%"&gt;&lt;SPAN lang=zh-cn&gt;它装载类型库后，就认识了 &lt;/SPAN&gt;Ixxx &lt;SPAN lang=zh-cn&gt;接口&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="34%"&gt;&lt;SPAN lang=zh-cn&gt;可以直接调用 &lt;/SPAN&gt;Ixxx &lt;SPAN lang=zh-cn&gt;函数，效率最高啦&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="22%" rowSpan=5&gt;
&lt;P align=center&gt;&lt;B&gt;&lt;SPAN lang=zh-cn&gt;结论&lt;/SPAN&gt;&lt;/B&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD width="78%" colSpan=2&gt;
&lt;P align=center&gt;&lt;SPAN lang=zh-cn&gt;双接口，既满足脚本语言的使用方便性，又满足编译型语言的使用高效性。&lt;BR&gt;于是，我们写的所有的 COM 组件接口，都用双接口实现吗？&lt;BR&gt;错！否！NO！&lt;BR&gt;如果不是明确非要支持脚本的调用，则最好不要使用双接口，因为：&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="78%" colSpan=2&gt;&lt;I&gt;&lt;SPAN lang=zh-cn&gt;如果所有函数都放在一个双接口中，那么层次、结构、分类不清&lt;/SPAN&gt;&lt;/I&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="78%" colSpan=2&gt;&lt;I&gt;&lt;SPAN lang=zh-cn&gt;如果使用多个双接口，则会产生其它问题（注4）&lt;/SPAN&gt;&lt;/I&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="78%" colSpan=2&gt;&lt;I&gt;&lt;SPAN lang=zh-cn&gt;双接口、&lt;/SPAN&gt;IDispatch&lt;SPAN lang=zh-cn&gt;接口只支持自动化的参数类型，使用受到限制，某些情况下很不方便喽&lt;/SPAN&gt;&lt;/I&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="78%" colSpan=2&gt;&lt;I&gt;&lt;SPAN lang=zh-cn&gt;还有很多弊病呦，不过现在我想不起来喽&lt;/SPAN&gt;......&lt;/I&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P&gt;&lt;SPAN lang=zh-cn&gt;&lt;B&gt;三、使用方法&lt;/B&gt;&lt;BR&gt;&lt;/SPAN&gt;&lt;SPAN lang=en-us&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN lang=zh-cn&gt;如果你的开发环境是 &lt;/SPAN&gt;vc6.0&lt;SPAN lang=zh-cn&gt;，那么我们使用&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1506"&gt;第九回&lt;/A&gt;中的&lt;/SPAN&gt;Simple6&lt;SPAN lang=zh-cn&gt;组件为例，&lt;A href="http://www.vckbase.com/code/downcode.asp?id=2734"&gt;快去下载呀&lt;/A&gt;&lt;/SPAN&gt;......&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;SPAN lang=zh-cn&gt;如果你的开发环境是 &lt;/SPAN&gt;vc.net 2003&lt;SPAN lang=zh-cn&gt;，那么用&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1507"&gt;第十回&lt;/A&gt;中的&lt;/SPAN&gt;Simple8&lt;SPAN lang=zh-cn&gt;组件为例，&lt;A href="http://www.vckbase.com/code/downcode.asp?id=2735"&gt;快去下载呀&lt;/A&gt;&lt;/SPAN&gt;......&lt;BR&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 嘿嘿，其实不下载也没有关系，因为你只要下载本回的示例程序，里面已经包含了所需的组件。但使用前不要忘了去注册呀：&lt;/SPAN&gt;regsvr32.exe simple6.dll &lt;SPAN lang=zh-cn&gt;或 &lt;/SPAN&gt;regsvr32.exe simple8.dll &lt;SPAN lang=zh-cn&gt;（注意别忘了输入组件的安装目录）。注册成功后，就可以使用了，使用方法有：&lt;BR&gt;　&lt;/SPAN&gt; 
&lt;TABLE cellSpacing=1 width="100%" border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD align=middle width="10%"&gt;&lt;B&gt;&lt;SPAN lang=zh-cn&gt;示例程序&lt;/SPAN&gt;&lt;/B&gt;&lt;/TD&gt;
&lt;TD align=middle width="31%"&gt;&lt;B&gt;&lt;SPAN lang=zh-cn&gt;自动化组件的使用方式&lt;/SPAN&gt;&lt;/B&gt;&lt;/TD&gt;
&lt;TD align=middle width="58%"&gt;&lt;B&gt;&lt;SPAN lang=zh-cn&gt;简要说明&lt;/SPAN&gt;&lt;/B&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="10%"&gt;&lt;SPAN lang=zh-cn&gt;示例0&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="31%"&gt;&lt;SPAN lang=zh-cn&gt;在脚本中调用&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="58%"&gt;&lt;SPAN lang=zh-cn&gt;在&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1506"&gt;第九回&lt;/A&gt;/&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1507"&gt;第十回&lt;/A&gt;中，已经做了介绍&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="10%"&gt;&lt;SPAN lang=zh-cn&gt;示例1&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="31%"&gt;&lt;SPAN lang=zh-cn&gt;使用 API 方式调用&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="58%"&gt;&lt;SPAN lang=zh-cn&gt;揭示&lt;/SPAN&gt; IDispatch &lt;SPAN lang=zh-cn&gt;的调用原理，但傻子才去这么使用那，会累死了&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="10%"&gt;&lt;SPAN lang=zh-cn&gt;示例2&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="31%"&gt;&lt;SPAN lang=zh-cn&gt;使用 &lt;/SPAN&gt;&lt;FONT face=verdana,arial,helvetica&gt;CComDispatchDriver&lt;SPAN lang=zh-cn&gt; 的智能指针包装类&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD width="58%"&gt;&lt;SPAN lang=zh-cn&gt;比直接使用 API 方式要简单多啦，这个不错！&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="10%"&gt;&lt;SPAN lang=zh-cn&gt;示例3&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="31%"&gt;&lt;SPAN lang=zh-cn&gt;使用 MFC 装载类型库的包装方式&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="58%"&gt;&lt;SPAN lang=zh-cn&gt;简单！好用！常用！但它本质上是使用 &lt;/SPAN&gt;IDispatch&lt;SPAN lang=zh-cn&gt; 接口，所以执行效率稍差&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="10%"&gt;&lt;SPAN lang=zh-cn&gt;示例4&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="31%"&gt;&lt;SPAN lang=zh-cn&gt;使用 &lt;/SPAN&gt;#import &lt;SPAN lang=zh-cn&gt;方式加载类型库方式&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD width="58%"&gt;#import &lt;SPAN lang=zh-cn&gt;方式使用组件，咱们在&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1500"&gt;第七回&lt;/A&gt;中讲过啦。常用！对双接口组件，直接调用自定义接口函数，不再经过 &lt;/SPAN&gt;IDispatch&lt;SPAN lang=zh-cn&gt;，因此执行效率最高啦&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD width="10%"&gt;&lt;SPAN lang=zh-cn&gt;示例&lt;/SPAN&gt;x&lt;/TD&gt;
&lt;TD width="31%"&gt;vb&lt;SPAN lang=zh-cn&gt;、&lt;/SPAN&gt;java&lt;SPAN lang=zh-cn&gt;、&lt;/SPAN&gt;c#&lt;SPAN lang=zh-cn&gt;、&lt;/SPAN&gt;bcb&lt;SPAN lang=zh-cn&gt;、&lt;/SPAN&gt;delphi.......&lt;/TD&gt;
&lt;TD width="58%"&gt;&lt;SPAN lang=zh-cn&gt;反正我不会，自己去请教高人去吧 &lt;/SPAN&gt;:-(&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P&gt;示例一、IDispatch 调用原理篇&lt;/P&gt;&lt;PRE&gt;void demo()
{
	::CoInitialize( NULL );		// COM 初始化

	CLSID clsid;				// 通过 ProgID 得到 CLSID
	HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &amp;amp;clsid );
	ASSERT( SUCCEEDED( hr ) );	// 如果失败，说明没有注册组件

	IDispatch * pDisp = NULL;	// 由 CLSID 启动组件，并得到 IDispatch 指针
	hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&amp;amp;pDisp );
	ASSERT( SUCCEEDED( hr ) );	// 如果失败，说明没有初始化 COM

	LPOLESTR pwFunName = L"Add";	// 准备取得 Add 函数的序号 DispID
	DISPID dispID;					// 取得的序号，准备保存到这里
	hr = pDisp-&amp;gt;GetIDsOfNames(		// 根据函数名，取得序号的函数
		IID_NULL,
		&amp;amp;pwFunName,					// 函数名称的数组
		1,							// 函数名称数组中的元素个数
		LOCALE_SYSTEM_DEFAULT,		// 使用系统默认的语言环境
		&amp;amp;dispID );					// 返回值
	ASSERT( SUCCEEDED( hr ) );		// 如果失败，说明组件根本就没有 ADD 函数

	VARIANTARG v[2];					// 调用 Add(1,2) 函数所需要的参数
	v[0].vt = VT_I4;	v[0].lVal = 2;	// 第二个参数，整数2
	v[1].vt = VT_I4;	v[1].lVal = 1;	// 第一个参数，整数1

	DISPPARAMS dispParams = { v, NULL, 2, 0 };	// 把参数包装在这个结构中
	VARIANT vResult;			// 函数返回的计算结果

	hr = pDisp-&amp;gt;Invoke(			// 调用函数
		dispID,					// 函数由 dispID 指定
		IID_NULL,
		LOCALE_SYSTEM_DEFAULT,	// 使用系统默认的语言环境
		DISPATCH_METHOD,		// 调用的是方法，不是属性
		&amp;amp;dispParams,			// 参数
		&amp;amp;vResult,				// 返回值
		NULL,					// 不考虑异常处理
		NULL);					// 不考虑错误处理
	ASSERT( SUCCEEDED( hr ) );	// 如果失败，说明参数传递错误

	CString str;			// 显示一下结果
	str.Format("1 + 2 = %d", vResult.lVal );
	AfxMessageBox( str );

	pDisp-&amp;gt;Release();		// 释放接口指针
	::CoUninitialize();		// 释放 COM
}&lt;/PRE&gt;示例二、CComDispatchDriver 智能指针包装类的使用方法&lt;PRE&gt;void demo()
{
	// 已经进行过了 COM 初始化

	CLSID clsid;				// 通过 ProgID 取得组件的 CLSID
	HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &amp;amp;clsid );
	ASSERT( SUCCEEDED( hr ) );	// 如果失败，说明没有注册组件

	CComPtr &amp;lt; IUnknown &amp;gt; spUnk;	// 由 CLSID 启动组件，并取得 IUnknown 指针
	hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IUnknown, (LPVOID *)&amp;amp;spUnk );
	ASSERT( SUCCEEDED( hr ) );

	CComDispatchDriver spDisp( spUnk );	// 构造只能指针
	CComVariant v1(1), v2(2), vResult;	// 参数
	hr = spDisp.Invoke2(	// 调用2个参数的函数
		L"Add",				// 函数名是 Add
		&amp;amp;v1,				// 第一个参数，值为整数1
		&amp;amp;v2,				// 第二个参数，值为整数2
		&amp;amp;vResult);			// 返回值
	ASSERT( SUCCEEDED( hr ) );	// 如果失败，说明或者没有 ADD 函数，或者参数错误

	CString str;			// 显示一下结果
	str.Format("1 + 2 = %d", vResult.lVal );
	AfxMessageBox( str );
}&lt;/PRE&gt;示例程序中使用了 &lt;SPAN lang=en-us&gt;Invoke2()&lt;/SPAN&gt;函数，其实你根据不同的函数，还可以使用 &lt;SPAN lang=en-us&gt;Invoke&lt;/SPAN&gt;0&lt;SPAN lang=en-us&gt;()&lt;/SPAN&gt;、&lt;SPAN lang=en-us&gt;Invoke&lt;/SPAN&gt;1&lt;SPAN lang=en-us&gt;()&lt;/SPAN&gt;、&lt;SPAN lang=en-us&gt;InvokeN()&lt;/SPAN&gt;、&lt;SPAN lang=en-us&gt;PutProperty()&lt;/SPAN&gt;、&lt;SPAN lang=en-us&gt;GetProperty()......&lt;/SPAN&gt;等等等，的确很方便。&lt;BR&gt;&lt;BR&gt;示例三、加载类型库，产生包装类来使用&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个方法使用更简单一些，如果你观察 MFC 帮你产生的包装类的实现，你就会发现，其实它调用的是 &lt;SPAN lang=en-us&gt;IDispatch &lt;/SPAN&gt;接口函数。使用 &lt;SPAN lang=en-us&gt;vc6.0 &lt;/SPAN&gt;的朋友，步骤如下：&lt;BR&gt;1、建立一个 MFC 的应用程序&lt;BR&gt;2、开启&lt;SPAN lang=en-us&gt; ClassWizard&lt;/SPAN&gt;，执行 &lt;SPAN lang=en-us&gt;Add Class&lt;/SPAN&gt;，选择 &lt;SPAN lang=en-us&gt;From a type library&lt;BR&gt;&lt;BR&gt;&lt;IMG height=201 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut11pic2.jpg" width=747 border=0&gt;&lt;BR&gt;&lt;/SPAN&gt;图二、加载类型库&lt;BR&gt;&lt;BR&gt;3、然后找到你要使用的组件文件 &lt;SPAN lang=en-us&gt;simple6.dll(tlb&lt;/SPAN&gt; 文件也可以&lt;SPAN lang=en-us&gt;)&lt;/SPAN&gt;，选择接口后确认&lt;BR&gt;&lt;BR&gt;&lt;IMG height=429 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut11pic3.jpg" width=444 border=0&gt;&lt;BR&gt;图三、选择类型库中需要包装的接口&lt;BR&gt;&lt;BR&gt;4、在适当的地方输入调用代码&lt;PRE&gt;#include "simple6.h"	// 包装类的头文件

void demo() 
{
	// 已经进行过了 COM 初始化

	IDispSimple spDisp;		// 包装类的对象

	spDisp.CreateDispatch( _T("Simple6.DispSimple.1") )	//启动组件
	spDisp.xxx(...);	// 调用函数

	spDisp.ReleaseDispatch();	// 释放接口
}&lt;/PRE&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 使用 &lt;SPAN lang=en-us&gt;vc.net &lt;/SPAN&gt;的朋友，步骤如下：&lt;BR&gt;1、建立一个 MFC 的应用程序&lt;BR&gt;2、执行菜单&amp;#8220;添加&lt;SPAN lang=en-us&gt;\&lt;/SPAN&gt;添加类&amp;#8221;，选择 MFC 分类中的&amp;#8220;类型库中的MFC类&amp;#8221;&lt;BR&gt;&lt;BR&gt;&lt;IMG height=418 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut11pic4.jpg" width=531 border=0&gt;&lt;BR&gt;图四、添加类型库中的MFC类&lt;BR&gt;&lt;BR&gt;3、选择组件文件&lt;SPAN lang=en-us&gt; simple8.dll(&lt;/SPAN&gt;或 &lt;SPAN lang=en-us&gt;tlb &lt;/SPAN&gt;文件&lt;SPAN lang=en-us&gt;)&lt;/SPAN&gt;，并选择需要包装的接口&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase44/images/comtut11pic5.jpg" width=615 border=0&gt;&lt;BR&gt;图五、选择文件和接口&lt;BR&gt;&lt;BR&gt;4、在适当的位置输入调用代码&lt;PRE&gt;#include "CDispSimple.h"	// 包装类的头文件

void demo()
{
	// 已经进行过了 COM 初始化

	CDispSimple spDisp;	// 包装类的对象
	spDisp.CreateDispatch( _T("Simple8.DispSimple.1") )	// 启动组件
	spDisp.xxx(...);	// 调用函数

	spDisp.ReleaseDispatch();	// 释放接口
}&lt;/PRE&gt;示例四、使用 &lt;SPAN lang=en-us&gt;#import &lt;/SPAN&gt;方式调用组件&lt;BR&gt;&lt;SPAN lang=en-us&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; #import &lt;/SPAN&gt;方式在&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1500"&gt;第七回&lt;/A&gt;中已经作过介绍，这里就不多罗嗦了。大家下载本回的示例程序后，自己去看吧。并且一定要掌握这个方法，因为它的运行效率是最快的呀。&lt;BR&gt;&lt;B&gt;&lt;BR&gt;四、小结&lt;BR&gt;&lt;/B&gt;&lt;SPAN lang=zh-cn&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 留作业啦。在我们以前所实现的所有组件程序中，只添加了接口方法（函数），而没有添加接口属性（变量），你自己练习一下吧，很简单的，然后写个程序调用看看。其实对于 VC 来说，调用属性和调用方法没有太大的区别（&lt;/SPAN&gt;&lt;SPAN lang=en-us&gt;vc &lt;/SPAN&gt;把属性包装为&lt;SPAN lang=en-us&gt; GetXXX()/PutXXX()&lt;/SPAN&gt;或&lt;SPAN lang=en-us&gt;getXXX()/putXXX()&lt;/SPAN&gt;的函数方式&lt;SPAN lang=zh-cn&gt;），但在另外一些语言中（比如脚本语言）则更方便，设置属性值是：&lt;/SPAN&gt;对象&lt;SPAN lang=en-us&gt;.&lt;/SPAN&gt;属性 = 变量或常量，获取属性值是：变量 &lt;SPAN lang=en-us&gt;= &lt;/SPAN&gt;对象&lt;SPAN lang=en-us&gt;.&lt;/SPAN&gt;属性。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 本回书至此做一了断，更多组件设计和使用的知识，且听下回分解&lt;SPAN lang=en-us&gt;......&lt;/SPAN&gt;&lt;SPAN lang=zh-cn&gt; 
&lt;HR&gt;
注1：多个自动化接口的实现方法，我们以后再说。&lt;BR&gt;注2：将来介绍 ITypeLib::GetTypeInfo&lt;/SPAN&gt;()&lt;SPAN lang=zh-cn&gt; 的时候，大家再回味 &lt;/SPAN&gt;IDispatch::GetTypeInfo()&lt;SPAN lang=zh-cn&gt;吧。&lt;BR&gt;注3：在后面介绍&amp;#8220;事件&amp;#8221;的时候，我们会自己真正去实现一个&lt;/SPAN&gt; IDispatch::Invoke() &lt;SPAN lang=zh-cn&gt;函数。&lt;BR&gt;注4：介绍多个双接口实现的时候，会谈到这个问题。&lt;BR&gt;&lt;/SPAN&gt;&lt;img src ="http://blog.vckbase.com/teacheryang/aggbug/11558.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>杨老师的茅屋</dc:creator><title>《COM 组件设计与应用（十）--IDispatch for vc.net》(原文发表在vckbase上)</title><link>http://blog.vckbase.com/teacheryang/archive/2005/08/26/11557.html</link><pubDate>Fri, 26 Aug 2005 14:11:00 GMT</pubDate><guid>http://blog.vckbase.com/teacheryang/archive/2005/08/26/11557.html</guid><wfw:comment>http://blog.vckbase.com/teacheryang/comments/11557.html</wfw:comment><comments>http://blog.vckbase.com/teacheryang/archive/2005/08/26/11557.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://blog.vckbase.com/teacheryang/comments/commentRss/11557.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/teacheryang/services/trackbacks/11557.html</trackback:ping><description>&lt;P align=center&gt;&lt;B&gt;COM组件设计与应用（十）&lt;BR&gt;IDispatch 接口 for vc.net&lt;BR&gt;&lt;/B&gt;&lt;BR&gt;作者：&lt;A href="mailto:good_yf@sina.com"&gt;杨老师&lt;/A&gt;&lt;/P&gt;&lt;A href="http://www.vckbase.com/code/downcode.asp?id=2735"&gt;下载源代码&lt;/A&gt; 
&lt;P&gt;&lt;B&gt;一、前言&lt;/B&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 终于写到了第十回，我也一直期盼着写这回的内容耶，为啥呢？因为自动化(automation)是非常常用、非常有用、非常精彩的一个 COM 功能。由于 WORD、EXCEL 等 OFFICE 软件提供了&amp;#8220;宏&amp;#8221;的功能，就连我们使用的VC开发环境也提供了&amp;#8220;宏&amp;#8221;功能，更由于 HTML、ASP、JSP 等都要依靠脚本(Script)的支持，更体现出了自动化接口的重要性。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果你使用 vc6.0 的开发环境，请阅读前一回。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果你使用 vc.net 2003，请继续...... 
&lt;P&gt;&lt;B&gt;二、IDispatch接口&lt;BR&gt;&lt;/B&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果是编译型语言，那么我们可以让编译器在编译的时候装载类型库，也就是装载接口的描述。在&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1500"&gt;第七回&lt;/A&gt;文章当中，我们分别使用了 #include 方法和 #import 方法来实现的。装载了类型库后，编译器就知道应该如何编译接口函数的调用了---这叫&amp;#8220;前绑定&amp;#8221;。但是，如果想在脚本语言中使用组件，问题就大了，因为脚本语言是解释执行的，它执行的时候不会知道具体的函数地址，怎么办？自动化接口就为此诞生了---&amp;#8220;后绑定&amp;#8221;。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 自动化组件，其实就是实现了 IDispatch 接口的组件。IDispatch 接口有4个函数，解释语言的执行器就通过这仅有的4个函数来执行组件所提供的功能。IDispatch 接口用 IDL 形式说明如下：(注1)&lt;/P&gt;&lt;PRE&gt;[
    object,
    uuid(00020400-0000-0000-C000-000000000046),	// IDispatch 接口的 IID = IID_IDispatch
    pointer_default(unique)
]

interface IDispatch : IUnknown
{
    typedef [unique] IDispatch * LPDISPATCH;	// 转定义 IDispatch * 为 LPDISPATCH

    HRESULT GetTypeInfoCount([out] UINT * pctinfo);	// 有关类型库的这两个函数，咱们以后再说
    HRESULT GetTypeInfo([in] UINT iTInfo,[in] LCID lcid,[out] ITypeInfo ** ppTInfo);

    HRESULT GetIDsOfNames(	// 根据函数名字，取得函数序号(DISPID)
                [in] REFIID riid,
                [in, size_is(cNames)] LPOLESTR * rgszNames,
                [in] UINT cNames,
                [in] LCID lcid,
                [out, size_is(cNames)] DISPID * rgDispId
            );

    [local]		// 本地版函数
    HRESULT Invoke(	// 根据函数序号，解释执行函数功能
                [in] DISPID dispIdMember,
                [in] REFIID riid,
                [in] LCID lcid,
                [in] WORD wFlags,
                [in, out] DISPPARAMS * pDispParams,
                [out] VARIANT * pVarResult,
                [out] EXCEPINFO * pExcepInfo,
                [out] UINT * puArgErr
            );

    [call_as(Invoke)]	// 远程版函数
    HRESULT RemoteInvoke(
                [in] DISPID dispIdMember,
                [in] REFIID riid,
                [in] LCID lcid,
                [in] DWORD dwFlags,
                [in] DISPPARAMS * pDispParams,
                [out] VARIANT * pVarResult,
                [out] EXCEPINFO * pExcepInfo,
                [out] UINT * pArgErr,
                [in] UINT cVarRef,
                [in, size_is(cVarRef)] UINT * rgVarRefIdx, 
                [in, out, size_is(cVarRef)] VARIANTARG * rgVarRef
            );
}
&lt;/PRE&gt;以上 IDispatch 接口函数的讲解，我们留到后回中进行介绍。如何在组件程序中实现这些函数那？还好，还好，就象 IUnknown 一样，MFC 和 ATL 都帮我们已经完成了。本回我们着重介绍组件的编写，下回则介绍组件的调用方法。&lt;BR&gt;&lt;BR&gt;&lt;B&gt;三、用 MFC 实现自动化组件&lt;/B&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我写的这整个系列文章---《COM 组件设计与应用》，多是用 ATL 写组件程序，但由于自动化非常有用，在后续的文章中，还要给大家介绍组件的&amp;#8220;事件&amp;#8221;功能，还要介绍如何在 MFC 的程序中象 WORD 一样支持&amp;#8220;宏&amp;#8221;的功能。这些都要用到 MFC，所以就给读者唠一唠啦:-)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-1：建立一个解决方案&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-2：建立一个 MFC DLL 项目，项目名称为&amp;#8220;Simple7&amp;#8221;&lt;BR&gt;&lt;IMG height=387 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic01.jpg" width=531 border=0&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-3：一定要选择附加功能中的&amp;#8220;自动化&amp;#8221;，切记！切记！&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic02.jpg" width=615 border=0&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-4：添加新类&lt;BR&gt;&lt;BR&gt;&lt;IMG height=342 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic03.jpg" width=351 border=0&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-5：在新建类中支持自动化&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic04.jpg" width=615 border=0&gt;&lt;BR&gt;&lt;BR&gt;&lt;B&gt;&lt;I&gt;类名&lt;/I&gt;&lt;/B&gt; 你随便写个类名子啦&lt;BR&gt;&lt;B&gt;&lt;I&gt;基类&lt;/I&gt;&lt;/B&gt; 一定要从 CComTarget 派生呀，只有它才提供了 IDispatch 的支持&lt;BR&gt;&lt;B&gt;&lt;I&gt;自动化 - 无&lt;/I&gt;&lt;/B&gt; 表示不支持自动化，你要选择了它，那就白干啦&lt;BR&gt;&lt;I&gt;&lt;B&gt;自动化 - 自动化&lt;/B&gt;&lt;/I&gt; 支持自动化，但不能被直接实例化。后面在讲解多个 IDispatch 的时候就用到它了，现在先不要着急。&lt;BR&gt;&lt;I&gt;&lt;B&gt;自动化 - 可按类型ID创建&lt;/B&gt;&lt;/I&gt; 一定要选择这个项目，这样我们在后面的调用中，VB就能够CreateObject(),VC就能够CreateDispatch()对组件对象实例化了。注意一点，这个 ID 其实就是组件的 ProgID 啦。&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-6：选择接口，添加函数&lt;BR&gt;&lt;BR&gt;&lt;IMG height=408 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic05.jpg" width=422 border=0&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-7：添加函数。我们要写一个整数加法函数Add()。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic06.jpg" width=615 border=0&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-8：再增加一个转换字符串大小写的函数 Upper()。&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic07.jpg" width=615 border=0&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-9：好了，下面开始输入程序代码： &lt;PRE&gt;LONG CDispSimple::Add(LONG n1, LONG n2)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	return n1 + n2;
}

BSTR CDispSimple::Upper(LPCTSTR str)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	CString strResult(str);
	strResult.MakeUpper();

	return strResult.AllocSysString();
}&lt;/PRE&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-10：编译注册&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果上面的操作由于疏忽而发生了错误，那么你可以手工进行改正。&lt;BR&gt;其一、你可以打开 IDL 文件进行修改，修改时要特别小心函数的声明中，有一个[id(n)] 的函数序号，可不要乱了；&lt;BR&gt;其二、同步修改 H/CPP 中的函数声明和函数体；&lt;BR&gt;其三、在CPP文件中，根据情况也要修改 BEGIN_DISPATCH_MAP/END_DISPATCH_MAP()函数影射宏。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 正确编译后，vc.net 2003 比 vc6.0 要聪明多了，它会自动注册组件。如果复制到其它计算机上，你也需要手工执行 regsvr32.exe 进行注册。&lt;BR&gt;&lt;BR&gt;&lt;B&gt;四、用 ATL 实现双接口组件&lt;/B&gt;(操作方法和步骤，请参考&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1497"&gt;《&lt;/A&gt;&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1498"&gt;COM 组件设计与应用(六)》&lt;/A&gt;)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-1：建立一个 ATL 项目，项目名称为&amp;#8220;Simple8&amp;#8221;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-2：选择 DLL 类型、非属性方式、不要选择任何附加选项&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-3：添加新类，选择ATL 的简单对象&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-4：输入简称和选项，选项按默认进行，也就是双重接口方式(注2)&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic08.jpg" width=615 border=0&gt;&lt;BR&gt;&lt;BR&gt;&lt;IMG height=449 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic09.jpg" width=615 border=0&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-5：增加函数。选择接口、鼠标右键菜单、添加方法...&lt;BR&gt;Add([in] VARIANT v1, [in] VARIANT v2, [out, retval] VARIANT * pVal);&lt;BR&gt;Upper([in] BSTR str, [out,retval] BSTR * pVal);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 关于Add()函数，你依然可以使用 Add([in] long n1, [in] long n2, [out,retval] long * pVal) 方式。但这次我们没有使用 long ，而是使用了 VARIANT 做参数和返回值。这里我先卖个关子，往下看，就知道使用 VARIANT 的精彩之处了。&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-6：完成代码&lt;PRE&gt;STDMETHODIMP CDispSimple::Add(VARIANT v1, VARIANT v2, VARIANT *pVal)
{
	::VariantInit( pVal );	// 永远初始化返回值是个好习惯

	CComVariant v_1( v1 );
	CComVariant v_2( v2 );

	if((v1.vt &amp;amp; VT_I4) &amp;amp;&amp;amp; (v2.vt &amp;amp; VT_I4) )	// 如果都是整数类型
	{	// 这里比较没有使用 == ，而使用了运算符 &amp;amp; ，你知道这是为什么吗？
		v_1.ChangeType( VT_I4 );	// 转换为整数
		v_2.ChangeType( VT_I4 );	// 转换为整数

		pVal-&amp;gt;vt = VT_I4;
		pVal-&amp;gt;lVal = v_1.lVal + v_2.lVal;	// 加法
	}
	else
	{
		v_1.ChangeType( VT_BSTR );	// 转换为字符串
		v_2.ChangeType( VT_BSTR );	// 转换为字符串

		CComBSTR bstr( v_1.bstrVal );
		bstr.AppendBSTR( v_2.bstrVal );	// 字符串连接

		pVal-&amp;gt;vt = VT_BSTR;
		pVal-&amp;gt;bstrVal = bstr.Detach();
	}
	return S_OK;
}

STDMETHODIMP CDispSimple::Upper(BSTR str, BSTR *pVal)
{
	*pVal = NULL;	// 永远初始化返回值是个好习惯

	CComBSTR s(str);
	s.ToUpper();	// 转换为大写

	*pVal = s.Copy();

	return S_OK;
}
&lt;/PRE&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 刚才卖的关子，现在开始揭密了......加法函数Add()不使用long类型，而使用VARIANT的好处是：函数内部动态判断参数类型，如果是整数则进行整数加法，如果是字符串，则进行字符串加法（字符串加法就是字符串连接哈）。也就是说，如果参数是VARIANT，那么我们就可以实现函数的可变参数类型呀。怪怪个咙，真爽！&lt;BR&gt;&lt;BR&gt;&lt;B&gt;五、脚本中调用举例&lt;/B&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 打开&amp;#8220;记事本&amp;#8221;程序，输入脚本程序，保存为 xxx.vbs 文件。然后在资源管理器里就可以双击运行啦。&lt;BR&gt;&lt;IMG height=187 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic10.jpg" width=511 border=0&gt;&lt;BR&gt;如果你有能力，也可以用 JScript 书写上面的程序，然后保存为 xxx.js 文件，同样也可以在资源管理器里运行。另外需要说明的一点是，脚本程序文件的图标(win 2000下)是&lt;IMG height=38 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic11.jpg" width=35 border=0&gt;，如果你不是这样的（有一个软件叫&amp;#8220;XX 解霸&amp;#8221;。写这款软件的人水平太低，它居然使用 .vbs 的扩展名文件作为它的数据流文件，破坏了系统默认的文件类型影射模式，咳......），那么需要重新设置，方法是：&lt;BR&gt;&lt;IMG height=434 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic12.jpg" width=386 border=0&gt;&lt;BR&gt;&lt;BR&gt;&lt;B&gt;六、WORD 中使用举例&lt;/B&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 6-1：录制一段宏程序&lt;BR&gt;&lt;BR&gt;&lt;IMG height=205 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic13.jpg" width=652 border=0&gt;&lt;BR&gt;&lt;BR&gt;&lt;IMG height=237 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic14.jpg" width=360 border=0&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 6-2：选择&amp;#8220;键盘&amp;#8221;，当然你也可以把这个&amp;#8220;宏&amp;#8221;程序放到&amp;#8220;工具栏&amp;#8221;上去。这里我们随便指定一个快捷键，比如Ctrl+Z&lt;BR&gt;&lt;BR&gt;&lt;IMG height=307 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic15.jpg" width=486 border=0&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 6-3：开始录制了，下面你随便输入点什么东东。然后点&amp;#8220;停止&amp;#8221;&lt;IMG height=49 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic16.jpg" width=62 border=0&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 6-4：接下来，我们执行菜单，选择这个刚刚录制的宏，然后编辑它&lt;BR&gt;&lt;BR&gt;&lt;IMG height=303 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic17.jpg" width=437 border=0&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 6-5：点&amp;#8220;编辑&amp;#8221;按钮，输入下面的程序：&lt;BR&gt;&lt;BR&gt;&lt;IMG height=187 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic18.jpg" width=373 border=0&gt;&lt;BR&gt;&lt;BR&gt;不做解释了，你如果会一点点 VB ，就能看懂这个东东哈。然后保存关闭 VBA 的编辑器(注4)。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 6-6：执行啦，执行啦，看看有什么效果呀......&lt;BR&gt;&lt;BR&gt;&lt;IMG height=258 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic19.jpg" width=346 border=0&gt;&lt;BR&gt;&lt;BR&gt;然后按快捷键Ctrl+Z &lt;BR&gt;&lt;BR&gt;&lt;IMG height=258 src="http://www.vckbase.com/document/journal/vckbase43/images/comp10pic20.jpg" width=346 border=0&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 你已经扩展了 MS WORD 的功能啦，嘿啦啦啦啦，嘿啦啦啦，天空出彩霞呀......我们只是举了一个简单的例子，其实这个例子并没有什么实际应用的意义，因为人家 WORD 本身就有大小写转换功能。但通过这个小例子，你可以体会出自动化组件的功能了，有够厉害吧？！&lt;BR&gt;&lt;BR&gt;&lt;B&gt;七、小结&lt;/B&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 没小结！嘿嘿......上当喽:-) 
&lt;HR&gt;
注1：以后我们描述接口函数，都采用 IDL 的形式了。&lt;BR&gt;注2：双接口，是支持 IDispatch 接口的一种特殊接口方式，后面马上就要讲啦&lt;BR&gt;注3：VBA 是专门开发 Office 的一种语言---Visual Basic for Application&lt;img src ="http://blog.vckbase.com/teacheryang/aggbug/11557.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>杨老师的茅屋</dc:creator><title>ATL 无窗口 ActiveX 控件中，得到容器窗口句柄的方法</title><link>http://blog.vckbase.com/teacheryang/archive/2005/08/05/10497.html</link><pubDate>Fri, 05 Aug 2005 00:55:00 GMT</pubDate><guid>http://blog.vckbase.com/teacheryang/archive/2005/08/05/10497.html</guid><wfw:comment>http://blog.vckbase.com/teacheryang/comments/10497.html</wfw:comment><comments>http://blog.vckbase.com/teacheryang/archive/2005/08/05/10497.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://blog.vckbase.com/teacheryang/comments/commentRss/10497.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/teacheryang/services/trackbacks/10497.html</trackback:ping><description>&lt;P&gt;ATL 无窗口 ActiveX 控件中，得到容器窗口句柄的方法：&lt;BR&gt;&lt;BR&gt;CComPtr &amp;lt; IOleClientSite &amp;gt; spClientSite;&lt;BR&gt;GetClientSite( &amp;amp;spClientSite );&lt;BR&gt;CComQIPtr &amp;lt; IOleInPlaceSite &amp;gt; spSite = spClientSite;&lt;/P&gt;
&lt;P&gt;HWND hwnd;&lt;BR&gt;spSite-&amp;gt;GetWindow( &amp;amp;hwnd );&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/teacheryang/aggbug/10497.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>杨老师的茅屋</dc:creator><title>《COM 组件设计与应用(九)》- IDispatch (vc6)原文发表在vckbase中</title><link>http://blog.vckbase.com/teacheryang/archive/2005/07/30/10269.html</link><pubDate>Fri, 29 Jul 2005 16:47:00 GMT</pubDate><guid>http://blog.vckbase.com/teacheryang/archive/2005/07/30/10269.html</guid><wfw:comment>http://blog.vckbase.com/teacheryang/comments/10269.html</wfw:comment><comments>http://blog.vckbase.com/teacheryang/archive/2005/07/30/10269.html#Feedback</comments><slash:comments>10</slash:comments><wfw:commentRss>http://blog.vckbase.com/teacheryang/comments/commentRss/10269.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/teacheryang/services/trackbacks/10269.html</trackback:ping><description>&lt;P align=center&gt;&lt;B&gt;COM组件设计与应用（九）&lt;BR&gt;IDispatch 接口 for vc6.0&lt;BR&gt;&lt;/B&gt;&lt;/P&gt;&lt;A href="http://www.vckbase.com/code/downcode.asp?id=2734"&gt;下载源代码&lt;/A&gt; 
&lt;P&gt;&lt;B&gt;一、前言&lt;/B&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 终于写到了第九回，我也一直期盼着写这回的内容耶，为啥呢？因为自动化(automation)是非常常用、非常有用、非常精彩的一个 COM 功能。由于 WORD、EXCEL 等 OFFICE 软件提供了&amp;#8220;宏&amp;#8221;的功能，就连我们使用的VC开发环境也提供了&amp;#8220;宏&amp;#8221;功能，更由于 HTML、ASP、JSP 等都要依靠脚本(Script)的支持，更体现出了自动化接口的重要性。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果你使用 vc6.0 的开发环境，请继续阅读。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果你使用 vc.net 2003，请阅读下一回。 
&lt;P&gt;&lt;B&gt;二、IDispatch接口&lt;BR&gt;&lt;/B&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果是编译型语言，那么我们可以让编译器在编译的时候装载类型库，也就是装载接口的描述。在&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1500"&gt;第七回&lt;/A&gt;文章当中，我们分别使用了 #include 方法和 #import 方法来实现的。装载了类型库后，编译器就知道应该如何编译接口函数的调用了---这叫&amp;#8220;前绑定&amp;#8221;。但是，如果想在脚本语言中使用组件，问题就大了，因为脚本语言是解释执行的，它执行的时候不会知道具体的函数地址，怎么办？自动化接口就为此诞生了---&amp;#8220;后绑定&amp;#8221;。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 自动化组件，其实就是实现了 IDispatch 接口的组件。IDispatch 接口有4个函数，解释语言的执行器就通过这仅有的4个函数来执行组件所提供的功能。IDispatch 接口用 IDL 形式说明如下：(注1)&lt;/P&gt;&lt;PRE&gt;[
    object,
    uuid(00020400-0000-0000-C000-000000000046),	// IDispatch 接口的 IID = IID_IDispatch
    pointer_default(unique)
]

interface IDispatch : IUnknown
{
    typedef [unique] IDispatch * LPDISPATCH;	// 转定义 IDispatch * 为 LPDISPATCH

    HRESULT GetTypeInfoCount([out] UINT * pctinfo);	// 有关类型库的这两个函数，咱们以后再说
    HRESULT GetTypeInfo([in] UINT iTInfo,[in] LCID lcid,[out] ITypeInfo ** ppTInfo);

    HRESULT GetIDsOfNames(	// 根据函数名字，取得函数序号(DISPID)
                [in] REFIID riid,
                [in, size_is(cNames)] LPOLESTR * rgszNames,
                [in] UINT cNames,
                [in] LCID lcid,
                [out, size_is(cNames)] DISPID * rgDispId
            );

    [local]		// 本地版函数
    HRESULT Invoke(	// 根据函数序号，解释执行函数功能
                [in] DISPID dispIdMember,
                [in] REFIID riid,
                [in] LCID lcid,
                [in] WORD wFlags,
                [in, out] DISPPARAMS * pDispParams,
                [out] VARIANT * pVarResult,
                [out] EXCEPINFO * pExcepInfo,
                [out] UINT * puArgErr
            );

    [call_as(Invoke)]	// 远程版函数
    HRESULT RemoteInvoke(
                [in] DISPID dispIdMember,
                [in] REFIID riid,
                [in] LCID lcid,
                [in] DWORD dwFlags,
                [in] DISPPARAMS * pDispParams,
                [out] VARIANT * pVarResult,
                [out] EXCEPINFO * pExcepInfo,
                [out] UINT * pArgErr,
                [in] UINT cVarRef,
                [in, size_is(cVarRef)] UINT * rgVarRefIdx, 
                [in, out, size_is(cVarRef)] VARIANTARG * rgVarRef
            );
}
&lt;/PRE&gt;以上 IDispatch 接口函数的讲解，我们留到后回中进行介绍。如何在组件程序中实现这些函数那？还好，还好，就象 IUnknown 一样，MFC 和 ATL 都帮我们已经完成了。本回我们着重介绍组件的编写，下回则介绍组件的调用方法。&lt;BR&gt;&lt;BR&gt;&lt;B&gt;三、用 MFC 实现自动化组件&lt;/B&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我写的这整个系列文章---《COM 组件设计与应用》，多是用 ATL 写组件程序，但由于自动化非常有用，在后续的文章中，还要给大家介绍组件的&amp;#8220;事件&amp;#8221;功能，还要介绍如何在 MFC 的程序中象 WORD 一样支持&amp;#8220;宏&amp;#8221;的功能。这些都要用到 MFC，所以就给读者唠一唠啦:-)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-1：建立一个工作区(Workspace)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-2：建立一个 MFC DLL 工程(Project)，工程名称为&amp;#8220;Simple5&amp;#8221;&lt;BR&gt;&lt;IMG height=480 src="http://www.vckbase.com/document/journal/vckbase43/images/comp9pic01.jpg" width=741 border=0&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-3：一定要选择 automation，切记！切记！&lt;BR&gt;&lt;IMG height=467 src="http://www.vckbase.com/document/journal/vckbase43/images/comp9pic02.jpg" width=612 border=0&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-4：建立新类&lt;BR&gt;&lt;IMG height=348 src="http://www.vckbase.com/document/journal/vckbase43/images/comp9pic03.jpg" width=362 border=0&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-5：在新建类中支持automation&lt;BR&gt;&lt;IMG height=481 src="http://www.vckbase.com/document/journal/vckbase43/images/comp9pic04.jpg" width=536 border=0&gt;&lt;BR&gt;&lt;I&gt;&lt;B&gt;Class information - Name&lt;/B&gt;&lt;/I&gt; 你随便写个类名子啦&lt;BR&gt;&lt;I&gt;&lt;B&gt;Class information - Base class&lt;/B&gt;&lt;/I&gt; 一定要从 CComTarget 派生呀，只有它才提供了 IDispatch 的支持&lt;BR&gt;&lt;I&gt;&lt;B&gt;Automation - None&lt;/B&gt;&lt;/I&gt; 表示不支持自动化，你要选择了它，那就白干啦&lt;BR&gt;&lt;I&gt;&lt;B&gt;Automation - Automation&lt;/B&gt;&lt;/I&gt; 支持自动化，但不能被直接实例化。后面在讲解多个 IDispatch 的时候就用到它了，现在先不要着急。&lt;BR&gt;&lt;I&gt;&lt;B&gt;Automation - Createable by type ID&lt;/B&gt;&lt;/I&gt; 一定要选择这个项目，这样我们在后面的调用中，VB就能够CreateObject(),VC就能够CreateDispatch()对组件对象实例化了。注意一点，这个 ID 其实就是组件的 ProgID 啦。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-6：启动 ClassWizard，选择 Automation 卡片，准备建立函数&lt;BR&gt;&lt;IMG height=484 src="http://www.vckbase.com/document/journal/vckbase43/images/comp9pic05.jpg" width=741 border=0&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-7：添加函数。我们要写一个整数加法函数Add()。&lt;BR&gt;&lt;IMG height=417 src="http://www.vckbase.com/document/journal/vckbase43/images/comp9pic06.jpg" width=474 border=0&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-8：再增加一个转换字符串大小写的函数 Upper()。函数返回值是 BSTR，这个没有什么疑问，但参数类型怎么居然是 LPCTSTR？在 COM 中，字符串不是应该使用 BSTR 吗？是的，是应该使用 BSTR，但由于我们是用 MFC 写自动化组件，它帮我们进行 BSTR 和 LPCTSTR 之间的转换了。&lt;BR&gt;&lt;IMG height=417 src="http://www.vckbase.com/document/journal/vckbase43/images/comp9pic07.jpg" width=474 border=0&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-9：好了，下面开始输入程序代码：&lt;PRE&gt;long CDispSimple::Add(long n1, long n2) 
{
	return n1 + n2;
}

BSTR CDispSimple::Upper(LPCTSTR str) 
{
	CString strResult(str);
	strResult.MakeUpper();

	return strResult.AllocSysString();
}&lt;/PRE&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3-10：编译注册&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果上面的操作由于疏忽而发生了错误，那么你可以手工进行改正。&lt;BR&gt;其一、步骤&amp;lt;3-6&amp;gt;的对话窗中有&amp;#8220;Delete&amp;#8221;操作；&lt;BR&gt;其二、你可以打开 ODL 文件(注2)进行修改，修改时要特别小心函数的声明中，有一个[id(n)] 的函数序号，可不要乱了；&lt;BR&gt;其三、同步修改 H/CPP 中的函数声明和函数体；&lt;BR&gt;其四、在CPP文件中，根据情况也要修改 BEGIN_DISPATCH_MAP/END_DISPATCH_MAP()函数影射宏。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 正确编译后，MFC不象ATL那样会自动注册。你需要手工执行 regsvr32.exe 进行注册，或者执行菜单&amp;#8220;Tools\Register control&amp;#8221;&lt;BR&gt;&lt;BR&gt;&lt;B&gt;四、用 ATL 实现双接口组件&lt;/B&gt;(操作方法和步骤，请参考&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1497"&gt;《&lt;/A&gt;&lt;A href="http://www.vckbase.com/document/viewdoc/?id=1497"&gt;COM 组件设计与应用(五)》&lt;/A&gt;)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-1：建立一个 ATL 工程(Project)，工程名称为&amp;#8220;Simple6&amp;#8221;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-2：按默认进行。选择 DLL 类型、不合并代理和存根代码、不支持MFC、不支持MTS&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-3：New Atl Object... 选择Simple Object&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-4：输入名称和属性，属性按默认进行，也就是 dual(双接口)方式(注3)&lt;BR&gt;&lt;IMG height=260 src="http://www.vckbase.com/document/journal/vckbase43/images/comp9pic08.jpg" width=421 border=0&gt; &lt;IMG height=260 src="http://www.vckbase.com/document/journal/vckbase43/images/comp9pic09.jpg" width=421 border=0&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-5：增加函数。在 ClassView 卡片中，选择接口、鼠标右键菜单、Add Method...&lt;BR&gt;Add([in] VARIANT v1, [in] VARIANT v2, [out, retval] VARIANT * pVal);&lt;BR&gt;Upper([in] BSTR str, [out,retval] BSTR * pVal);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 关于Add()函数，你依然可以使用 Add([in] long n1, [in] long n2, [out,retval] long * pVal) 方式。但这次我们没有使用 long ，而是使用了 VARIANT 做参数和返回值。这里我先卖个关子，往下看，就知道使用 VARIANT 的精彩之处了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4-6：完成代码&lt;PRE&gt;STDMETHODIMP CDispSimple::Add(VARIANT v1, VARIANT v2, VARIANT *pVal)
{
	::VariantInit( pVal );	// 永远初始化