??xml version="1.0" encoding="utf-8" standalone="yes"?>
q且些函数通向 com 库提供了 com 的二q制指针。然?/span> com 使这些函数运行在
h了一个指向该接口的Q何客L?/font>
Interfaces and Interface Implementations
COM 在接口的定义和实C有根本的差别。一个接口实际上是由一l定义了用法的相
联系的函数原型组成,只是他不能够被实现。这些函数原型就相当?/span> C++ 中含有纯虚拟函数的基cR一个接口定义制定了接口的成员函数、调用方法、返回类型、他们的参数的数量和cdQ这些函数要q什么。但是这里ƈ没有与接口实现相关的东西。一个接口的实现是E序员在一个借口定义上提供的执行相关动作的代码?/span>
一个接口的实现是E序员在一个借口定义上提供的执行相关动作的代码。客戯用完全是军_于接口的定义?/span>
Interface Pointers and Interfaces
接口实现的一个实例,实际上就是一个指向一l方法的指针Q即是指指向一个接口的?/font> 数表Q该函数表引用了该接口所有方法的实现?/font>
每个接口Q是一个固定的一l方法的集合Q在q行旉过 globally unique interface identifier (IID) 来定位。这里, IID ?/span> com 支持?/span> globally unique identifier (GUID) 的特D的实例。这样做׃会生单一pȝ上相同名字、接口的多个版本?/span> COM 之间的冲H了?/span>
· COM接口的不变性——你不能够用老版本的接口标识W定义新的版本,接口的IID定义的接口合同是明确的、唯一?/span> ?/span>
IUnknown and Interface Inheritance
l承?/span>
COM
里ƈ不意味着代码的重用。因为接口没有实现关联,借口l承q意味着代码l承。他的意思仅仅是Q一个接口同一个合同关联,像
C++
的纯虚拟基类的创建和修改P可以dҎ或者更q一步的加强Ҏ的用。在
COM
里没有选择性ѝ如果一个接口由另一个接口承的话,他就包含了另一个接口定义的所有的Ҏ?/span>
Using and Implementing IUnknown
COM 为实现和使用对象和对象的内部通信提供了一个丰富的标准集合。对IUnknown接口的实现和使用的细节,请参见下面主题:QueryInterface: Navigating in an Object 。?o:p>
Rules for Implementing QueryInterface
理实现一?span lang="EN-US">COM对象?/span>
IUnknown::QueryInterface
Ҏ的三个主要规则:
1Q对象必要有一个标识符Q?/font>
2Q一个对象实例的接口集合必须是静态的Q?b style="mso-bidi-font-weight: normal">staticQ;
3Q在对象中从M一个其他的接口查询此接口都应该成功?/span>
通过引用计数来管理对象的生命周期
使用
AddRef
Q)
//增加引用
RealaseQ) //减少引用
BEGIN_MSG_MAP( CMyMainWindow ) COMMAND_ID_HANDLER( ID_HELP_ABOUT, onHelpAbout ) ... LRESULT onHelpAbout( WORD, WORD, HWND, BOOL& ) { CSimpleDialog<IDD_DIALOG1> dlg; int ret = dlg.DoModal(); return 0; }我们可以看到对话框资源的IDQIDD_DIALOG1Q被作ؓ一个模版参C递给CSimpleDialogc,DoModalҎ昄对话框。当用户点击OK按钮ӞCSimpleDialogcd闭对话框q返回按钮的ID。(CSimpleDialogcdCҎ钮IDOKQIDCANCELQIDABORTQIDRETRYQIDIGNOREQIDYES和IDNO的响应。)
class CMyModelessDialog: public CDialogImpl和CSimpleDialog不同Q我们不需要将对话框资源的ID作ؓ模板参数传递给它,但是我们必须这个类和对话框资源联系hQ我们通过在类中定义一个枚丑֏量实玎ͼ{
public: enum { IDD = IDD_DIALOG1 };然后定义消息映射表:
BEGIN_MSG_MAP( CMyDialog ) MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog ) MESSAGE_HANDLER( WM_CLOSE, OnClose ) ... END_MSG_MAP()响应函数的定义和前面的一P但是有一炚w要注意,如果你实现的是一个非模式对话框,那么在WM_CLOSE消息的响应函C必须调用DestroyWindowQ?pre>LRESULT OnClose( UINT, WPARAM, LPARAM, BOOL& ) { DestroyWindow(); return 0; } ... }; // CMyModelessDialog 要在屏幕上创样一个对话框Q需要创个类的一个实例ƈ调用CreateҎQ?
CMyModelessDialog dlg; dlg.Create( wndParent );如果对话框资源没有选中WS_VISIBLE属性,我们需要这栯对话框显C出来:
dlg.ShowWindow( SW_SHOW );下面的例子有一个非模式的对话框可以接受用户输入的字W串Q然后在ȝ口中昄q个字符丌Ӏ对话框中有一个编辑框控g和一个按钮;当按钮被点击Ӟ对话框调用它所属窗口的DoSomethingҎ对编辑框中的字符串进行处理,它所属的H口是一个超cd的列表框控gQDoSomethingҎ的功能是字W串d到列表框中?
#include "atlbase.h" CComModule _Module; #include "atlwin.h" #include "resource.h" class CMyWindow: public CWindowImpl<CMyWindow> { public: DECLARE_WND_SUPERCLASS( "MyWindow", "listbox" ) BEGIN_MSG_MAP( CMyWindow ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) END_MSG_MAP() LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ) { PostQuitMessage( 0 ); return 0; } void DoSomething( LPCTSTR s ) { SendMessage( LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(s) ); } }; class CMyDialog: public CDialogImpl<CMyDialog> { public: enum { IDD = IDD_DIALOG1 }; BEGIN_MSG_MAP( CMyDialog ) COMMAND_ID_HANDLER( IDC_BUTTON1, OnButton ) MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog ) MESSAGE_HANDLER( WM_CLOSE, OnClose ) END_MSG_MAP() LRESULT OnButton(WORD, WORD, HWND, BOOL&) { char buf[100]; m_ed.GetWindowText( buf, 100 ); m_owner.DoSomething( buf ); return 0; } LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& ) { m_owner.Attach( GetParent() ); CenterWindow( m_owner ); m_ed = GetDlgItem( IDC_EDIT1 ); return TRUE; } LRESULT OnClose( UINT, WPARAM, LPARAM, BOOL& ) { DestroyWindow(); m_owner.Detach(); return 0; } CMyWindow m_owner; CWindow m_ed; }; CMyDialog dlg; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { _Module.Init( NULL, hInstance ); CMyWindow win; win.Create( NULL, CWindow::rcDefault, _T("modeless dialog test"), WS_OVERLAPPEDWINDOW|WS_VISIBLE ); dlg.Create( win ); dlg.ShowWindow( SW_SHOW ); MSG msg; while( GetMessage( &msg, NULL, 0, 0 ) ){ if( !IsWindow(dlg) || !dlg.IsDialogMessage( &msg ) ){ DispatchMessage( &msg ); } } _Module.Term(); return 0; }指定H口cȝ信息Q?/strong>
CMyWindow wnd; wnd.Create( NULL, CWindow::rcDefault, _T("Hello"), WS_OVERLAPPEDWINDOW|WS_VISIBLE );如果你不指定M样式和扩展样式,ATL用默认的样式Q这些默认的样式是作为窗口的特征定义的,默认特征是CControlWinTraitsQ定义如下:
typedef CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |WS_CLIPSIBLINGS, 0> CControlWinTraits;CWinTraits是一个模板类Q它需?个参敎ͼH口样式、扩展窗口样式?
template <class T, class TBase = CWindow, class TWinTraits = CControlWinTraits> class CWindowImpl : public ...所以在默认情况下,从CWindowImplz的窗口都h可视、子H口、裁剪兄弟窗口、裁减子H口的属性?
typedef CWinTraits<WS_OVERLAPPEDWINDOW|WS_VISIBLE,0> MyTraits;然后Q从CWindowImplz一个窗口类Q指定自qH口特征Q?
class CMyWindow: public CWindowImpl<CMyWindow,CWindow,MyTraits> {...};或者象下面q样更加直接Q?
class CMyWindow: public CWindowImpl< CMyWindow, CWindow, CWinTraits<WS_OVERLAPPEDWINDOW|WS_VISIBLE,0> > {...};注意Q我们必L供全部的三个模板参数Q派生类Q基c(CWindowQ和特征cR?
CMyWindow wnd; wnd.Create( NULL, CWindow::rcDefault, _T("Hello") ); // style: WS_OVERLAPPEDWINDOW|WS_VISIBLE我们也可以重写窗口特征:
ovwnd.Create( NULL, CWindow::rcDefault, _T("Hello"), WS_OVERLAPPEDWINDOW ); // not visibleH口特征也可以包含扩展样式:
class CClientWindow: public CWindowImpl<CClientWindow, CWindow, CWinTraits< WS_OVERLAPPEDWINDOW|WS_VISIBLE, WS_EX_CLIENTEDGE > > {...};DECLARE_WND_CLASS
DECLARE_WND_CLASS("my window class");q等价于Q?pre>DECLARE_WND_CLASS_EX( "my window class", CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, // default style COLOR_WINDOW // default color ); DECLARE_WND_CLASS_EX
class CMyWindow: public CWindowImpl<CMyWindow> { public: DECLARE_WND_CLASS_EX( "my window class", // class name CS_HREDRAW|CS_VREDRAW, // class style COLOR_WINDOW // background color ); BEGIN_MSG_MAP(CMyWindow) ...所谓的H口cd是指注册的窗口类的名字,如果我们不指定窗口类名,ATL自动生成一个,但是当我们用Spy++之类的工L时候,你将会发现我们自己取的类名比"ATL:00424bd0"之类的名字要有用得多?
#define DECLARE_WND_CLASS(WndClassName) \ static CWndClassInfo& GetWndClassInfo() \ { \ static CWndClassInfo wc = \ { \ { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, \ StartWindowProc, \ 0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, \ WndClassName, NULL }, \ NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \ }; \ return wc; \ }CWndClassInfol构提供了更灉|的自定义的可能,它是q样定义的:
struct CWndClassInfo { struct WNDCLASSEX { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName; HICON hIconSm; } m_wc; LPCSTR m_lpszOrigName; WNDPROC pWndProc; LPCSTR m_lpszCursorID; BOOL m_bSystemCursor; ATOM m_atom; CHAR m_szAutoName[13]; ATOM Register(WNDPROC* p); };例如Q要指定一个窗口的指针Q我们可以将m_lpszCursorID讄为指针的名字Q如果它是一个系l指针,m_bSystemCursor讄为TRUEQ否则设|ؓFALSE。注意DECLARE_WND_CLASS宏是怎样这两个成员变量分别讄为IDC_ARROW ?TRUE的。既然DECLARE_WND_宏不能让我们改写q些默认的|我们可以q样做:
class CMyWindow: public CWindowImpl<CMyWindow> { public: static CWndClassInfo& GetWndClassInfo() { // a manual DECLARE_WND_CLASS macro expansion // modified to specify an application-defined cursor: static CWndClassInfo wc = { { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, 0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, "MyWindow", NULL }, NULL, NULL, MAKEINTRESOURCE(IDC_CURSOR1), FALSE, 0, _T("") }; return wc; } ...l论Q?/strong>
class CMyWindow: public CWindowImplCmyWindow是一个容器窗口类Q它的构造函数对CcontainedWindowcd的成员做q样的初始化Q被包含的窗口是~辑框,发送它的消息到“this”(它的父窗口)Q用可选消息映表99?pre>BEGIN_MSG_MAP( CMyWindow ) MESSAGE_HANDLER( WM_CREATE, OnCreate ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) ALT_MSG_MAP( 99 ) // contained window''s messages come here... MESSAGE_HANDLER( WM_CHAR, OnChar ) END_MSG_MAP() 当父H口被创建的时候,被包含的H口也被创徏Q在WM_CREATE消息的响应函CQ。因包含的控件是以编辑框为基的,所以它在屏q上看v来象一个编辑框Q?{ CContainedWindow m_contained; public: CMyWindow(): m_contained( _T("edit"), this, 99 ) { } ...
LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& ) { RECT rc = { 10, 10, 200, 35 }; m_contained.Create( *this, rc, _T("non-numeric edit"), WS_CHILD|WS_VISIBLE|WS_BORDER, 0, 666 ); return 0; }在这个例子中Q容器窗口同时也是被包含H口的父H口?
LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL& bHandled ) { TCHAR ch = wParam; if( _T(''0'') <= ch && ch <= _T(''9'') ) MessageBeep( 0 ); else bHandled = FALSE; return 0; } LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ) { PostQuitMessage( 0 ); return 0; } };我们同样也可以用被包含的H口来子cd对话框中已经存在的控Ӟ和正规的子类化不同,被子cd的窗口的消息时被容器H口捕获的。在下面的例子中Q一个对话框子类化了一个编辑框控gQ把它{化成了被包含的窗口;那个对话框(容器Q捕获WM_CHAR消息q忽略掉数字字符Q然后在发送到~辑框控件。(CdialogImpl在ATL中的对话框类一节讲q。)
class CMyDialog: public CDialogImpl<CMyDialog> { public: enum { IDD = IDD_DIALOG1 }; // contained window is an edit control: CMyDialog(): m_contained( "edit", this, 123 ) { } BEGIN_MSG_MAP( CMyDialog ) MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog ) ALT_MSG_MAP( 123 ) // contained window''s messages come here... MESSAGE_HANDLER( WM_CHAR, OnChar ) END_MSG_MAP() LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& bHandled ) { // when the dialog box is created, subclass its edit control: m_contained.SubclassWindow( GetDlgItem(IDC_EDIT1) ); bHandled = FALSE; return 0; } LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL& bHandled ) { TCHAR ch = wParam; if( _T(''0'') <= ch && ch <= _T(''9'') ) MessageBeep( 0 ); else bHandled = FALSE; return 0; } CContainedWindow m_contained; };消息反射Q?/strong>
class CParentWindow: CWindowImpl<CParentWindow> { // 假设q个H口有一个按钮型的子H口Q? // q且?ID ?ID_BUTTON BEGIN_MSG_MAP( CParentWindow ) COMMAND_ID_HANDLER( ID_BUTTON, OnButton ) MESSAGE_HANDLER( WM_CTLCOLORBUTTON, OnColorButton ) ...当按钮被按下的时候,它发送一个命令消息给父窗口,然后CParentWindow::OnButton被调用。同理,当按钮需要被l制的时候,它发送WM_CTLCOLORBUTTON消息l父H口QCParentWindow::OnColorButton响应q个消息Q它使用特定的画L制控件?
class CParentWindow: CWindowImpl当父H口收到一个消息,先查扑֮的消息映表Q如果没有和q个消息相匹配的入口Q则REFLECT_NOTIFICATIONS宏得该消息被反给发送这个消息的控g。控件可以提供响应反消息的处理函数Q如下:{ BEGIN_MSG_MAP( CParentWindow ) MESSAGE_HANDLER( WM_CREATE, OnCreate ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) ...other messages that CParentWindow will handle... REFLECT_NOTIFICATIONS() END_MSG_MAP() ...
class CHandlesItsOwnMessages: CWindowImpl<CHandlesItsOwnMessage> { public: DECLARE_WND_SUPERCLASS( _T("Superbutton"), _T("button") ) BEGIN_MSG_MAP( CHandlesItsOwnMessage ) MESSAGE_HANDLER( OCM_COMMAND, OnCommand ) MESSAGE_HANDLER( OCM_CTLCOLORBUTTON, OnColorButton ) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() ...注意Q反消息的消息标志以OCM_开_而不是WM_。这可以让你区分q个消息I竟是否是被反射回来的?
// in CParentWindow: CHandlesItsOwnMessages m_button; LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& ) { RECT rc; // initialize appropriately m_button.Create( *this, rc, _T("click me"), WS_CHILD|WS_VISIBLE ); ...或者,如果q个按钮控g是已存在的(例如Q父H口是一个对话框Q:
m_button.SubclassWindow( GetDlgItem(ID_BUTTON) );下面的例子定义了一个CstaticLinkc,它是一个Static控gQ当点击它的时候,打开一个指定的|页。所有从CstaticLink发送出ȝ消息都被它的父窗口反回来(在这个例子中Q用到对话框Q请看ATL中的对话框类q一节)。除了响应反回的命令消息,CstaticLinkq处理反回的WM_CTLCOLORSTATIC消息以便它能够让自己在点d和点d昄不同的颜艌Ӏ?
#include "stdafx.h" #include "resource.h" CComModule _Module; class CStaticLink : public CWindowImpl<CStaticLink> { /* Based on CStaticLink by Paul DiLascia, C++ Q&A, Microsoft Systems Journal 12/1997. Turns static controls into clickable "links" -- when the control is clicked, the file/program/webpage named in the control''s text (or set by SetLinkText()) is opened via ShellExecute(). Static control can be either text or graphic (bitmap, icon, etc.). */ public: DECLARE_WND_SUPERCLASS( _T("StaticLink"), _T("Static") ) CStaticLink() : m_colorUnvisited( RGB(0,0,255) ), m_colorVisited( RGB(128,0,128) ), m_bVisited( FALSE ), m_hFont( NULL ) { } void SetLinkText( LPCTSTR szLink ) { USES_CONVERSION; m_bstrLink = T2OLE( szLink ); } BEGIN_MSG_MAP(CStaticLink) // uses message reflection: WM_* comes back as OCM_* MESSAGE_HANDLER( OCM_COMMAND, OnCommand ) MESSAGE_HANDLER( OCM_CTLCOLORSTATIC, OnCtlColor ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) // not a reflected message DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ) { if( m_hFont ) DeleteObject( m_hFont ); return 0; } LRESULT OnCommand( UINT, WPARAM wParam, LPARAM, BOOL& ) { USES_CONVERSION; int code = HIWORD( wParam ); if( code == STN_CLICKED || code == STN_DBLCLK ){ if( m_bstrLink.Length() == 0 ){ GetWindowText( &m_bstrLink ); } if( (int)ShellExecute( *this, _T("open"), OLE2T(m_bstrLink), NULL, NULL, SW_SHOWNORMAL ) > 32 ){ m_bVisited = TRUE; // return codes > 32 => success Invalidate(); }else{ MessageBeep( 0 ); ATLTRACE( _T("Error: CStaticLink couldn''t open file") ); } } return 0; } LRESULT OnCtlColor( UINT, WPARAM wParam, LPARAM, BOOL& ) { // notify bit must be set to get STN_* notifications ModifyStyle( 0, SS_NOTIFY ); HBRUSH hBr = NULL; if( (GetStyle() & 0xff) <= SS_RIGHT ){ // it''s a text control: set up font and colors if( !m_hFont ){ LOGFONT lf; GetObject( GetFont(), sizeof(lf), &lf ); lf.lfUnderline = TRUE; m_hFont = CreateFontIndirect( &lf ); } HDC hDC = (HDC)wParam; SelectObject( hDC, m_hFont ); SetTextColor( hDC, m_bVisited ? m_colorVisited : m_colorUnvisited ); SetBkMode( hDC, TRANSPARENT ); hBr = (HBRUSH)GetStockObject( HOLLOW_BRUSH ); } return (LRESULT)hBr; } private: COLORREF m_colorUnvisited; COLORREF m_colorVisited; BOOL m_bVisited; HFONT m_hFont; CComBSTR m_bstrLink; }; // CStaticLink class CReflectDlg : public CDialogImpl<CReflectDlg> { public: enum { IDD = IDD_DIALOG1 }; BEGIN_MSG_MAP(CReflectDlg) COMMAND_RANGE_HANDLER( IDOK, IDCANCEL, OnClose ) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) REFLECT_NOTIFICATIONS() // reflect messages back to static links END_MSG_MAP() LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) { CenterWindow( GetParent() ); // a textual static control: s1.SubclassWindow( GetDlgItem(IDS_TEST1) ); // a static control displaying an icon s2.SubclassWindow( GetDlgItem(IDS_TEST2) ); // set the icon''s link s2.SetLinkText( _T("http://www.microsoft.com") ); return 1; } LRESULT OnClose(UINT, WPARAM wID, HWND, BOOL& ) { ::EndDialog( m_hWnd, wID ); return 0; } private: CStaticLink s1, s2; }; // CReflectDlg int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) { _Module.Init( NULL, hInstance ); CReflectDlg dlg; dlg.DoModal(); _Module.Term(); return 0; }
HWND hWnd = ::CreateWindow( "button", "Click me", WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); ::ShowWindow( hWnd, nCmdShow ); ::UpdateWindow( hWnd );使用ATL中的CWindowcdQ等效代码如下:
CWindow win; win.Create( "button", NULL, CWindow::rcDefault, "Click me", WS_CHILD ); win.ShowWindow( nCmdShow ); win.UpdateWindow();我们应该在我们的大脑中我们应该保持这样一个概念:ATL的窗口对象与Windowspȝ中的H口是不同的。Windowspȝ中的H口指的是操作系l中l持的一块数据,操作pȝ靠这块数据来操作屏幕上的一块区域。而一个ATLH口对象Q是CWindowcȝ一个实例,它是一个C++对象Q它的内部没有保存Q何有兛_q区域或者窗口数据结构的内容Q只保存了一个窗口的句柄Q这个句柄保存在它的数据成员m_hWnd中,CWindow对象和它在屏q上昄出来的窗口就是靠q个句柄联系h的?br />理解了ATL中的H口对象和Windowspȝ中窗口的区别Q就更加Ҏ理解CWindow对象的构造与H口的创建是两个分开的过E。我们再看看前面的代码,׃发现Q首先是一个CWindow对象被构造:
CWindow win;然后创徏它的H口Q?pre>win.Create( "button", NULL, CWindow::rcDefault, "Click me", WS_CHILD ); 我们也可以构造一个CWindow对象Q然后把它和一个已l存在的H口兌hQ这h们就可以通过CWindowcȝ成员函数来操作这个已l存在的H口。这U方法非常有用,因ؓCWindowcL供的函数都是装好了的,用v来很方便Q比如CWindowcM的CenterWindow, GetDescendantWindow{函数用h比直接使用Windows API方便得多?
HWND hWnd = CreateWindow( szWndClass, "Main window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); // 下面的方法中可以任选一U: // CWindow win( hWnd ); // 通过构造函数关? // ? // CWindow win; // win = hWnd; // 通过赋值操作符兌 // ? // CWindow win; // win.Attach( hWnd ); // 使用Attach()Ҏ兌 win.CenterWindow(); // 现在可以使用win对象来代替hWndq行操作 win.ShowWindow( nCmdShow ); win.UpdateWindow();CWindowcM提供了一个HWND操作W,可以把CWindowcȝ对象转化为窗口句柄,q样QQ何要求用HWND的地斚w可以使用CWindowcȝ对象代替Q?
::ShowWindow( win, nCmdShow ); // 此API函数本来要求HWNDcd的参?/pre>CWindowcM得对H口的操作更单,而且不会增加pȝ开销——它l过~译和优化后的代码与使用UAPI~程的代码是{h的?br />
不幸的是QCWindowcM能让我们自己军_H口如何响应消息。当Ӟ我们可以使用CWindowcL供的Ҏ来一个窗口居中或隐藏Q甚臛_以向一个窗口发送消息,但是当窗口收到消息后怎么处理则取决于创徏q个H口时用的H口c,如果我们是创建的是”button”类的窗口,那么它的表现p个按钮,如果用”listbox”类创徏Q那它就h跟列表框相同的行为,使用CWindowcL们没有办法改变这炏V幸好,ATL为我们提供了另外一个类CWindowImplQ它允许我们指定H口的新行ؓ?
CWindowImpl:
CWindowImplcL从CWindowcL生的Q所以我们依然可以用CWindowcM的成员函敎ͼ但是CWindowImplcȝ功能更强大,它允许我们指定窗口怎样处理消息。在传统的窗口编E中Q如果我们要处理H口消息Q我们必M用窗口函敎ͼ但是使用ATLQ我们只需要在我们的ATLH口cM定义一个消息映?
首先Q从CWindowImplcL生自qH口c,如下Q?class CMyWindow : public CWindowImpl注意Q我们自qcd必须作ؓ一个模版参C递给CWindowImplcR?{
然后在类的定义里面定义如下的消息映射Q?BEGIN_MSG_MAP(CMyWindow) MESSAGE_HANDLER(WM_PAINT,OnPaint) MESSAGE_HANDLER(WM_CREATE,OnCreate) MESSAGE_HANDLER(WM_DESTROY,OnDestroy) END_MSG_MAP()下面q句MESSAGE_HANDLER(WM_PAINT,OnPaint)的意思是Q当WM_PAINT消息到达Ӟ调用CMyWindow::OnPaint成员函数?
最后就是定义处理消息的函数了,如下Q?LRESULT OnPaint( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { ... } LRESULT OnCreate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { ... } LRESULT OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { ... } }; // CmyWindowq些函数中的参数意义为:W一个是消息IDQ中间的两个参数的意义取决于消息cdQ第四个参数是一个标志,用它来决定这个消息是已经处理完了q是需要进一步的处理。关于这些参敎ͼ我们在Message Map结有更详细的讨论?
当窗口收C个消息,它将从消息映表的顶部开始查扑配的消息处理函数Q因此把最常用的消息放在消息映表的前面是个不错的注意。如果没有找到匹配的消息处理函数Q则q个消息被发送到默认的窗口过E进行处理?
ATL的消息映表装了Windows的消息处理过E,它比传统的窗口函C的大量switch分支或者if语句看v来更加直观?
要创Z个基于CWindowImplzcȝH口Q请调用CWindowImplcȝCreateҎQ?CMyWindow wnd; // 构造一?CMyWindow cȝ对象 wnd.Create( NULL, CWindow::rcDefault, _T("Hello"), WS_OVERLAPPEDWINDOW|WS_VISIBLE );注意QCWindowImplcȝCreateҎ与CWindowcȝCreateҎ略有不同Q在CWindowcȝCreate中,我们必须指定一个注册了的窗口类Q但是CWindowImpl则不同,它创Z个新的窗口类Q因此,不需要ؓ它指定窗口类?
一个简单而完整的CZQ?br />
q篇文章中的大部分示例都只是代码片段Q但是下面列出的是一个完整的Hello world的示例程序。虽然我们用的是ATLQ但是没有涉及到COMQ因此在使用Visual C++®建立目的时候,我们选择Win32® application而不是ATL COMQ?
在stdafx.h文g中,加入下面几行Q?#include <atlbase.h> extern CComModule _Module; #include <atlwin.h>在hello.cpp文g中,写如下代码:#include "stdafx.h" CComModule _Module; class CMyWindow : public CWindowImpl<CMyWindow> { BEGIN_MSG_MAP( CMyWindow ) MESSAGE_HANDLER( WM_PAINT, OnPaint ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) END_MSG_MAP() LRESULT OnPaint( UINT, WPARAM, LPARAM, BOOL& ){ PAINTSTRUCT ps; HDC hDC = GetDC(); BeginPaint( &ps ); TextOut( hDC, 0, 0, _T("Hello world"), 11 ); EndPaint( &ps ); return 0; } LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ){ PostQuitMessage( 0 ); return 0; } }; int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR, int ) { _Module.Init( NULL, hInstance ); CMyWindow wnd; wnd.Create( NULL, CWindow::rcDefault, _T("Hello"), WS_OVERLAPPEDWINDOW|WS_VISIBLE ); MSG msg; while( GetMessage( &msg, NULL, 0, 0 ) ){ TranslateMessage( &msg ); DispatchMessage( &msg ); } _Module.Term(); return msg.wParam; }在这个示例程序中QCmyWindow是从CWindowImplz的,它的消息映射捕获了两个消息WM_PAINT和WM_DESTROYQ当收到WM_PAINT消息Ӟ它的成员函数OnPaint处理q个消息q在H口上输出“Hello world”,当收到WM_DESTROY消息Ӟ也就是当用户关闭q个H口的时候,调用OnDestroy函数处理q个消息Q在OnDestroy函数中调用PostQuitMessage来结束消息@环?
WinMain函数中创Z一个CmyWindowcȝ实例q实C一个标准的消息循环。(有一些地方,我们必须遵@ATL的规范,比如在这里我们必M用_Module。)
消息映射Q?
有三l用于消息映的宏,他们分别是:
H口消息映射宏:
有两个窗口消息映宏Q他们分别是Q?/p>
W一个宏一个特定的消息映射到相应的处理函数Q第二个宏将一l消息映到一个处理函数。消息处理函数都要求h如下的原形:
LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
其中Q参数uMsg是消息标识,wParam和lParam是两个附加与消息的参敎ͼQ他们的具体意义取决与消息类别。)
消息处理函数使用bHandled来标志消息是否已l被完全捕获Q如果bHandled被设|成FALSEQ程序将l箋在消息映表的后l部分查找这个消息的其它处理函数。这个特性得我们对一个消息用多个处理函数成为可能。什么时候需要对一个消息用多个处理函数呢Q可能是在对多个c链接时Q也可能是我们只惛_一个消息做出响应但是ƈ不真正捕获它。在处理函数被调用之前,bHandled被置为TRUEQ所以如果我们不在函数的l尾昑ּ地将它置为FALSEQ则消息映射表的后箋部分不会被l查找,也不会有其它的处理函数被调用?
命o消息映射宏:
命o消息映射宏只处理命o消息QWM_COMMAND消息Q,但是它能让我们根据消息类型或者发送命令消息的控gID来指定消息处理函数?
命o消息处理函数应该h如下的原形:
LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
其中Q参数wNotifyCode代表消息代码QwID代表发送消息的控g的IDQhWndCtl代表发送消息的控g的窗口句柄,bHandled的意义如前所q?
通知消息映射宏:
通知消息映射宏用来处理通知消息QWM_NOTUFY消息Q,它根据通知消息的类型和发送通知消息的控件的不同消息映到不同的处理函敎ͼq些宏与前面讲的命o消息映射宏是{h的,唯一的不同就是它处理的是通知消息而不是命令消息?
通知消息处理函数都需要如下的原ŞQ?/p>
LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);其中Q参数idCtrl代表发送通知消息的控件的IDQ参数pnmh是指向一个NMHDRl构的指针,bHandled的意义如前所q?
NOTIFY_HANDLER( ID_LISTVIEW, LVN_ENDLABELEDIT, OnEndLabelEdit)q个通知消息附带的额外信息包含在一个NMLVDISPINFOl构中,因此Q消息处理函数看h应该象下面这个样子:
LRESULT OnEndLabelEdit(int idCtrl, LPNMHDR pnmh, BOOL& bHandled) { // The item is -1 if editing is being canceled. if ( ((NMLVDISPINFO*)pnmh)->item.iItem == -1) return FALSE; ...可以看出Qpnmh指针被{化成NMLVDISPINFO*cdQ以便访问头部结构以外的数据?
class CBase: public CWindowImpl< CBase > // simple base window class: shuts down app when closed { BEGIN_MSG_MAP( CBase ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) END_MSG_MAP() LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ) { PostQuitMessage( 0 ); return 0; } }; class CDerived: public CBase // derived from CBase; handles mouse button events { BEGIN_MSG_MAP( CDerived ) MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown ) END_MSG_MAP() LRESULT OnButtonDown( UINT, WPARAM, LPARAM, BOOL& ) { ATLTRACE( "button down\n" ); return 0; } }; // in WinMain(): ... CDerived win; win.Create( NULL, CWindow::rcDefault, "derived window" );可是Q上面的代码有一个问题。当我们在调试模式下q行q个E序Q一个窗口出CQ如果我们在q个H口中单击,“button down”将出现在输出窗口中Q这是CDrivedcȝ功能Q可是,当我们关闭这个窗口的时候,E序q不退出,管CBasecd理了WM_DESTROY消息q且CDrivedcL从CBasecL生的?
BEGIN_MSG_MAP( CDerived ) MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown ) CHAIN_MSG_MAP( CBase ) // 链接到基c? END_MSG_MAP()现在QQ何在CDrivedcM没有被处理的消息都会被传到CBasecM?
// in class CBase: BEGIN_MSG_MAP( CBase ) MESSAGE_HANDLER( WM_CREATE, OnCreate1 ) MESSAGE_HANDLER( WM_PAINT, OnPaint1 ) ALT_MSG_MAP( 100 ) MESSAGE_HANDLER( WM_CREATE, OnCreate2 ) MESSAGE_HANDLER( WM_PAINT, OnPaint2 ) ALT_MSG_MAP( 101) MESSAGE_HANDLER( WM_CREATE, OnCreate3 ) MESSAGE_HANDLER( WM_PAINT, OnPaint3 ) END_MSG_MAP()如上Q基cȝ消息映射表由3节组成:一个默认的消息映射表(隐含的标识ؓ0Q和两个可选的消息映射表(标识?00?01Q?
class CDerived: public CBase { BEGIN_MSG_MAP( CDerived ) CHAIN_MSG_MAP_ALT( CBase, 100 ) END_MSG_MAP() ...CDrivedcȝ消息映射表链接到CBasecM标识号ؓ100的可选节Q因此当WM_PAINT到达ӞCBase::OnPaint2被调用?
class CBeepButton: public CWindowImpl< CBeepButton > { public: DECLARE_WND_SUPERCLASS( _T("BeepButton"), _T("Button") ) BEGIN_MSG_MAP( CBeepButton ) MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLButtonDown ) END_MSG_MAP() LRESULT OnLButtonDown( UINT, WPARAM, LPARAM, BOOL& bHandled ) { MessageBeep( MB_ICONASTERISK ); bHandled = FALSE; // alternatively: DefWindowProc() return 0; } }; // CBeepButtonDECLARE_WND_SUPERCLASS宏声明了q个H口的类名(“BeepButton”)和被类化的cdQ“Button”)。它的消息映表只有一个入口项Q将WM_LBUTTONDOWN消息映射到OnLButtonDown函数。其余的消息都让默认的窗口过E处理,除了可以发出蜂鸣外,CbeepButton需要和其它的按钮表现相同,因此在OnLButtonDown函数的最后,需要将bHandled讄为FALSEQ让默认的窗口过E在OnLButtonDown函数完成后对WM_LBUTTONDOWN消息q行其它的处理。(另外的一U方法是直接调用DefWindowProc函数。)
const int ID_BUTTON1 = 101; const int ID_BUTTON2 = 102; class CMyWindow: public CWindowImpl< CMyWindow, CWindow, CWinTraits<WS_OVERLAPPEDWINDOW|WS_VISIBLE> > { CBeepButton b1, b2; BEGIN_MSG_MAP( CMyWindow ) MESSAGE_HANDLER( WM_CREATE, OnCreate ) COMMAND_CODE_HANDLER( BN_CLICKED, onClick ) END_MSG_MAP() LRESULT onClick(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { ATLTRACE( "Control %d clicked\n", wID ); return 0; } LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& ) { RECT r1 = { 10, 10, 250, 80 }; b1.Create(*this, r1, "beep1", WS_CHILD|WS_VISIBLE, 0, ID_BUTTON1); RECT r2 = { 10, 110, 250, 180 }; b2.Create(*this, r2, "beep2", WS_CHILD|WS_VISIBLE, 0, ID_BUTTON2); return 0; } }; // CMyWindowH口的子cdQ?/strong>
class CNoNumEdit: public CWindowImpl< CNoNumEdit > { BEGIN_MSG_MAP( CNoNumEdit ) MESSAGE_HANDLER( WM_CHAR, OnChar ) END_MSG_MAP() LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL& bHandled ) { TCHAR ch = wParam; if( _T(''0'') <= ch && ch <= _T(''9'') ) MessageBeep( 0 ); else bHandled = FALSE; return 0; } };q个cd处理一个消息WM_CHARQ如果这个字W是数字的话Q则调用MessageBeep( 0 )q返回,q样可以有效地忽略这个字W。如果不是数字,则将bHandled讄为FALSEQ指明默认的H口q程q个消息需要进一步处理?
class CMyDialog: public CDialogImpl<CMyDialog> { public: enum { IDD = IDD_DIALOG1 }; BEGIN_MSG_MAP( CMyDialog ) MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog ) END_MSG_MAP() LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& ) { ed.SubclassWindow( GetDlgItem( IDC_EDIT1 ) ); return 0; } CNoNumEdit ed; };
void f(void) { IUnknown *pUnk = 0; // 调用 HRESULT hr = GetSomeObject(&pUnk); if (SUCCEEDED(hr)) { // 使用 UseSomeObject(pUnk); // 释放 pUnk->Release(); } }q个模式在COME序员心中是如此Ҏ蒂固Q以至于他们常常不写实际使用指针的语句,而是先在代码块末敲入Release语句。这很像CE序员用switch语句时的条g反射一P先敲入break再说?br /> 其实调用Release实在不是什么可怕的负担Q但是,客户端程序员面两个相当严重的问题。第一个问题与获得多接口指针有兟뀂如果某个函数需要在做Q何实际工作之前获得三个接口指针,也就是说在第一个用指针的语句之前必须要由三个调用语句。在书写代码Ӟq常常意味着E序员需要写许多嵌套条g语句Q如Q?br />
void f(void) { IUnknown *rgpUnk[3]; HRESULT hr = GetObject(rgpUnk); if (SUCCEEDED(hr)) { hr = GetObject(rgpUnk + 1); if (SUCCEEDED(hr)) { hr = GetObject(rgpUnk + 2); if (SUCCEEDED(hr)) { UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]); rgpUnk[2]->Release(); } rgpUnk[1]->Release(); } rgpUnk[0]->Release(); } }像这L语句常常促ɽE序员将TAB键设|成一个或两个I格Q甚x愿用大一点的昄器。但事情q不L按你惌的那P׃U种原因目团队中的COMlg~程人员往往得不?所想的g支持Q而且在公司确定关于TAB键的使用标准之前Q程序员常常求助于用有很大争议但仍然有效的“GOTO”语句:
void f(void) { IUnknown *rgpUnk[3]; ZeroMemory(rgpUnk, sizeof(rgpUnk)); if (FAILED(GetObject(rgpUnk))) goto cleanup; if (FAILED(GetObject(rgpUnk+1))) goto cleanup; if (FAILED(GetObject(rgpUnk+2))) goto cleanup; UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]); cleanup: if (rgpUnk[0]) rgpUnk[0]->Release(); if (rgpUnk[1]) rgpUnk[1]->Release(); if (rgpUnk[2]) rgpUnk[2]->Release(); }q样的代码虽然不那么专业Q但臛_减少了屏q的水^滚动?br />使用以上q些代码D|在着更加手的问题,那就是在到C++异常时。如果函数UseObjects丢出异常Q则释放指针的代码被完全屏蔽掉了?解决q个问题的一个方法是使用Win32的结构化异常处理QSEHQ进行终l操作:
void f(void) { IUnknown *rgpUnk[3]; ZeroMemory(rgpUnk, sizeof(rgpUnk)); __try { if (FAILED(GetObject(rgpUnk))) leave; if (FAILED(GetObject(rgpUnk+1))) leave; if (FAILED(GetObject(rgpUnk+2))) leave; UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]); } __finally { if (rgpUnk[0]) rgpUnk[0]->Release(); if (rgpUnk[1]) rgpUnk[1]->Release(); if (rgpUnk[2]) rgpUnk[2]->Release(); }可惜Win32 SHE在C++中的表现q不如想象得那么好。较好的Ҏ是用内建的C++异常处理模型Q同时停止用没有加工过的指针。标准C++库有一个类Qauto_ptrQ在其析构函C?M一个操作指针的delete调用Q即使在出现异常时也能保证调用)。与之类|ATL有一个COM指针QCComPtrQ它的析构函C正确调用Release?br /> CComPtrcd现客L基本的COM引用计数模型。CComPtr有一个数据成员,它是一个未l过M加工的COM接口指针。其cd被作为模板参C递:
CComPtr<IUnknown> unk; CComPtr<IClassFactory> cf;~省的构造函数将q个原始指针数据成员初始化ؓNULL。智能指针也有构造函敎ͼ它的参数要么是原始指针,要么是相同类型的参数。不论哪U情况,指针都调用AddRef控制引用。CComPtr的赋值操作符 既可以处理原始指针,也可以处理智能指针,q且在调用新分配指针的AddRef之前自动释放保存的指针。最重要的是QCComPtr的析构函数释放保存的接口Q如果非I)。请看下列代码:
void f(IUnknown *pUnk1, IUnknown *pUnk2) { // 如果非空Q构造函数调用pUnk1的AddRef CComPtr除了正确实现COM的AddRef ?Release规则之外QCComPtrq允许实现原始和指针的透明操作Q参?a >附表?/a>所C。也是说下面的代码按照你所惌的方式运行:unk1(pUnk1); // 如果非空Q构造函数调用unk1.p的AddRef CComPtr unk2 = unk1; // 如果非空Qoperator= 调用unk1.p的Releaseq且 //如果非空Q调用unk2.p的AddRef unk1 = unk2; //如果非空Q析构函数释放unk1 ?unk2 }
void f(IUnknown *pUnkCO) { CComPtr除了~Z对Release的显式调用外Q这D代码像是纯_的COM代码。有了CComPtrcȝ武装Q前面所遇到的麻烦问题顿时变得简单了Q?br />cf; HRESULT hr; // 使用操作W?& 获得?&cf.p 的存? hr = pUnkCO->QueryInterface(IID_IClassFactory,(void**)&cf); if (FAILED(hr)) throw hr; CComPtr unk; // 操作W?-> 获得对cf.p的存? // 操作W?& 获得?&unk.p的存? hr = cf->CreateInstance(0, IID_IUnknown, (void**)&unk); if (FAILED(hr)) throw hr; // 操作W?IUnknown * q回 unk.p UseObject(unk); // 析构函数释放unk.p ?cf.p }
void f(void) {׃CComPtrҎ作符重蝲用法的扩展,使得代码的编译和q行无懈可击?br /> 假定模板cȝ道它所操纵的指针类型,你可能会问:那ؓ什么智能指针不能在它的功能操作W或构造函C自动调用QueryInterfaceQ从而更有效地包装IUnknown呢?在Visual C++ 5.0出来以前Q没有办法将某个接口的GUID与它的本w的C++cd兌h——Visual C++ 5.0用私有的declspec某个IID与一个接口定义绑定在一赗因为ATL的设?考虑C它要与大量不同的C++~译器一起工作,它需要用与编译器无关的手D|供GUID。下面我们来探讨另一个类——CComQIPtrcR?br /> CComQIPtr与CComPtr关系很密切(实际上,它只增加了两个成员函敎ͼ。CComQIPtr必须要两个模板参敎ͼ一个是被操U늚指针cd Q另一个是对应于这个指针类型的GUID。例如,下列代码声明了操UIDataObject 和IPersist接口的智能指针:
CComPtr<IUnknown> rgpUnk[3];
if (FAILED(GetObject(&rgpUnk[0]))) return;
if (FAILED(GetObject(&rgpUnk[1]))) return;
if (FAILED(GetObject(&rgpUnk[2]))) return;
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
}
CComQIPtr<IDataObject, &IID_IDataObject> do; CComQIPtr<IPersist, &IID_IPersist> p;CComQIPtr的优Ҏ它有重蝲的构造函数和赋值操作符。同cȝ本(例如Q接受相同类型的接口Q仅仅AddRef双的赋?初始化操作。这实际上就是CComPtr的功能。异cȝ本(接受cd不一致的接口Q正调用QueryInterface来决定是否这个对象确实支持所h的接口:
void f(IPersist *pPersist) { CComQIPtr<IPersist, &IID_IPersist> p; // 同类赋?- AddRef''s p = pPersist; CComQIPtr<IDataObject, &IID_IDataObject> do; // 异类赋?- QueryInterface''s do = pPersist; }在第二种赋D句中Q因为pPersist是非IDataObject *cdQ但它是z于IUnknown的接口指针,CComQIPtr通过pPersist调用QueryInterface来试图获得这个对象的IDataObject接口指针。如果QueryInterface调用成功Q则此智能指针将含有作ؓl果的原始IDataObject指针。如果QueryInterface调用p|Q则do.p被|ؓnull。如果QueryInterfaceq回的HRESULT值很重要Q但又没有办法从赋值操作获得其值时Q则必须昑ּ调用QueryInterface?br /> 既然有了CComQIPtrQ那Z么还要CComPtr呢?由几个理由:首先QATL最初的发布版本只支持CComPtrQ所以它׃直合法地保留下来了。其二(也是最重要的理由)Q由于重载的构造函数和赋值操作,对IUnknown使用CComQIPtr是非法的。因为所有COM接口的类型定义都必须与IUnknown兼容?br />
CComPtr<IUnknown> unk;从功能上它{同?
CComQIPtr<IUnknown, &IID_IUnknown> unk;前者正。后者是错误的用法。如果你q样写了QC++~译器将提醒你改正?br /> CComPtr作ؓ首选的另外一个理由可能是一些开发h员相信静悄悄地调用QueryInterfaceQ没有警告,削弱了C++pȝ的类型。毕竟,C++在没有进行强制类型{换的情况下不允许对类型不一致的原始指针 q行赋值操作,所以ؓ什么要用智能指针的道理也在q,q运的是开发h员可以选择最能满需要的指针cd?br /> 许多开发h员将指针看成是对q于的复杂编EQ务的化。我最初也是这么认为的。但只要留意它们使用COM指针的方法。就会逐渐认识到它们引入的潜在危险与它们解决的问题一样多?br />关于q一点,我用一个现成的使用原始指针的函Cؓ例:
void f(void) { IFoo *pFoo = 0; HRESULT hr = GetSomeObject(&pFoo); if (SUCCEEDED(hr)) { UseSomeObject(pFoo); pFoo->Release(); } }它自然而然转换C用CComPtr?br />
void f(void) { CComPtr<IFoo> pFoo = 0; HRESULT hr = GetSomeObject(&pFoo); if (SUCCEEDED(hr)) { UseSomeObject(pFoo); pFoo->Release(); } }注意CComPtr ?CComQIPtr输出所有受控接口成员,包括AddRef和Release。可惜当客户端通过操作W?>的结果调用ReleaseӞ指针很健?Q会二次调用构造函C的Release。显然这是错误的Q编译器和链接器也欣然接受了q个代码。如果你q气好的话,调试器会很快捕获到这个错误?br /> 使用ATL指针的另一个要引v注意的风险是cd强制转换操作W对原始指针提供的访问。如果隐式强制{换操作符的用存在争议。当 ANSI/ISO C++ 委员会在军_采用某个C++串类Ӟ他们明确止隐式cd转换。而是要求必须昑ּ使用c_str函数在需要常量char *Qconst char *Q的地方传递标准C++丌ӀATL提供了一U隐含式的类型{换操作符利地解决了q个问题。通常Q这个{换操作符可以Ҏ你的喜好来用,允许你将指针传递到需要用原始指针的函数?br />
void f(IUnknown *pUnk) { CComPtrq段代码能正运行,但是下面的代码也不会产生警告信息Q编译正帔R过Q?unk = pUnk; // 隐式调用操作WIUnknown *() CoLockObjectExternal(unk, TRUE, TRUE); }
HRESULT CFoo::Clone(IUnknown **ppUnk) { CComPtr在这U情况下Q智能指针(unkQ对原始值针**ppUnk的赋D发了与前面代码段相同的强制类型{换。在W一个例子中Q不需要用AddRef。在W二个例子中Q必要用AddRef?br /> 有关使用指针的更详细一般信息,请参见Scott Meyer的《More Effective C++》(Addison-Wesley, 1995q出版)。国内目前还没有q本书的中译本或影印本。有关COM指针的更多特定信息,请参见Don Box的一关于智能指针的专题文章http://www.develop.com/dbox/cxx/SmartPointer.htm?Q待l)unk; CoCreateInstance(CLSID_Foo, 0, CLSCTX_ALL, IID_IUnknown, (void **) &unk); // 隐式调用操作WIUnknown *() *ppUnk = unk; return S_OK; }
2个智能指针的模板包装c,CComPtr<> ?CComQIPtr<>Q这两个c都?<atlbase.h> 中声明。CComQIPtr<>
包含?CComPtr<>的所有功能,因此我们可以完全?CComQIPtr<> 来用智能接口指针,唯一要说明的一点就
是:CComQIPtr<> ׃使用了运符的重载功能,它会自动帮我们调用QueryInterface()函数Q因?
CComQIPtr<> 唯一的缺点就是不能定?IUnknown * 指针?/p>
// 指针 smart pointerQ按照匈牙利命名法,一般以 sp 开头来表示变量cd
CComPtr < IUnknown > spUnk; // 正确
// 假设 IFun 是一个接口类?br /> CComPtr < IFun > spFun; // 正确
CComQIPtr < IFun > spFun; // 正确
CComQIPtr < IFun, &IID_IFun > spFun; // 正确
CComQIPtr < IUnknown > spUnk; // 错误QCComQIPtr不能定义IUnknown指针
l智能指针赋值的ҎQ CComQIPtr < IFun > spFun; // 调用构造函敎ͼq没有赋|被包装的内部
接口指针?NULL
CComQIPtr < IFun > spFun( pOtherInterface ); // 调用构造函敎ͼ内部接口指针赋gؓ
// 通过 pOtherInterface q个普通接口指针调用QueryInterface()得到的IFun接口指针
CComQIPtr < IFun > spFun ( pUnknown ); // 调用构造函敎ͼ由IUnknown的QueryInterface()得到
IFun接口指针
CComQIPtr < IFun > spFun = pOtherInterface; // = q算W重载,含义和上面一?br /> spFun = spOtherInterface; // 同上
spFun = pUnknown; // 同上
pUnknown->QueryInterface( IID_IFun, &sp ); // 也可以通过QueryInterface赋?br />
// 指针赋值后Q可以用条g语句判断是否合法有效
if ( spFun ){} // 如果指针有效
if ( NULL != spFun ){} // 如果指针有效
if ( !spFun ){} // 如果指针无效
if ( NULL == spFun ){} // 如果指针无效
指针调用函数的方法: spFun.CoCreateInstance(...); // {h?API
函数::CoCreateInstance(...)
spFun.QueryInterface(...); // {h?API 函数::QueryInterface()
spFun->Add(...); // 调用内部接口指针的接口函?/p>
// 调用内部接口指针的QueryInterface()函数Q其实效果和 spFun.QueryInterface(...) 一?br /> spFun->QueryInterface(...);
spFun.Release(); // 释放内部的接口指针,同时内部指针赋gؓ NULL
spFun->Release(); // 错!Q!一定不要这么用?br /> // 因ؓq个调用q不把内部指针清I,那么析构的时候会被再ơ释放(释放了两ơ)
typedef HRESULT (WINAPI * FREG)(); TCHAR szWorkPath[ MAX_PATH ]; ::GetCurrentDirectory( sizeof(szWorkPath), szWorkPath ); // 保存当前q程的工作目? ::SetCurrentDirectory( lg目录 ); // 切换到组件的目录 HMODULE hDLL = ::LoadLibrary( lg文g?); // 动态装载组? if(hDLL) { FREG lpfunc = (FREG)::GetProcAddress( hDLL, _T("DllRegisterServer") ); // 取得注册函数指针 // 如果是反注册Q可以取?DllUnregisterServer"函数指针 if ( lpfunc ) lpfunc(); // 执行注册。这里ؓ了简单,没有判断q回? ::FreeLibrary(hDLL); } ::SetCurrentDirectory(szWorkPath); // 切换回原先的q程工作目录上面的示例,在多数情况下可以化掉切换工作目录的代码部分。但是,如果q个lg在装载的时候,它需要同时加载一些必M赖的DLLӞ有可能由于它自nE序?BUG D无法正确定位。咳......q是让我们自己写的程序,来I补它的错误吧......谁让׃是好人呢 Q谁让咱们的水^比他高呢Q谁让咱们在 vckbase 上是个“榜眼”呢......
#include Ҏ | IDL~译后,为方便C/C++E序员的使用Q会产生xxx.h和xxx_i.c文g。我们真q福Q直?include后就可以使用?/td> |
#import Ҏ | 比较通用的方法,vc 会帮我们产生包装c,让我们的调用更方?/td> |
加蝲cd库包装类 Ҏ | 如果lg提供?IDispatch 接口Q用q个Ҏ调用lg是最单的啦。不q还没讲IDispatchQ只能看以后的文章啦 |
加蝲ActiveX包装c?Ҏ | ActiveX q没介绍呢,以后再说?/td> |
下蝲CZE序后,请逐项览使用ҎQ?/p>
CZ | Ҏ | 要说?/b> |
1 | #include | 完全用最基本?API 方式调用lgQ大家熟悉调用原理 |
2 | #include | 大部分?API 方式Q?CComBSTR 化对字符串的使用 |
3 | #include | 展示指针 CComPtr<> 的用方?/td> |
4 | #include | 展示指针 CComPtr<> ?CComQIPtr<> 混合的用方?/td> |
5 | #include | 展示指针 CComQIPtr<> 的用方?/td> |
6 | #include | 展示指针的释放方?/td> |
7 | #import | vc 包装的智能指?IxxxPtr、_bstr_t、_variant_t 的用方法和异常处理 |
8 | #import | import 后的命名I间的用方?/td> |
CZE序中都写有注释Q请读者仔l阅dƈ同时参?MSDN 的函数说明。这里,我给大家介绍一下“智能指针”:
对于操作原始的接口指针是比较ȝ的,需要我们自己控制引用记数、API 调用、异常处理。于?ATL 提供?个智能指针的模板包装c,CComPtr<> ?CComQIPtr<>Q这两个c都?<atlbase.h> 中声明。CComQIPtr<> 包含?CComPtr<>的所有功能,因此我们可以完全?CComQIPtr<> 来用智能接口指针,唯一要说明的一点就是:CComQIPtr<> ׃使用了运符的重载功能,它会自动帮我们调用QueryInterface()函数Q因?CComQIPtr<> 唯一的缺点就是不能定?IUnknown * 指针?/p>
// 指针 smart pointerQ按照匈牙利命名法,一般以 sp 开头来表示变量cd CComPtr < IUnknown > spUnk; // 正确 // 假设 IFun 是一个接口类? CComPtr < IFun > spFun; // 正确 CComQIPtr < IFun > spFun; // 正确 CComQIPtr < IFun, &IID_IFun > spFun; // 正确 CComQIPtr < IUnknown > spUnk; // 错误QCComQIPtr不能定义IUnknown指针l智能指针赋值的ҎQ?pre> CComQIPtr < IFun > spFun; // 调用构造函敎ͼq没有赋|被包装的内部接口指针?NULL CComQIPtr < IFun > spFun( pOtherInterface ); // 调用构造函敎ͼ内部接口指针赋gؓ // 通过 pOtherInterface q个普通接口指针调用QueryInterface()得到的IFun接口指针 CComQIPtr < IFun > spFun( spOtherInterface ); // 调用构造函敎ͼ内部接口指针赋gؓ // 通过 spOtherInterface q个只能接口指针调用QueryInterface()得到的IFun接口指针 CComQIPtr < IFun > spFun ( pUnknown ); // 调用构造函敎ͼ由IUnknown的QueryInterface()得到IFun接口指针 CComQIPtr < IFun > spFun = pOtherInterface; // = q算W重载,含义和上面一? spFun = spOtherInterface; // 同上 spFun = pUnknown; // 同上 pUnknown->QueryInterface( IID_IFun, &sp ); // 也可以通过QueryInterface赋? // 指针赋值后Q可以用条g语句判断是否合法有效 if ( spFun ){} // 如果指针有效 if ( NULL != spFun ){} // 如果指针有效 if ( !spFun ){} // 如果指针无效 if ( NULL == spFun ){} // 如果指针无效指针调用函数的方法:
spFun.CoCreateInstance(...); // {h?API 函数::CoCreateInstance(...) spFun.QueryInterface(...); // {h?API 函数::QueryInterface() spFun->Add(...); // 调用内部接口指针的接口函? // 调用内部接口指针的QueryInterface()函数Q其实效果和 spFun.QueryInterface(...) 一? spFun->QueryInterface(...); spFun.Release(); // 释放内部的接口指针,同时内部指针赋gؓ NULL spFun->Release(); // 错!Q!一定不要这么用? // 因ؓq个调用q不把内部指针清I,那么析构的时候会被再ơ释放(释放了两ơ)?.....不说了,不说了,大家多看书,多看MSNDQ多看示例程序吧?写篏?-(
二?/b>建立 ATL 工程
步骤2.1Q徏立一个解x案?br /> 步骤2.2Q在 该解x案中Q新Z?vc++ ?ATL 目。示例程序叫 Simple2Qƈ选择DLL方式Q见图一、图二?br />
图一、新?ATL 目
图二、选择非属性化的DLLlgcd
属性化 属性化~程Q是未来的方向,但我们现在先不要选它?br /> 动态链接库(DLL) 选择它?br /> 可执行文?EXE) 以后再讲?br /> 服务(EXE) 表示建立一个系l服务组件程序,pȝ启动后就会加载ƈ执行的程序?br /> 允许合ƈ代理/存根(stub)代码 选择该项表示把“代?存根”代码合q到lgE序中,否则需要单独编译,单独注册代理存根E序。代?存根Q这个是什么概念?q记得我们在上回?/font>中介l的吗?当调用者调用进E外或远E组件功能的时候,其实是代?存根负责数据交换的。关于代?存根的具体变成和操作Q以后再说啦......
支持 MFC 除非有特D的原因Q我们写 ATL E序Q最好不要选择该项。你可能会说Q如果没有MFC的支持,那CString怎么办呀Q告诉你个秘密吧Q一般h我都不告诉他Q我后半辈子靠着q个U密zȝ了:
1、你会STL吗?可以?STL 中的 string 代替Q?br /> 2、自己写?MyString c,嘿嘿Q?br /> 3、悄悄地、秘密地、不要告诉别人(特别是别告诉微YQ,?MFC 中的 CString 源码拿过来用Q?br /> 4、?CComBSTR c,臛_也能化我们字W串操作Q?br /> 5、直接用 API 操作字符Ԍ反正我们大家学习 C 语言的时候,都是从这里干L。({于没说Q呵呵)
支持 COM+ 1.0 支持事务处理?COM+ 功能。COM+ 也许在第 99 回介l吧?br />
三、添?ATL 对象c?/b>
步骤3.1Q菜?目\dc?.."Q或者用鼠标右键?目中弹?d\dc?.."Qƈ选择 ATL 单对象。见图三?br />
图三、选择建立ATL单对?br />
除了单对?只实C IUnknown 接口)Q还可以选择“ATL控g?ActiveXQ实C10多个接口)......可以选择的组件对象类型很多,但本质上Q就是让向导帮我们默认加上一些接口。在以后的文章中Q陆l介l吧?br />
步骤3.2Q增加自定义c?CFun(接口 IFun) ,见图四?br />
囑֛、填写名U?br />
其实Q我们只需要输入简Uͼ其它的项目会自动填写。没什么多说的Q只请大家注意一?ProgID ,默认?ProgID 构造方式ؓ“项目名.U名”?br />
步骤3.3Q填写接口属性选项Q见?五?br />
图五、接口选项
U程模型 COM 中的U程Q我认ؓ是最讨厌Q最复杂的部分。COM U程和公寓的概念Q留待后l介l。现在吗......大家都?单元"(Apartment)Q它代表什么那Q简单地_当在U程中调用组件函数的时候,q些调用会排队进行。因此,q种模式下,我们可以暂时不用考虑同步的问题??)
接口。双?Dual)Q这个非?非常重要Q非帔R常常用,但我们今天不??)?b>切记Q切讎ͼ我们的这W一?COM E序中,一定要选择“自定义”!Q!Q?/b>Q如果你选错了,请删除全部内容,重新来过。)
聚合 我们写的lgQ将来是否允许被别h聚合(?)使用。“只能创Zؓ聚合”,有点cM C++ 中的U虚c,你要是dE师Q只负责设计但不亲自写代码的话,才选择它?br /> ISupportErrorInfo 是否支持丰富信息的错误处理接口。以后就讌Ӏ?br /> q接?/b> 是否支持q接Ҏ口(事g、回调)。以后就讌Ӏ?br /> IObjectWithSite 是否支持IE的调?br />
四、添加接口函?br />
囑օ、调出增加接口方法的菜单
图七、增加接口函?Add
h照图C的ҎQ增加Add()函数Q增加Cat()函数 。[in]表示参数方向是输入;[out]表示参数方向是输出;[out,retval]表示参数方向是输出,同时可以作ؓ函数q算l果的返回倹{一个函CQ可以有多个[in]、[out]Q但[retval]只能有一个,q且要和[out]l合后在最后一个位|??)
囑օ、接口函数定义完成后的图C?br />
我们都知道,要想改变 C++ 中的cd敎ͼ需要修改两个地方:一是头文g(.h)中类的函数声明,二是函数?.cpp)文g的实现处。而我们现在用 ATL 写组件程序,则还要修改一个地方,是接口定义(IDL)文g。别着?IDL 下次p讨论啦?br />
五、实现接口函?/b>
鼠标双点囑օ中CFun\基项和接口\Add(...)可以开始输入函数实CQ?/p>STDMETHODIMP CFun::Add(long n1, long n2, long *pVal)
{
*pVal = n1 + n2;
return S_OK;
}
q个太简单了Q不再浪费“口条”。下面我们实现字W串q接的Cat()函数Q?pre>STDMETHODIMP CFun::Cat(BSTR s1, BSTR s2, BSTR *pVal)
{
int nLen1 = ::SysStringLen( s1 ); // s1 的字W长?
int nLen2 = ::SysStringLen( s2 ); // s2 的字W长?
*pVal = ::SysAllocStringLen( s1, nLen1 + nLen2 );// 构造新?BSTR 同时?s1 先保存进?
if( nLen2 )
{
::memcpy( *pVal + nLen1, s2, nLen2 * sizeof(WCHAR) ); // 然后?s2 再连接进?
// wcscat( *pVal, s2 );
}
return S_OK;
}学生Q上面的函数实现Q完全是调用基本?API 方式完成的?br />老师Q是的,说实话,的确比较烦琐?br />学生Q我们是用memcpy()完成q接W二个字W串功能的,那么Z么不用函?wcscat()那?
老师Q多数情况下可以Q但你需要知道:׃BSTR包含有字W串长度Q因此实际的BSTR字符串内容中是可以存储L''\0''的,而函?wcscat() 是以L''\0''作ؓ复制l束标志Q因此可能会丢失数据。明白了吗?
学生Q明白,明白。我看过《COM lg设计与应??之数据类型?/font>后就明白了。那么老师Q有没有单一些的Ҏ那?
老师Q有呀Q你?.....STDMETHODIMP CFun::Cat(BSTR s1, BSTR s2, BSTR *pVal)
{
CComBSTR sResult( s1 );
sResult.AppendBSTR( s2 );
*pVal = sResult.Copy();
// *pVal = sResult.Detach();
return S_OK;
}
学生Q哈哈,好!使用?CComBSTRQ这个就单多了。CComBSTR::Copy()和CComBSTR::Detach()有什么区别?
老师QCComBSTR::Copy() 会制造一?BSTR 的副本,另外CComBSTR::CopyTo()也有cM功能。而CComBSTR::Detach()是对象与内部的 BSTR 指针剥离Q这个函数由于没有复制过E,因此速度E微快一点点。但要注意,一但剥dQ就不能再用该对象啦?br />学生Q老师Q您讲的太牛啦,我对您的敬Ԓ如巍巍泰山,直入云霄......
老师QSTOPQSTOPQ留作业?.....
1、自己先按照今天讲的内容写出q个lgQ?br /> 2、不你懂不懂,一定要去观?IDL 文gQCPP 文gQ?br /> 3、编译后Q看都生了些什么文Ӟ如果是文本的文gQ就打开看看Q?br /> 4、下载本文的CZE序(vc.net 2003版本)~译q行Q看看效果。然后预习一下示例程序中的调用方法;
学生Q知道啦Q快下课吧,我要上厕所Q我都憋的不行了......
老师Q下课!别忘了顶我的帖子呀......
六、小l?/b>
本回介绍W一个ATLlgE序的徏立步骤,而如何用该lgQ敬请关注《COM lg设计与应??》?
?QApartmentQ系l通过隐藏的窗口消息来排队lg调用Q因此我们可以暂时不考虑同步问题。注意,是暂时哈?br />?Q双接口表示在一个接口中Q同时支持自定义接口?IDispatch 接口。以后,以后Q以后就讌Ӏ因为双接口非常重要Q我们以后会天天讌Ӏ夜夜讲、常常讲------U“三讜y?)
?Q组件的重用Ҏ?个,聚合和包宏V?br />?Q这些都?IDL 文g中的概念Q以后用C么,׃l什么?img src ="http://m.shnenglu.com/yishanhante/aggbug/19606.html" width = "1" height = "1" />
]]>
二?/b>建立 ATL 工程
步骤2.1Q徏立一个工作区(WorkSpace)?br /> 步骤2.2Q在工作ZQ徏立一?ATL 工程(Project)。示例程序叫 Simple1Qƈ选择DLL方式Q见图一?br />
图一、徏?ATL DLL 工程
Dynamic Link Library(DLL) 表示建立一?DLL 的组件程序?br /> Executable(EXE) 表示建立一?EXE 的组件程序?br /> Service(EXE) 表示建立一个服务程序,pȝ启动后就会加载ƈ执行的程序?br /> Allow merging of proxy/stub code 选择该项表示把“代?存根”代码合q到lgE序中,否则需要单独编译,单独注册代理存根E序。代?存根Q这个是什么概念?q记得我们在上回?/a>中介l的吗?当调用者调用进E外或远E组件功能的时候,其实是代?存根负责数据交换的。关于代?存根的具体变成和操作Q以后再说啦......
Support MFC 除非有特D的原因Q我们写 ATL E序Q最好不要选择该项。你可能会说Q如果没有MFC的支持,那CString怎么办呀Q告诉你个秘密吧Q一般h我都不告诉他Q我后半辈子靠着q个U密zȝ了:
1、你会STL吗?可以?STL 中的 string 代替Q?br /> 2、自己写?MyString c,嘿嘿Q?br /> 3、悄悄地、秘密地、不要告诉别人(特别是别告诉微YQ,?MFC 中的 CString 源码拿过来用Q?br /> 4、?CComBSTR c,臛_也能化我们字W串操作Q?br /> 5、直接用 API 操作字符Ԍ反正我们大家学习 C 语言的时候,都是从这里干L。({于没说Q呵呵)
Support MTS 支持事务处理Q也是是否支持 COM+ 功能。COM+ 也许在第 99 回介l吧?br />
三、增?ATL 对象c?br />
步骤3.1Q菜?Insert\New ATL Object...Q或者用鼠标右键?ClassView 卡片中弹单)q择Object 分类Q选中 Simple Object 目。见图二?br />
图二、选择建立单COM对象
Category Object 普通组件。其中可以选择的组件对象类型很多,但本质上Q就是让向导帮我们默认加上一些接口。比如我们?"Simple Object"Q则向导l我们的lg加上 IUnknown 接口Q我们?"Internet Explorer Object"Q则向导除了加上 IUnknown 接口外,再增加一个给 IE 所使用?IObjectWithSite 接口。当然了Q我们完全可以手工增加Q何接口?br /> Category Controls ActiveX 控g。其中可以选择?ActiveX cd也很多。我们在后箋的专门介l?ActiveX ~程中再讨论?br /> Category Miscellaneous 辅助杂类lg?br /> Categroy Data Access 数据库类lg(我最讨厌数据库编E了Q所以我也不??br />
步骤3.2Q增加自定义c?CFun(接口 IFun) ,见图三?br />
图三、输入类中的各项名称
其实Q我们只需要输入短?Short Name)Q其它的目会自动填写。没什么多说的Q只请大家注意一?ProgID ,默认?ProgID 构造方式ؓ“工E名.短名”?br />
步骤3.3Q填写接口属性,见图四?br />
囑֛、接口属?br />
Threading Model 选择lg支持的线E模型。COM 中的U程Q我认ؓ是最讨厌Q最复杂的部分。COM U程和公寓的概念Q留待后l介l。现在吗......大家都?ApartmentQ它代表什么那Q简单地_当在U程中调用组件函数的时候,q些调用会排队进行。因此,q种模式下,我们可以暂时不用考虑同步的问题??)
Interface 接口基本cd。Dual 表示支持双接??)Q这个非?非常重要Q非帔R常常用,但我们今天不讌ӀCustom 表示自定义借口?b>切记Q切讎ͼ我们的这W一?COM E序中,一定要选择它!Q!Q?/b>Q如果你选错了,请删除全部内容,重新来过。)
Aggregation 我们写的lgQ将来是否允许被别h聚合(?)使用。Only 表示必须被聚合才能用,有点cM C++ 中的U虚c,你要是dE师Q只负责设计但不亲自写代码的话,才选择它?br /> Support ISupportErrorInfo 是否支持丰富信息的错误处理接口。以后就讌Ӏ?br /> Support Connection Points 是否支持q接Ҏ口(事g、回调)。以后就讌Ӏ?br /> Free Threaded Marshaler 以后也不Ԍq打死你,我也不说Q??)
四、添加接口函?br />
图五、调出增加接口方法的菜单
囑օ、增加接口函?Add
图七、增加接口函?Cat
请严格按照图六的方式Q增加Add()函数Q由于图七中增加Cat()函数的参数比较长Q我没有适当的输入空|请大家自p入的时候注意一下。[in]表示参数方向是输入;[out]表示参数方向是输出;[out,retval]表示参数方向是输出,同时可以作ؓ函数q算l果的返回倹{一个函CQ可以有多个[in]、[out]Q但[retval]只能有一个,q且要和[out]l合后在最后一个位|??)
囑օ、接口函数定义完成后的图C?br /> 我们都知道,要想改变 C++ 中的cd敎ͼ需要修改两个地方:一是头文g(.h)中类的函数声明,二是函数?.cpp)文g的实现处。而我们现在用 ATL 写组件程序,则还要修改一个地方,是接口定义(IDL)文g。别着?IDL 下次p讨论啦?br /> ׃ vc6.0 的BUGQ导致大家在增加完接口和接口函数后,可能不会向上图(囑օQ所表现的样式。解x法:
1 | 关闭工程Q然后重新打开 | 该方法常常有?/td> |
2 | 关闭 IDEQ然后重新运?/td> | |
3 | 打开 IDL 文gQ检查接口函数是否正,如不正确请修?/td> | |
4 | 打开 IDL 文gQ随便修改一?加一个空|再删除这个空?Q然后保?/td> | 该方法常常有?/td> |
5 | 打开 h/cpp 文gQ检查函数是否存在或是否正确Q有则改?/td> | 无则嘉勉Q不说完q个成语心理别扭 |
6 | 删除 IDL/H/CPP 中的接口函数Q然?/td> | 再来一?/td> |
7 | 重新建立工程、重新安装vc、重新安装windows、砸计算?/td> | 砸! |
五、实现接口函?/b>
鼠标双点囑օ中CFun\IFun\Add(...)可以开始输入函数实CQ?/p>STDMETHODIMP CFun::Add(long n1, long n2, long *pVal)
{
*pVal = n1 + n2;
return S_OK;
}
q个太简单了Q不再浪费“口条”。下面我们实现字W串q接的Cat()函数Q?pre>STDMETHODIMP CFun::Cat(BSTR s1, BSTR s2, BSTR *pVal)
{
int nLen1 = ::SysStringLen( s1 ); // s1 的字W长?
int nLen2 = ::SysStringLen( s2 ); // s2 的字W长?
*pVal = ::SysAllocStringLen( s1, nLen1 + nLen2 ); // 构造新?BSTR 同时?s1 先保存进?
if( nLen2 )
{
::memcpy( *pVal + nLen1, s2, nLen2 * sizeof(WCHAR) ); // 然后?s2 再连接进?
// wcscat( *pVal, s2 );
}
return S_OK;
}学生Q上面的函数实现Q完全是调用基本?API 方式完成的?br />老师Q是的,说实话,的确比较烦琐?br />学生Q我们是用memcpy()完成q接W二个字W串功能的,那么Z么不用函?wcscat()那?
老师Q多数情况下可以Q但你需要知道:׃BSTR包含有字W串长度Q因此实际的BSTR字符串内容中是可以存储L''\0''的,而函?wcscat() 是以L''\0''作ؓ复制l束标志Q因此可能会丢失数据。明白了吗?
学生Q明白,明白。我看过?a >COM lg设计与应??之数据类型》后明白了。那么老师Q有没有单一些的Ҏ那?
老师Q有呀Q你?.....STDMETHODIMP CFun::Cat(BSTR s1, BSTR s2, BSTR *pVal)
{
CComBSTR sResult( s1 );
sResult.AppendBSTR( s2 );
*pVal = sResult.Copy();
// *pVal = sResult.Detach();
return S_OK;
}
学生Q哈哈,好!使用?CComBSTRQ这个就单多了。CComBSTR::Copy()和CComBSTR::Detach()有什么区别?
老师QCComBSTR::Copy() 会制造一?BSTR 的副本,另外CComBSTR::CopyTo()也有cM功能。而CComBSTR::Detach()是对象与内部的 BSTR 指针剥离Q这个函数由于没有复制过E,因此速度E微快一点点。但要注意,一但剥dQ就不能再用该对象啦?br />学生Q老师Q您讲的太牛啦,我对您的敬Ԓ如巍巍泰山,直入云霄......
老师QSTOPQSTOPQ留作业?.....
1、自己先按照今天讲的内容写出q个lgQ?br /> 2、不你懂不懂,一定要去观?IDL 文gQCPP 文gQ?br /> 3、编译后Q看都生了些什么文Ӟ如果是文本的文gQ就打开看看Q?br /> 4、下载本文的CZE序(vc6.0版本)~译q行Q看看效果。然后预习一下示例程序中的调用方法;
学生Q知道啦Q快下课吧,我要上厕所Q我都憋的不行了......
老师Q下课!别忘了顶我的帖子呀......
六、小l?/b>
本回介绍W一个ATLlgE序的徏立步骤,而如何用该lgQ敬请关注《COM lg设计与应??》?br />
?QApartmentQ系l通过隐藏的窗口消息来排队lg调用Q因此我们可以暂时不考虑同步问题。注意,是暂时哈?br />?Q双接口表示在一个接口中Q同时支持自定义接口?IDispatch 接口。以后,以后Q以后就讌Ӏ因为双接口非常重要Q我们以后会天天讌Ӏ夜夜讲、常常讲------U“三讜y?)
?Q组件的重用Ҏ?个,聚合和包宏V?br />?Q名U的功能很好听,但微软根本就没有实现?br />?Q这些都?IDL 文g中的概念Q以后用C么,׃l什么?br />
]]>