|
|
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++源文件),在其中输入下面的程序骨架:
05
|
#import "C:\\WINNT\\msagent\\agentctl.dll"
|
06
|
using
namespace
AgentObjects;
|
10
|
int
WINAPI _tWinMain(
HINSTANCE
hInstance,
HINSTANCE
hPrevInstance,
|
11
|
LPTSTR
lpCmdLine,
int
nShowCmd)
|
13
|
_Module.Init(NULL,
hInstance);
|
然后,在工程设置中加入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);
|
4
|
HRESULT
hr = wndAgent.QueryControl(__uuidof(IAgentCtlEx), (
LPVOID
*)&pAgent);
|
然后,就可以利用pAgent指针对Agent控件进行操作了。你可以在对话框回调函数中的WM_INITDIALOG中加入下面的代码来测试效
果:
04
|
IAgentCtlExPtr pAgent;
|
05
|
IAgentCtlCharactersPtr pChars;
|
06
|
IAgentCtlCharacterExPtr pMerlin;
|
07
|
IAgentCtlRequestPtr pRequest;
|
10
|
wndAgent =
GetDlgItem(hDlg, IDC_AGENT);
|
11
|
hr = wndAgent.QueryControl(__uuidof(IAgentCtlEx), (
LPVOID
*)&pAgent);
|
14
|
TCHAR
szPath[MAX_PATH];
|
15
|
GetWindowsDirectory(szPath, MAX_PATH);
|
16
|
lstrcat(szPath, _T(
"\msagent\chars\merlin.acs"
));
|
19
|
hr = pAgent->put_Connected((VARIANT_BOOL)-1);
|
22
|
hr = pAgent->get_Characters(&pChars);
|
24
|
pRequest = pChars->Load(_bstr_t(
"merlin"
), CComVariant(szPath));
|
25
|
pMerlin = pChars->Character(_bstr_t(
"merlin"
));
|
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(
"右键单击我,选择“隐藏”以结束程序。"
));
|
这里我有两点需要说明。第一,事实上对 COM
接口的调用需要非常严谨地判断每个方法返回值的成功与否,而在这里出于篇幅考虑我便将其一概略去,你可以在配套源代码中看到这些容错处理;第二,在使用
Agent控件之前必须将它的Connected状态置为真(-1)—— 也就是 pAgent->put_Connected
的一句,否则以下的方法都会失败,而在MFC的封装下倒可以略去这一句,可能MFC有个自动连接的过程。
到现在为止,你应该已经可以把这个助手显示在屏幕上了,运行看看效果吧。
连接点
可能你注意到了,在WM_INITDIALOG的最后一句,我让Agent助手说了一句话:“右键单击我,选择‘隐藏’以结束程序。”但事实上如果
你运行这段代码的话,你在助手身上右击鼠标并选择“隐藏”,助手虽然隐藏了起来,程序却并没有退出,这是怎么回事呢?答案是显而易见的——我并没有处理
Agent控件的Hide(隐藏)事件。
那么,又如何处理这个事件呢?——在MFC中,ActiveX控件的事件是通过一张宏映射表来实现的,类似下面这个样子:
1
|
BEGIN_EVENTSINK_MAP(CActiveXDlg, CDialog)
|
3
|
ON_EVENT(CActiveXDlg,
IDC_AGENT, 7
, OnHideAgent, VTS_BSTR VTS_I2)
|
对于SDK来说,就没有那么简单了。我们必须从COM最底层的机制入手,也就是连接点。
那么,什么又是连接点呢?
在有的时候——比如我们这里的ActiveX的事件处理,COM服务器需要将一个接口开放给客户端,然后由客户端实现这个接口供服务器进行回调,这
就是COM的连接点事件。下面,我用两个C++类来模拟连接点和COM服务器之间的关系。
04
|
void
DoSomeOtherThings()
|
06
|
cout <<
"I still want to do
some other things, so this sentence is from sink."
<< endl;
|
14
|
ISomeInterface()
: m_pSink(NULL) {}
|
15
|
void
SetSink(CSink *pSink)
|
21
|
cout <<
"Do something...."
<< endl;
|
24
|
m_pSink->DoSomeOtherThings();
|
然后,在程序中这样调用:
2
|
ISomeInterface *p =
new
ISomeInterface;
|
如你所见, ISomeInterface 并不是一个严格意义上的
“接口”,而是一个实实在在的类,不过既然这是段模拟代码,所以我也没有必要做得惟妙惟肖了——同样,你也可以把new和delete的过程看作一个接口
CoCreateInstance和Release的过程。CSink类则是用来处理ISomeInterface类回调事件的,在COM中我们称之为
“接收器”。那么,ISomeInterface::SetSink就是设置连接点的“连接”过
程,CSink::DoSomeOtherThings()则是接收器的事件处理实现。
接收器的实现
现在,我们就要开始本文最核心的部分了。通常对于COM接收器来讲,我们可以将它理解为一个没有CLSID的COM组件类,也就是说,我们只需要给
出它的实现,并提供它的指针给服务器就足够了。而且对于我们的实现语言——C++来说,这个实现过程其实和编写一个C++类没有什么两样。那么,首先让我
把这个实现完整地呈现给你,然后再逐一解释吧。
02
|
class
CSink :
public
IDispatch
|
06
|
CSink()
: m_uRef(0) {}
|
09
|
STDMETHODIMP
QueryInterface(REFIID iid,
void
**ppvObject)
|
11
|
if
(iid ==
__uuidof(_AgentEvents))
|
13
|
*ppvObject =
(_AgentEvents *)
this
;
|
17
|
if
(iid == IID_IUnknown)
|
19
|
*ppvObject =
(IUnknown *)
this
;
|
25
|
ULONG
STDMETHODCALLTYPE AddRef()
|
30
|
ULONG
STDMETHODCALLTYPE Release()
|
40
|
STDMETHODIMP
GetTypeInfoCount(
UINT
*pctinfo)
|
44
|
STDMETHODIMP
GetTypeInfo(
UINT
iTInfo,
LCID
lcid, ITypeInfo **ppTInfo)
|
48
|
STDMETHODIMP
GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames,
|
49
|
UINT
cNames,
LCID
lcid, DISPID
*rgDispId)
|
53
|
STDMETHODIMP
Invoke(DISPID dispIdMember, REFIID riid,
LCID
lcid,
|
54
|
WORD
wFlags, DISPPARAMS
*pDispParams,
|
55
|
VARIANT *pVarResult, EXCEPINFO *pExcepInfo,
UINT
*puArgErr)
|
58
|
if
(NULL != pDispParams
&& 7 == dispIdMember)
|
60
|
if
(2 == pDispParams->cArgs )
|
62
|
if
(VT_I2 ==
pDispParams->rgvarg[0].vt &&
|
63
|
VT_BSTR == pDispParams->rgvarg[1].vt)
|
65
|
OnHide(pDispParams->rgvarg[1].bstrVal,
|
66
|
pDispParams->rgvarg[0].iVal);
|
70
|
hr = DISP_E_TYPEMISMATCH;
|
75
|
hr = DISP_E_BADPARAMCOUNT;
|
81
|
STDMETHODIMP
OnHide(_bstr_t CharacterID,
short
Cause)
|
83
|
PostMessage(g_hDlgMain, WM_CLOSE, 0, 0);
|
我们可以用OLE/COM Object Viewer从agentctl.dll的类型库接口定义之中看到以下的内容:
1
|
dispinterface _AgentEvents {
|
这样我们可以很容易猜到,这个接口就是Agent控件开放给我们处理连接点事件用的。这一句用C++的语法来表示,就是:
2
|
struct
__declspec
(
uuid
(
"f5be8bd4-7de6-11d0-91fe-00c04fd701a5"
))
|
3
|
_AgentEvents : IDispatch
|
也就是说,这个接口继承自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)
|
05
|
if
(7 == dispIdMember)
|
13
|
PostMessage(g_hDlgMain,
WM_CLOSE, 0, 0 );
|
看起来的确是简单多了,不过我还是建议你使用前面的方法。
连接点的设置
连接点的使用非常简单,很模式化的代码:
02
|
IConnectionPointContainer *pCPC
= NULL;
|
04
|
hr =
pAgent->QueryInterface(IID_IConnectionPointContainer, (
void
**)&pCPC);
|
06
|
hr =
pCPC->FindConnectionPoint(__uuidof(_AgentEvents), &pCP);
|
11
|
CSink *pSink =
new
CSink;
|
12
|
hr =
pSink->QueryInterface(IID_IUnknown, (
void
**)&pSinkUnk);
|
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);
|
需要注意的是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 及以后不再出错了:
008
|
#import "C:\windows\msagent\agentctl.dll"
no_implementation \
|
009
|
no_smart_pointers raw_interfaces_only
|
011
|
using
namespace
AgentObjects;
|
013
|
#include "resource.h"
|
016
|
class
CSink :
public
IDispatch
|
022
|
CSink(
HWND
hOwner) :
m_uRef( 0 ) , g_hDlgMain(hOwner) {}
|
025
|
STDMETHODIMP
QueryInterface( REFIID iid,
void
**ppvObject )
|
027
|
if
( iid == __uuidof(
_AgentEvents ) )
|
029
|
*ppvObject =
(_AgentEvents *)
this
;
|
033
|
if
( iid == IID_IUnknown )
|
035
|
*ppvObject =
(IUnknown *)
this
;
|
039
|
return
E_NOINTERFACE;
|
041
|
ULONG
STDMETHODCALLTYPE AddRef()
|
046
|
ULONG
STDMETHODCALLTYPE Release()
|
056
|
STDMETHODIMP
GetTypeInfoCount(
UINT
*pctinfo )
|
060
|
STDMETHODIMP
GetTypeInfo(
UINT
iTInfo,
LCID
lcid, ITypeInfo **ppTInfo)
|
064
|
STDMETHODIMP
GetIDsOfNames( REFIID riid, LPOLESTR *rgszNames,
UINT
cNames,
|
065
|
LCID
lcid, DISPID
*rgDispId)
|
069
|
STDMETHODIMP
Invoke( DISPID dispIdMember, REFIID riid,
LCID
lcid,
WORD
wFlags,
|
070
|
DISPPARAMS *pDispParams, VARIANT *pVarResult,
|
071
|
EXCEPINFO *pExcepInfo,
UINT
*puArgErr )
|
074
|
if
( NULL != pDispParams
&& 7 == dispIdMember )
|
076
|
if
( 2 == pDispParams->cArgs )
|
078
|
if
( VT_I2 ==
pDispParams->rgvarg[0].vt &&
|
079
|
VT_BSTR == pDispParams->rgvarg[1].vt )
|
081
|
OnHide( pDispParams->rgvarg[1].bstrVal,
|
082
|
pDispParams->rgvarg[0].iVal );
|
086
|
hr = DISP_E_TYPEMISMATCH;
|
091
|
hr = DISP_E_BADPARAMCOUNT;
|
097
|
STDMETHODIMP
OnHide( _bstr_t CharacterID,
short
Cause )
|
099
|
PostMessage( g_hDlgMain, WM_CLOSE, 0, 0 );
|
106
|
class
CMyDlg :
public
CAxDialogImpl<CMyDlg>
|
113
|
BEGIN_MSG_MAP(CMyDlg)
|
114
|
#if _ATL_VER > 0x0300
|
115
|
CHAIN_MSG_MAP(CAxDialogImpl<CMyDlg>)
|
117
|
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
|
118
|
MESSAGE_HANDLER(WM_CLOSE, OnClose)
|
119
|
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
|
122
|
IConnectionPoint *pCP;
|
133
|
LRESULT
OnInitDialog(
UINT
,
WPARAM
,
LPARAM
,
BOOL
& bHandled)
|
141
|
CComPtr<IAgentCtlEx>
pAgent;
|
142
|
CComPtr<IAgentCtlCharacters>
pChars;
|
143
|
CComPtr<IAgentCtlCharacterEx>
pMerlin;
|
144
|
CComPtr<IAgentCtlRequest>
pRequest;
|
147
|
wndAgent =
::GetDlgItem( hDlg, IDC_AGENT );
|
148
|
hr = wndAgent.QueryControl( __uuidof( IAgentCtlEx ),
|
149
|
(
LPVOID
* )&pAgent );
|
152
|
::MessageBox( hDlg, _T(
"
查询IAgentCtlEx失败!"
),
|
153
|
_T(
"错误"
), MB_ICONSTOP );
|
158
|
TCHAR
szPath[MAX_PATH];
|
159
|
GetWindowsDirectory( szPath, MAX_PATH );
|
160
|
lstrcat( szPath, _T(
"\\msagent\\chars\\merlin.acs"
) );
|
163
|
hr = pAgent->put_Connected( (VARIANT_BOOL)-1 );
|
166
|
::MessageBox( hDlg, _T(
"
连接控件失败!"
),
|
167
|
_T(
"错误"
), MB_ICONSTOP );
|
172
|
IConnectionPointContainer
*pCPC = NULL;
|
174
|
hr = pAgent->QueryInterface(
IID_IConnectionPointContainer,
|
178
|
::MessageBox( hDlg, _T(
"
查询连接点容器失败!"
),
|
179
|
_T(
"错误"
), MB_ICONSTOP );
|
183
|
hr = pCPC->FindConnectionPoint( __uuidof(
_AgentEvents ), &pCP );
|
186
|
::MessageBox( hDlg, _T(
"
查找连接点失败!"
),
|
187
|
_T(
"错误"
), MB_ICONSTOP );
|
194
|
CSink *pSink =
new
CSink(m_hWnd);
|
195
|
hr = pSink->QueryInterface( IID_IUnknown, (
void
**)&pSinkUnk
);
|
198
|
::MessageBox( hDlg, _T(
"
接口查询失败!"
), _T(
"错误"
), MB_ICONSTOP );
|
202
|
hr = pCP->Advise( pSinkUnk, &dwCookie );
|
205
|
::MessageBox( hDlg, _T(
"
连接点设置失败!"
), _T(
"错误"
), MB_ICONSTOP );
|
210
|
hr = pAgent->get_Characters( &pChars );
|
213
|
::MessageBox( hDlg, _T(
"
获得角色列表失败!"
), _T(
"错误"
),
|
218
|
hr = pChars->Load( _bstr_t(
"merlin"
),
|
219
|
CComVariant(szPath), &pRequest );
|
220
|
hr = pChars->Character( _bstr_t(
"merlin"
),
&pMerlin );
|
222
|
CComPtr<IAgentCtlRequest> spTmp;
|
223
|
hr = pMerlin->Show(vtMissing, &spTmp);
|
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;
|
231
|
hr = pMerlin->MoveTo( x, y, vtMissing,
&pRequest);
|
234
|
CComVariant(
"右键单击我,
选择 \"隐藏\" 以结束程序. "
),
|
235
|
vtMissing, &pRequest );
|
241
|
LRESULT
OnClose(
UINT
,
WPARAM
,
LPARAM
,
BOOL
& bHandled)
|
245
|
pCP->Unadvise( dwCookie );
|
249
|
::DestroyWindow( hDlg );
|
253
|
LRESULT
OnDestroy(
UINT
,
WPARAM
,
LPARAM
,
BOOL
& bHandled)
|
255
|
PostQuitMessage( 0 );
|
260
|
int
WINAPI
_tWinMain(
HINSTANCE
hInstance,
HINSTANCE
,
LPTSTR
lpCmdLine,
int
nShowCmd )
|
263
|
_Module.Init(
NULL, hInstance );
|
267
|
dlg.ShowWindow(SW_NORMAL);
|
269
|
while
( GetMessage(
&msg, NULL, 0, 0 ) )
|
271
|
TranslateMessage(
&msg );
|
272
|
DispatchMessage( &msg );
|
|