posts - 0, comments - 0, trackbacks - 0, articles - 2
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

[收藏][转]用 SDK 玩转 ActiveX

Posted on 2010-03-02 10:21 捷波 阅读(214) 评论(0)  编辑 收藏 引用 所属分类: COM

原帖: http://blog.tinybrowser.net/archives/1175

原帖在此(也许不能访问): http://blog.titilima.com/show-64-1.html

调用 ActiveX 控件?呃,这实在不是一件容易的事情:用各种封装精良的 Framework(MFC、VCL等等)的话,最后成品 EXE 的体积难免偏大;用SDK虽然可以有效地减小这个体积,但是往往又无从下手——总之,这似乎是一件鱼与熊掌不能兼得的憾事。还好,“不容易”并不代表“不 可能”,我在本文中要介绍给诸位的,就是“玩转”ActiveX 的一种方法,这种方法包括了从 ActiveX 控件调用到 ActiveX 控件事件处理的一切必要细节。当然,题目所说的“SDK”也并不是纯粹的 SDK,而是借助了 ATL 的 OLE 支持,毕竟用 SDK 实现 OLE 容器太繁琐了。

在开始正文之前,我还想说明一下本文所面向的读者群。首先,你必须对 SDK 的编程方式和 COM 组件的调用方式有所了解,因为本文中的绝大部分示例代码都与之相关,涉及到这方面的知识我也不会再加以解释;其次,你可以不了解 ATL,因为本文中对 ATL 的使用仅限于 ActiveX 的 OLE 容器,我也只是在适当的地方给予简要的说明;再次,你可以不了解 COM 连接点的知识,我在文中会给予详细的介绍。
那么闲话毋庸赘叙,让我们开始吧。

准备工作

现在让我们来完成代码之外的事情,请按照以下步骤建立我们的工程:

打开 Visual C++,新建一个 Win32 Application(我名之为ActiveX)。
新建一个 Resource Script(资源脚本),在其中添加一个对话框(我名之为 IDD_MAIN_DLG)。
在对话框上单击右键,选择 “Insert ActiveX Control…”(如下图)。

在本文中,我以Microsoft Agent Control为例,所以在之后的列表之中选择“Microsoft Agent Control 2.0”。

注意: 在 windows 7 下没有默认装配 Microsoft Agent 组件了, 必须自己在微软网站上 下 载 安装, 并同时安装 Merlin 角色文件. (free2000fly 注)

完成后的对话框如下图。

骨架代码

现在就可以编写代码了。建立一个C++ Source File(C++源文件),在其中输入下面的程序骨架:

01 #include <atlbase.h>
02 CComModule _Module;
03 #include <atlwin.h>   
04  
05 #import "C:\\WINNT\\msagent\\agentctl.dll"
06 using namespace AgentObjects;   
07  
08 #include "resource.h"   
09  
10 int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
11              LPTSTR lpCmdLine, int nShowCmd)
12 {
13      _Module.Init(NULL, hInstance);
14      _Module.Term();
15      return 0;
16 }

然后,在工程设置中加入atl.lib,如下图:

(free2000fly 注: 这一步可以不做, 代之以包含 atlcom.h 和 atlhost.h 这两个头文件.)

让我们再回过头来看看上面的代码。程序的头三行就是我在本文开头时所说到的“ATL的支持”,其中预处理的部分你大可以略去不管,你只需要了解的就 是_Module这个全局变量,它保存了程序模块的一些相关信息。并且,在WinMain之中的Init和Term已经包括了CoInitialize、 OleInitialize、CoUninitialize、OleUninitialize的初始化和卸载工作。
#import的一行表示导入Agent控件的类型库,并且由于Agent控件的各个接口被封装在了library AgentObjects之中(这些东西可以使用Visual Studio自带的工具“OLE/COM Object Viewer”从agentctl.dll的类型库接口定义之中看到),所以要使用AgentObjects的命名空间——当然不用也无所谓,只不过是以 后的使用会稍稍麻烦一些。
现在你可以编译链接这段代码了。在编译链接完成之后,你就可以在工程目录下的Debug或Release目录下(取决于你的工程设置)发现名为 agentctl.tlh和agentctl.tli的两个文件。你可以用文本方式打开它们看看,你会发现agentctl.tlh中是 agentctl.dll类型库中各接口的C/C++支持以及各接口的智能指针定义;至于agentctl.tli之中,则是一些更有趣的东西,在这里我 就不多介绍了。

使用 ActiveX

骨架完成后,就可以使用Agent这个ActiveX控件了。不过在使用之前,你需要把你曾经用来显示对话框的代码写成类似下面这个样子:

1 g_hDlgMain = AtlAxCreateDialog(hInstance, MAKEINTRESOURCE(IDD_MAIN_DLG),
2          NULL, (DLGPROC)MainDlgProc, 0);

对于这行代码我需要解释三点。第一,由于我们的对话框中含有ActiveX控件,所以不能使用普通的CreateDialog;第 二,g_hDlgMain是一个全局变量,我需要在另一个类中使用它;第三,由于我们需要显示Agent助手而不显示对话框,所以在此使用了无模式对话框 ——这样就可以创建一个不可见的对话框了。
现在可以在对话框的回调函数中使用ActiveX控件了。以Agent控件为例,通常使用ActiveX是类似这个样子:

1 CAxWindow wndAgent = GetDlgItem(hDlg, IDC_AGENT);
2 // IAgentCtlExPtr的定义来自于agentctl.tlh
3 IAgentCtlExPtr pAgent;
4 HRESULT hr = wndAgent.QueryControl(__uuidof(IAgentCtlEx), ( LPVOID *)&pAgent);

然后,就可以利用pAgent指针对Agent控件进行操作了。你可以在对话框回调函数中的WM_INITDIALOG中加入下面的代码来测试效 果:

01 case WM_INITDIALOG:
02      {
03          CAxWindow wndAgent;
04          IAgentCtlExPtr pAgent;
05          IAgentCtlCharactersPtr pChars;
06          IAgentCtlCharacterExPtr pMerlin;
07          IAgentCtlRequestPtr pRequest;
08          HRESULT hr;   
09  
10          wndAgent = GetDlgItem(hDlg, IDC_AGENT);
11          hr = wndAgent.QueryControl(__uuidof(IAgentCtlEx), ( LPVOID *)&pAgent);   
12  
13          // 获取角色文件路径
14          TCHAR szPath[MAX_PATH];
15          GetWindowsDirectory(szPath, MAX_PATH);
16          lstrcat(szPath, _T( "\msagent\chars\merlin.acs" ));   
17  
18          // 进行连接
19          hr = pAgent->put_Connected((VARIANT_BOOL)-1);   
20  
21          // 获得角色列表
22          hr = pAgent->get_Characters(&pChars);
23          // 装载角色
24          pRequest = pChars->Load(_bstr_t( "merlin" ), CComVariant(szPath));
25          pMerlin = pChars->Character(_bstr_t( "merlin" ));
26          // 显示角色
27          pMerlin->Show();
28          // 计算屏幕中央坐标,并移动
29          short x = (GetSystemMetrics(SM_CXFULLSCREEN) - pMerlin->GetWidth()) / 2;
30          short y = (GetSystemMetrics(SM_CYFULLSCREEN) - pMerlin->GetHeight()) / 2;
31          pRequest = pMerlin->MoveTo(x, y);
32          pRequest = pMerlin->Speak(CComVariant( "右键单击我,选择“隐藏”以结束程序。" ));
33      }
34      break ;

这里我有两点需要说明。第一,事实上对 COM 接口的调用需要非常严谨地判断每个方法返回值的成功与否,而在这里出于篇幅考虑我便将其一概略去,你可以在配套源代码中看到这些容错处理;第二,在使用 Agent控件之前必须将它的Connected状态置为真(-1)—— 也就是 pAgent->put_Connected 的一句,否则以下的方法都会失败,而在MFC的封装下倒可以略去这一句,可能MFC有个自动连接的过程。
到现在为止,你应该已经可以把这个助手显示在屏幕上了,运行看看效果吧。

连接点

可能你注意到了,在WM_INITDIALOG的最后一句,我让Agent助手说了一句话:“右键单击我,选择‘隐藏’以结束程序。”但事实上如果 你运行这段代码的话,你在助手身上右击鼠标并选择“隐藏”,助手虽然隐藏了起来,程序却并没有退出,这是怎么回事呢?答案是显而易见的——我并没有处理 Agent控件的Hide(隐藏)事件。
那么,又如何处理这个事件呢?——在MFC中,ActiveX控件的事件是通过一张宏映射表来实现的,类似下面这个样子:

1 BEGIN_EVENTSINK_MAP(CActiveXDlg, CDialog)
2      //{{AFX_EVENTSINK_MAP(CActiveX)
3      ON_EVENT(CActiveXDlg, IDC_AGENT, 7 /* Hide */ , OnHideAgent, VTS_BSTR VTS_I2)
4      //}}AFX_EVENTSINK_MAP
5 END_EVENTSINK_MAP()

对于SDK来说,就没有那么简单了。我们必须从COM最底层的机制入手,也就是连接点。
那么,什么又是连接点呢?

在有的时候——比如我们这里的ActiveX的事件处理,COM服务器需要将一个接口开放给客户端,然后由客户端实现这个接口供服务器进行回调,这 就是COM的连接点事件。下面,我用两个C++类来模拟连接点和COM服务器之间的关系。

01 class CSink
02 {
03 public :
04      void DoSomeOtherThings()
05      {
06          cout << "I still want to do some other things, so this sentence is from sink." << endl;
07      }
08 };   
09  
10 class ISomeInterface
11 {
12      CSink *m_pSink;
13 public :
14      ISomeInterface() : m_pSink(NULL) {}
15      void SetSink(CSink *pSink)
16      {
17          m_pSink = pSink;
18      }
19      void DoSomething()
20      {
21          cout << "Do something...." << endl;
22          if (NULL != m_pSink)
23          {
24              m_pSink->DoSomeOtherThings();
25          }
26      }
27 };

然后,在程序中这样调用:

1 CSink sink;
2 ISomeInterface *p = new ISomeInterface;
3 p->SetSink(&sink);
4 p->DoSomething();
5 delete p;

如你所见, ISomeInterface 并不是一个严格意义上的 “接口”,而是一个实实在在的类,不过既然这是段模拟代码,所以我也没有必要做得惟妙惟肖了——同样,你也可以把new和delete的过程看作一个接口 CoCreateInstance和Release的过程。CSink类则是用来处理ISomeInterface类回调事件的,在COM中我们称之为 “接收器”。那么,ISomeInterface::SetSink就是设置连接点的“连接”过 程,CSink::DoSomeOtherThings()则是接收器的事件处理实现。

接收器的实现

现在,我们就要开始本文最核心的部分了。通常对于COM接收器来讲,我们可以将它理解为一个没有CLSID的COM组件类,也就是说,我们只需要给 出它的实现,并提供它的指针给服务器就足够了。而且对于我们的实现语言——C++来说,这个实现过程其实和编写一个C++类没有什么两样。那么,首先让我 把这个实现完整地呈现给你,然后再逐一解释吧。

01 // 处理连接点事件的接收器实现
02 class CSink : public IDispatch
03 {
04 public :
05      // 构造/析构函数
06      CSink() : m_uRef(0) {}
07      virtual ~CSink() {}
08      // IUnknown接口实现
09      STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject)
10      {
11          if (iid == __uuidof(_AgentEvents))
12          {
13              *ppvObject = (_AgentEvents *) this ;
14              AddRef();
15              return S_OK;
16          }
17          if (iid == IID_IUnknown)
18          {
19              *ppvObject = (IUnknown *) this ;
20              AddRef();
21              return S_OK;
22          }
23          return E_NOINTERFACE;
24      }
25      ULONG STDMETHODCALLTYPE AddRef()
26      {
27          m_uRef++;
28          return m_uRef;
29      }
30      ULONG STDMETHODCALLTYPE Release()
31      {
32          ULONG u = m_uRef--;
33          if (0 == m_uRef)
34          {
35              delete this ;
36          }
37          return u;
38      }
39      // IDispatch接口实现
40      STDMETHODIMP GetTypeInfoCount( UINT *pctinfo)
41      {
42          return E_NOTIMPL;
43      }
44      STDMETHODIMP GetTypeInfo( UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
45      {
46          return E_NOTIMPL;
47      }
48      STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames,
49          UINT cNames, LCID lcid, DISPID *rgDispId)
50      {
51          return E_NOTIMPL;
52      }
53      STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
54          WORD wFlags, DISPPARAMS *pDispParams,
55          VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
56      {
57          HRESULT hr = S_OK;
58          if (NULL != pDispParams && 7 == dispIdMember)
59          {
60              if (2 == pDispParams->cArgs )
61              {
62                  if (VT_I2 == pDispParams->rgvarg[0].vt &&
63                      VT_BSTR == pDispParams->rgvarg[1].vt)
64                  {
65                      OnHide(pDispParams->rgvarg[1].bstrVal,
66                                pDispParams->rgvarg[0].iVal);
67                  }
68                  else // 类型错误
69                  {
70                      hr = DISP_E_TYPEMISMATCH;
71                  }
72              }
73              else // 参数个数错误
74              {
75                  hr = DISP_E_BADPARAMCOUNT;
76              }
77          }
78          return hr;
79      }
80      // 要处理的_AgentEvents事件
81      STDMETHODIMP OnHide(_bstr_t CharacterID, short Cause)
82      {
83          PostMessage(g_hDlgMain, WM_CLOSE, 0, 0);
84          return S_OK;
85      }
86 private :
87      ULONG m_uRef;
88 };

我们可以用OLE/COM Object Viewer从agentctl.dll的类型库接口定义之中看到以下的内容:

1 dispinterface _AgentEvents {
2 ...

这样我们可以很容易猜到,这个接口就是Agent控件开放给我们处理连接点事件用的。这一句用C++的语法来表示,就是:

1 // 摘自agentctl.tlh
2 struct __declspec ( uuid ( "f5be8bd4-7de6-11d0-91fe-00c04fd701a5" ))
3 _AgentEvents : IDispatch
4 {
5      // ...

也就是说,这个接口继承自IDispatch。IDispatch接口称作“调度”接口,通常用来实现对一些符号解释型语言(如Visual Basic)调用COM接口的支持。关于这个接口的详细情况你可以参考MSDN,里面有非常详尽的介绍。在这里我使接收器亦继承自IDispatch,是 因为我只需要处理Hide一个事件,而若将接收器继承自_AgentEvent,那么我必须完全实现_AgentEvent接口的全部方法,这将会是一个 非常浩大的工程——即使是将除Hide之外的所有方法都返回E_NOTIMPL。
我是前说过,可以将接收器理解为一个没有CLSID的COM组件类。因此,接收器必须完全按照COM组件的规格来实现,也就是你所看到的AddRef、 Release和QueryInterface的部分。不过,接收器终究有着它自己的特定性,所以我们可以简化QueryInterface,并且可以将 IDispatch的GetTypeInfoCount、GetTypeInfo和GetIDsOfNames直接返回E_NOTIMPL。
现在来到CSink::Invoke的部分。既然COM拥有语言无关的特性,那么就意味着像Visual Basic这样的符号解释型语言也可以处理ActiveX控件的事件。从这一点我们可以猜想到,所有事件都是经由IDispatch::Invoke调度 的——事实上这一点从MFC的事件映射表就可以看出来。所以,我们也通过Invoke来捕获ID为7的Hide事件,在检验一切条件都符合后便调用我们自 己的处理函数OnHide。
在CSink::Invoke中有着大量的条件判断,这是为了代码的严谨,从调度ID、参数类型、参数个数等几个方面来确定本次调用无误之后才调用 OnHide。也就是说,CSink::Invoke和CSink::OnHide完全可以写成这个样子:

01 STDMETHODIMP CSink::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
02                   WORD wFlags, DISPPARAMS *pDispParams,
03                   VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
04 {
05      if (7 == dispIdMember)
06      {
07          OnHide();
08      }
09      return S_OK;
10 }
11 void CSink::OnHide()
12 {
13      PostMessage(g_hDlgMain, WM_CLOSE, 0, 0 );
14 }

看起来的确是简单多了,不过我还是建议你使用前面的方法。

连接点的设置

连接点的使用非常简单,很模式化的代码:

01 // 设置连接点的过程开始
02 IConnectionPointContainer *pCPC = NULL;
03 // 查询连接点容器
04 hr = pAgent->QueryInterface(IID_IConnectionPointContainer, ( void **)&pCPC);
05 // 查找连接点
06 hr = pCPC->FindConnectionPoint(__uuidof(_AgentEvents), &pCP);
07 // 这时连接点容器已经没用了,释放之
08 pCPC->Release();
09 pCPC = NULL;
10 // 创建通知对象
11 CSink *pSink = new CSink;
12 hr = pSink->QueryInterface(IID_IUnknown, ( void **)&pSinkUnk);
13 // 对连接点进行设置
14 hr = pCP->Advise(pSinkUnk, &dwCookie);

需要注意的是,这段代码必须放在 pAgent->put_Connected 之后,否则连接点的设置就会失败。另外,这段代码中有几个变量是定义在回调函数头部的static变量,如下:

1 static IConnectionPoint *pCP = NULL;
2 static IUnknown *pSinkUnk = NULL;
3 static DWORD dwCookie = 0;

在程序结束的时候,这样释放连接点:

1 pCP->Unadvise(dwCookie);
2 pCP->Release();
3 pSinkUnk->Release();

需要注意的是pSinkUnk->Release()一句。由于先前进行了接口查询QueryInterface使得引用计数增加,所以必须 在结束使用的时候调用Release。
到现在为止,调用ActiveX的大致过程和原理我已经介绍完了。由于示例工程的代码是一段一段地根据文章逻辑而无序引用的,所以这可能会给诸位带来实现 上的麻烦,在此我给大家赔个不是。

附件: activex.zip (10.61 K, 下载次数:225) activex

补遗

已经不止一位朋友向我咨询过这个问题——《用SDK玩转ActiveX》的源代码在VS2003、VS2005下无法正常运行,错误原因是 GetDlgItem无法取到ActiveX控件的窗口容器句柄,因而也就无法为CAxWindow赋值。于是在此补遗,请诸位周知。
ATL 3.0中的ActiveX控件处理机制是这样的:

调用 AtlAxCreateDialog 或 AtlAxDialogBox 之前,检索对话框资源中的所有控件数据。
如果遇到 ActiveX 控件,则将其窗口类名称替换为 “AtlAxWin”(ATL窗口类名),窗口标题替换为该 ActiveX 控件的CLSID。
由于替换成了名为 “AtlAxWin” 的窗口类,所以对话框创建控件后会依次创建各个容器,再由各个容器自己创建相应的 ActiveX 控件。

但是从 ATL 7.0 开始,处理方式变成了:

调用 AtlAxCreateDialog 或 AtlAxDialogBox 之前,检索对话框资源中的所有控件数据。
如果遇到 ActiveX 控件,则将该控件数据从对话框资源中剔除。
在 CAxDialogImpl::DialogProc 中截获 WM_INITDIALOG 并创建所有的 ActiveX 控件。
所以,对于一个使用 ATL 7.0 编写的含有 ActiveX 控件的对话框而言,应该使用 CAxDialogImpl,而不再是简单的AtlAxCreateDialog 或 AtlAxDialogBox了。

Free2000fly 注:
修改版的程序, ATL 7.0 及以后不再出错了:

001 #include <atlbase.h>
002 CComModule _Module;
003 #include <atlwin.h>
004  
005 #include <atlcom.h>
006 #include <atlhost.h>
007  
008 #import "C:\windows\msagent\agentctl.dll" no_implementation \
009                    no_smart_pointers raw_interfaces_only
010  
011 using namespace AgentObjects;
012  
013 #include "resource.h"
014  
015 // 处理连接点事件的接收器实现
016 class CSink : public IDispatch
017 {
018 public :
019      HWND g_hDlgMain;
020  
021      // 构造/析构函数
022      CSink( HWND hOwner) : m_uRef( 0 ) , g_hDlgMain(hOwner) {}
023      virtual ~CSink() {}
024      // IUnknown接口实现
025      STDMETHODIMP QueryInterface( REFIID iid, void **ppvObject )
026      {
027          if ( iid == __uuidof( _AgentEvents ) )
028          {
029              *ppvObject = (_AgentEvents *) this ;
030              AddRef();
031              return S_OK;
032          }
033          if ( iid == IID_IUnknown )
034          {
035              *ppvObject = (IUnknown *) this ;
036              AddRef();
037              return S_OK;
038          }
039          return E_NOINTERFACE;
040      }
041      ULONG STDMETHODCALLTYPE AddRef()
042      {
043          m_uRef++;
044          return m_uRef;
045      }
046      ULONG STDMETHODCALLTYPE Release()
047      {
048          ULONG u = m_uRef--;
049          if ( 0 == m_uRef )
050          {
051              delete this ;
052          }
053          return u;
054      }
055      // IDispatch接口实现
056      STDMETHODIMP GetTypeInfoCount( UINT *pctinfo )
057      {
058          return E_NOTIMPL;
059      }
060      STDMETHODIMP GetTypeInfo( UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
061      {
062          return E_NOTIMPL;
063      }
064      STDMETHODIMP GetIDsOfNames( REFIID riid, LPOLESTR *rgszNames, UINT cNames,
065                  LCID lcid, DISPID *rgDispId)
066      {
067          return E_NOTIMPL;
068      }
069      STDMETHODIMP Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
070                   DISPPARAMS *pDispParams, VARIANT *pVarResult,
071                   EXCEPINFO *pExcepInfo, UINT *puArgErr )
072      {
073          HRESULT hr = S_OK;
074          if ( NULL != pDispParams && 7 == dispIdMember )
075          {
076              if ( 2 == pDispParams->cArgs )
077              {
078                  if ( VT_I2 == pDispParams->rgvarg[0].vt &&
079                                       VT_BSTR == pDispParams->rgvarg[1].vt )
080                  {
081                      OnHide( pDispParams->rgvarg[1].bstrVal,
082                                                     pDispParams->rgvarg[0].iVal );
083                  }
084                  else // 类型错误
085                  {
086                      hr = DISP_E_TYPEMISMATCH;
087                  }
088              }
089              else // 参数个数错误
090              {
091                  hr = DISP_E_BADPARAMCOUNT;
092              }
093          }
094          return hr;
095      }
096      // 要处理的_AgentEvents事件
097      STDMETHODIMP OnHide( _bstr_t CharacterID, short Cause )
098      {
099          PostMessage( g_hDlgMain, WM_CLOSE, 0, 0 );
100          return S_OK;
101      }
102 private :
103      ULONG m_uRef;
104 };
105  
106 class CMyDlg : public CAxDialogImpl<CMyDlg>
107 {
108 public :
109      enum {
110          IDD = IDD_MAIN_DLG,
111      };
112  
113      BEGIN_MSG_MAP(CMyDlg)
114 #if _ATL_VER > 0x0300
115          CHAIN_MSG_MAP(CAxDialogImpl<CMyDlg>)
116 #endif
117          MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
118          MESSAGE_HANDLER(WM_CLOSE, OnClose)
119          MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
120      END_MSG_MAP()
121  
122      IConnectionPoint *pCP;
123      IUnknown *pSinkUnk;
124      DWORD dwCookie;
125  
126      CMyDlg()
127      {
128          pCP = NULL;
129          pSinkUnk = NULL;
130          dwCookie = 0;
131      }
132  
133      LRESULT OnInitDialog( UINT , WPARAM , LPARAM , BOOL & bHandled)
134      {
135          bHandled = FALSE;
136          do
137          {
138              HWND hDlg = m_hWnd;
139  
140              CAxWindow wndAgent;
141              CComPtr<IAgentCtlEx> pAgent;
142              CComPtr<IAgentCtlCharacters> pChars;
143              CComPtr<IAgentCtlCharacterEx> pMerlin;
144              CComPtr<IAgentCtlRequest> pRequest;
145              HRESULT hr;
146  
147              wndAgent = ::GetDlgItem( hDlg, IDC_AGENT );
148              hr = wndAgent.QueryControl( __uuidof( IAgentCtlEx ),
149                                    ( LPVOID * )&pAgent );
150              if ( FAILED( hr ) )
151              {
152                  ::MessageBox( hDlg, _T( " 查询IAgentCtlEx失败!" ),
153                                         _T( "错误" ), MB_ICONSTOP );
154                  break ;
155              }
156  
157              // 获取角色文件路径
158              TCHAR szPath[MAX_PATH];
159              GetWindowsDirectory( szPath, MAX_PATH );
160              lstrcat( szPath, _T( "\\msagent\\chars\\merlin.acs" ) );
161  
162              // 进行连接
163              hr = pAgent->put_Connected( (VARIANT_BOOL)-1 );
164              if ( FAILED( hr ) )
165              {
166                  ::MessageBox( hDlg, _T( " 连接控件失败!" ),
167                                            _T( "错误" ), MB_ICONSTOP );
168                  break ;
169              }
170  
171              // 设置连接点的过程开始
172              IConnectionPointContainer *pCPC = NULL;
173              // 查询连接点容器
174              hr = pAgent->QueryInterface( IID_IConnectionPointContainer,
175                                            ( void **)&pCPC );
176              if ( FAILED( hr ) )
177              {
178                  ::MessageBox( hDlg, _T( " 查询连接点容器失败!" ),
179                                          _T( "错误" ), MB_ICONSTOP );
180                  break ;
181              }
182              // 查找连接点
183              hr = pCPC->FindConnectionPoint( __uuidof( _AgentEvents ), &pCP );
184              if ( FAILED( hr ) )
185              {
186                  ::MessageBox( hDlg, _T( " 查找连接点失败!" ),
187                                        _T( "错误" ), MB_ICONSTOP );
188                  break ;
189              }
190              // 这时连接点容器已经没用了,释放之
191              pCPC->Release();
192              pCPC = NULL;
193              // 创建通知对象
194              CSink *pSink = new CSink(m_hWnd);
195              hr = pSink->QueryInterface( IID_IUnknown, ( void **)&pSinkUnk );
196              if ( FAILED( hr ) )
197              {
198                  ::MessageBox( hDlg, _T( " 接口查询失败!" ), _T( "错误" ), MB_ICONSTOP );
199                  break ;
200              }
201              // 对连接点进行设置
202              hr = pCP->Advise( pSinkUnk, &dwCookie );
203              if ( FAILED( hr ) )
204              {
205                  ::MessageBox( hDlg, _T( " 连接点设置失败!" ), _T( "错误" ), MB_ICONSTOP );
206                  break ;
207              }
208  
209              // 获得角色列表
210              hr = pAgent->get_Characters( &pChars );
211              if ( FAILED( hr ) )
212              {
213                  ::MessageBox( hDlg, _T( " 获得角色列表失败!" ), _T( "错误" ),
214                                       MB_ICONSTOP );
215                  break ;
216              }
217              // 装载角色
218              hr = pChars->Load( _bstr_t( "merlin" ),
219                                CComVariant(szPath), &pRequest );
220              hr = pChars->Character( _bstr_t( "merlin" ), &pMerlin );
221              // 显示角色
222              CComPtr<IAgentCtlRequest> spTmp;
223              hr = pMerlin->Show(vtMissing, &spTmp);
224              // 计算屏幕中央坐标,并移动
225              short x0 = 0, y0 = 0;
226              pMerlin->get_Width( &x0 );
227              pMerlin->get_Height( &y0 );
228              short x = ( GetSystemMetrics( SM_CXFULLSCREEN ) - x0 ) / 2;
229              short y = ( GetSystemMetrics( SM_CYFULLSCREEN ) - y0 ) / 2;
230              pRequest.Release();
231              hr = pMerlin->MoveTo( x, y, vtMissing, &pRequest);
232              pRequest.Release();
233              hr = pMerlin->Speak(
234                                CComVariant( "右键单击我, 选择 \"隐藏\" 以结束程序. " ),
235                                vtMissing, &pRequest );
236          } while ( false );
237  
238          return 0;
239      }
240  
241      LRESULT OnClose( UINT , WPARAM , LPARAM , BOOL & bHandled)
242      {
243          HWND hDlg = m_hWnd;
244          // 释放所有资源
245          pCP->Unadvise( dwCookie );
246          pCP->Release();
247          // 由于pSinkUnk进行了QueryInterface,所以必须Release之
248          pSinkUnk->Release();
249          ::DestroyWindow( hDlg );
250          return 0;
251      }
252  
253      LRESULT OnDestroy( UINT , WPARAM , LPARAM , BOOL & bHandled)
254      {
255          PostQuitMessage( 0 );
256          return 0;
257      }
258 };
259  
260 int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE , LPTSTR lpCmdLine, int nShowCmd )
261 {
262      MSG msg = { 0 };
263      _Module.Init( NULL, hInstance );
264      {
265          CMyDlg dlg;
266          dlg.Create(NULL);
267          dlg.ShowWindow(SW_NORMAL);
268          dlg.UpdateWindow();
269          while ( GetMessage( &msg, NULL, 0, 0 ) )
270          {
271              TranslateMessage( &msg );
272              DispatchMessage( &msg );
273          }
274      }
275      _Module.Term();
276      return msg.wParam;
277 }