ATL的藝術(一)-實現簡單COM對象

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.....

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章