ATL的藝術(一)-實現簡單COM對象
關鍵詞: ATL WTL VC++
以一個DropTarget爲例,我們都知道在MFC裏有COleDropTarget實現OLE拖放目標端非常容易,缺點MFC太臃腫近八年沒有更新過了,而且功能類與窗體類分離代
碼不夠緊湊,那麼在ATL/WTL中要實現DropTarget也是非常的容易的,而且更碼更加緊湊完美,完美的有點變態,呵呵!
我覺得ATL小組的人曾經一定是些彙編語言狂熱者,因爲ATL的運行效率真的是太高了,非常高,難以想象的高.(如果你偏不信可以用WTL嚮導一個空工程,Release編
譯後再用反彙編工具反一下,看看生成的代碼質量如何,特別是使用VC6以後版本的VC++編譯器編譯)
首先向導一個空的不帶工具欄和狀態欄的WTL SDI工程,Viwe type選擇Edit這樣我們會得到一個類似Notepad界面的程序,然後分成6步完成拖放支持
1.在stdafx.h里加上以下語句:
#include <atlcom.h>
意義是使用ATL的COM支持類,包括CComObject,CComPtr等
2.在WinMain裏:
在::CoInitialize(NULL)語句後加入以下語句
::OleInitialize(NULL);
以及在
::CoUninitialize()語句前加入以下語句
::OleUninitialize();
意義是在單線程套間中初使化COM庫,初使化後便可使用以下功能
a,剪貼板(Clipboard)
b,拖與放(Drag & Drop)
c,對象連接與嵌入(Object linking and embedding,OLE)
d,就地激活(In-place activation)
我原來一直以爲使用CoInitialize就可以了,可我調用RegisterDragDrop總
是失敗,並返回E_OUTOFMEMORY,直到我仔細看了函數說明看到下面這句話:
Note
If you use CoInitialize or CoInitializeEx instead of OleInitialize
to initialize COM, RegisterDragDrop will always
return an E_OUTOFMEMORY error.
很多時候bug都是因爲不仔細產生的,呵呵!
3.將CComObjectRoot和IDropTarget加入CMainFrame的派生列表
class CMainFrame : public CFrameWindowImpl<CMainFrame>,...,
public CComObjectRootEx<CComSingleThreadModel>,public IDropTarget
4.定義標準DropTarget方法
在MainFrm.h的CMainFrm的類定義中定義標準的IDropTarget方法:
STDMETHOD(DragEnter)(IDataObject * pDataObject,DWORD grfKeyState,POINTL pt,DWORD * pdwEffect);
STDMETHOD(DragOver)(DWORD grfKeyState,POINTL pt,DWORD * pdwEffect);
STDMETHOD(DragLeave)();
STDMETHOD(Drop)(IDataObject * pDataObject,DWORD grfKeyState,POINTL pt,DWORD * pdwEffect);
並在實現文件MainFrm.cpp實現它們.
FORMATETC fe={0};
STDMETHODIMP CMainFrame::DragEnter(IDataObject * pDataObject,DWORD grfKeyState,POINTL pt,DWORD * pdwEffect)
{
CComPtr<IEnumFORMATETC> pEnum;
pDataObject->EnumFormatEtc(DATADIR_GET,&pEnum);
while(pEnum->Next(1,&fe,NULL)==NO_ERROR)
{
if(fe.cfFormat==CF_TEXT)
{
*pdwEffect=DROPEFFECT_COPY;
break;
}
}
return S_OK;
}
STDMETHODIMP CMainFrame::DragOver(DWORD grfKeyState,POINTL pt,DWORD * pdwEffect)
{
*pdwEffect=DROPEFFECT_COPY;
return S_OK;
}
STDMETHODIMP CMainFrame::DragLeave()
{
return S_OK;
}
STDMETHODIMP CMainFrame::Drop(IDataObject * pDataObject,DWORD grfKeyState,POINTL pt,DWORD * pdwEffect)
{
STGMEDIUM stg={0};
pDataObject->GetData(&fe,&stg);
LPCTSTR lpData=(LPCTSTR)GlobalLock(stg.hGlobal);
m_view.SetWindowText(lpData);
GlobalUnlock(stg.hGlobal);
ReleaseStgMedium(&stg);
*pdwEffect=DROPEFFECT_COPY;
return S_OK;
}
5.定義COM映射表
在MainFrm.h的CMainFrm的類定義中加入下面幾句:
BEGIN_COM_MAP(CMainFrame)
COM_INTERFACE_ENTRY(IDropTarget)
END_COM_MAP()
6.註冊和註銷
在WM_CREATE消息的Handler OnCreate中註冊
RegisterDragDrop(m_hWnd,this);
在WM_CLOSE消息的Handler OnClose中註銷
RevokeDragDrop(m_hWnd);
現在差不多已經完成了,在這裏不要問怎麼沒有見到你寫AddRef,Release,QueryInterface,ATL爲我們提供了非常高效且多線程安全的實現,我們要做的只是實
現不同的接口,COM對象的生存週期管理是基於"在堆中生成對象"的假設(否則還AddRef,Release幹什麼).這裏我們的主窗體對象也將在堆中生成.
修改一下Run函數,像下面這樣來生成主窗體
int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT)
{
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);
//一個COM對象指針
CComObject<CMainFrame> *pMainFrm=NULL;
//在堆上分配對象
CComObject<CMainFrame>::CreateInstance(&pMainFrm);
if(pMainFrm->CreateEx() == NULL)
{
ATLTRACE(_T("Main window creation failed!/n"));
return 0;
}
//增加引用計數,COM的老規矩,不用我多說
pMainFrm->AddRef();
pMainFrm->ShowWindow(nCmdShow);
//開始消息循環,Release編譯後都是展開的
//直接調用GetMessage,TranslateMessage.....
int nRet = theLoop.Run();
//減少引用計數,COM的老規矩,這裏的計數爲1,
//調用Release後對象自動析構
pMainFrm->Release();
_Module.RemoveMessageLoop();
return nRet;
}
運行一下,可以從WinWord裏拖放文本到這個小程序裏.
從註冊時的RegisterDragDrop(m_hWnd,this)調用可以看到窗口對象和COM對象完美的融合到了一起,在COM方法中可以很方便的與其它組成部分交互,比如Drop
方法中的m_view.SetWindowText(lpData)調用.爲什麼this可以自動轉成IDropTarget指針呢,因爲我們的CMainFrame繼承了IDropTarget抽像類,所以按
照面向對象的觀念來看CMainFrame類"是一個"IDropTarget類.
其實在CMainFrame中只有少數接口繼承來的函數會生成函數體,其它的如消息處理過程OnCreate,OnClose等如果代碼不是很多,最後都會內聯到一個叫做
ProcessWindowMessage的函數中,由一個大大的switch來處理.
再優化一下,我們如果接口繼承的層次太多(其實這個例子裏不多),便會生成龐大虛函數表,從而影響性能,這是一些偏執狂對C++一直都指責的地方,給CMainFrame
加上ATL_NO_VTABLE(__declspec(novtable))定義,這樣如果是從CMainFrame繼承,也只到最後的實現類才生成虛函數表.
使用默認的Release編譯選項(最大化速度優先)編譯生成的exe只有36K,而一個Win32 Application嚮導生成的帶窗口空工程編譯後都有40K了,知道爲什麼嗎?
呵呵,因爲ATL在Release編譯時用更小更快的啓動代碼面不是_WinMainCRTStartup之類的東東,並且不使用CRT,C++ RTTI,C++異常處理,以及自
定義了一套malloc,new,free,delete之類的函數和運算符(這不就是使用C++語法在寫C程序嘛!或者說比C更高效,連CRT都不用),如果再加上
/opt:nowin98,/align之類的指示便會更小.
總之,在我的腦海裏:ATL+WTL=快速小巧且界面漂亮的程序,:-)
其實這些優化在機器性能猛增的今天已經變的微不足道,畢竟開發工作不全都是在處理海量的多媒體數據.現在用MFC的人都少了,更不用說ATL這樣出力不討好的東
東,大家都在搞.NET用C#之類的東東,我一直很不喜歡解釋執行的東東,可往後每臺電腦都有了.NET Framework,就不存在託管代碼和本地代碼之分了,反正發佈的
軟件在哪臺電腦裏都能運行,就像你用Windows API來寫程序,系統中已經帶好了相關的DLL,以後用.NET框架來寫程序,系統中也有.NET程序的運行時環境,用戶不
會管你是真編譯的還是解釋執行的或是你少用了幾個時鐘週期,發現自己需要轉變一下那種偏執狂的想法.也許什麼時候我應該接受C#或是Java.....