程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> MFC通用控件的初始化

MFC通用控件的初始化

編輯:關於VC++

這是我在閱讀某源代碼時無意中想到的一個問題,進行了一番研究,現在把結果貼出來,希望對感興趣的人能有所幫助。

InitCommonControls和InitCommonControlsEx

從Win95開始,Windows提供了一些新的Win32控件,稱為通用控件. 如:Toolbar,Status bar,Tree view,List view,Animation,Hot-key,Image list,Tab等等.這些控件的可執行代碼都放在comctl32.dll中.要使用通用控件,必須加載comctl32.dll.

可以調用函數InitCommonControls或InitCommonControlsEx來初始化控件.這兩個函數都是動態鏈接庫comctl32.dll中的函數,兩個函數的原型如下:

void InitCommonControls(VOID);
BOOL InitCommonControlsEx(LPINITCOMMONCONTROLSEX lpInitCtrls);

可以看到,InitCommonControls沒有參數,表示初始化所有的(實際上是大部分,見下文)通用控件.而InitCommonControlsEx則可以指定初始化什麼控件.

這裡"初始化"的含義是明確的,就是指注冊相應的窗口類.比如,只有事先注冊了"SysTreeView32"窗口類,然後才可以創建該控件的窗口.

注意,注冊窗口類只對當前進程有效,因為注冊的時候必須指定一個窗口地址,而地址是只對一個進程有效的.因此,每個進程都必須初始化後才可以使用通用控件.

函數InitCommonControls是個空函數,不做任何事情.但如果你調用了該函數,則鏈接器會將你的程序鏈接到comcl32.lib,然後在程序啟動時,會加載comctl32.dll. 真正初始化的工作是在該庫的入口點處做的,在這裡會注冊通用控件窗口類,然後應用程序就可以創建控件窗口,就象創建其它的子窗口控件一樣.

InitCommonControlsEx是實際注冊控件窗口類的函數.它根據參數lpInitCtrls->dwICC的內容類決定調用哪些控件的注冊代碼.相關的值如下:

#define ICC_LISTVIEW_CLASSES 0x00000001 // listview, header
#define ICC_TREEVIEW_CLASSES 0x00000002 // treeview, tooltips
#define ICC_BAR_CLASSES 0x00000004 // toolbar, statusbar, trackbar, tooltips
#define ICC_TAB_CLASSES 0x00000008 // tab, tooltips
#define ICC_UPDOWN_CLASS 0x00000010 // updown
#define ICC_PROGRESS_CLASS 0x00000020 // progress
#define ICC_HOTKEY_CLASS 0x00000040 // hotkey
#define ICC_ANIMATE_CLASS 0x00000080 // animate
#define ICC_WIN95_CLASSES 0x000000FF
#define ICC_DATE_CLASSES 0x00000100 // month picker, date picker, time picker, updown
#define ICC_USEREX_CLASSES 0x00000200 // comboex
#define ICC_COOL_CLASSES 0x00000400 // rebar (coolbar) control
#define ICC_INTERNET_CLASSES 0x00000800
#define ICC_PAGESCROLLER_CLASS 0x00001000 // page scroller
#define ICC_NATIVEFNTCTL_CLASS 0x00002000 // native font control

注意到ICC_WIN95_CLASSES等於之前所有值的或,因此使用該標記調用InitCommonControlsEx會初始化listview,header,treeview等控件.

進程初次加載dll時,系統會以DLL_PROCESS_ATTACH參數調用DLLMain. 在動態庫comctl32.dll中,會在這時候用ICC_WIN95_CLASSES標記調用InitCommonControlsEx, 因此進程一旦加載了comctl32.dll,就注冊了一系列的通用控件.

進程最後一次卸載dll時,系統會以DLL_PROCESS_DETACH參數調用DLLMain. 在動態庫comctl32.dll中,會在這時候調用UnregisterClass取消所有已經冊過的通用控件窗口類.

注意:

對Windows 95/98/Me來說,dll卸載的時候,在其中注冊的所有窗口類會自動取消注冊.這是自動進行的,並不需要你寫下UnregisterClass的調用代碼.

對Windows NT/2000/XP來說,當dll卸載的時候,在該dll中注冊的窗口類並不會自動取消注冊,因此必須在DllMain中人為的用代碼調用unregisterClass. 否則一旦dll卸載後再次創建控件(因為沒有反注冊,系統認為窗口類仍有效),則該控件的窗口過程將指向無效的地址.

MFC中通用控件的初始化

MFC中采用了延遲加載的辦法來初始化通用控件.這樣,如果程序不使用任何通用控件,則不會加載comctl32.dll.如果使用了任何通用控件,則會在該控件的PreCreateWindow函數中初始化對應的通用控件.這就是使用depends工具查看一個使用了通用控件的MFC程序,一般都看不到有comctl32.dll存在的原因.這裡是說一般,如果在你的代碼中直接調用了兩個初始化函數之一,就會正常鏈接到comctl32.dll。

要在MFC源代碼中找到通用控件初始化的地方很簡單,只要看看一個使用了通用控件的程序何時加載comctl32.dll就可以了.你可以調試這樣一個程序,單步執行或每隔一段代碼設置一個斷點,然後每次執行後用工具看看exe是否加載了comctl32.dll模塊,如此逐步縮小范圍,很快就可以找到.查看exe在運行時包含模塊的工具很多,比如IceSword或者windows優化大師帶的一個進程管理工具都可以.

以控件syslistview32為例.MFC的包裝類是CListView.

BOOL CListView::PreCreateWindow(CREATESTRUCT& cs)
{
  return CCtrlView::PreCreateWindow(cs);
}
BOOL CCtrlView::PreCreateWindow(CREATESTRUCT& cs)
{
  ...
  // initialize common controls
  VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG));
  AfxDeferRegisterClass(AFX_WNDCOMMCTLSNEW_REG);
  ...
  return CView::PreCreateWindow(cs);
}

AfxDeferRegisterClass會根據控件的種類不同,設置不同的參數,然後調用_AfxInitCommonControls,比如下面的代碼:

init.dwICC = ICC_WIN95_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WIN95CTLS_MASK);

函數_AfxInitCommonControls完成實際的通用控件初始化.

LONG AFXAPI _AfxInitCommonControls(LPINITCOMMONCONTROLSEX lpInitCtrls, LONG fToRegister)
{
  ...
  HINSTANCE hInst = ::LoadLibraryA("COMCTL32.DLL");
  ...
  (FARPROC&)pfnInit = ::GetProcAddress(hInst, "InitCommonControlsEx");
  if (pfnInit == NULL)
  {
    ...
  }
  else if (InitCommonControlsEx(lpInitCtrls))
  {
    ...
  }
  FreeLibrary(hInst);
  ...
}

這裡要說明一下,LoadLibrary和FreeLibrary成對出現,保持了對dll的引用計數不變.只有當對dll的引用計數從0變為1時,才會以DLL_PROCESS_ATTACH調用DLLMain函數,只有dll的引用計數從1變為0時,才會以DLL_PROCESS_DETACH調用DLLMain函數.上面的代碼中,除了加載卸載DLL外,還有一次對dll中函數InitCommonControlsEx的調用,正是這個調用引起了系統額外的一次LoadLibrary的調用(注意,如前所述,MFC對這個DLL使用了延遲加載技術,因此,dll不會在程序一啟動就被加載,而是延遲到第一次訪問dll中的任何函數或數據).這個額外的調用使得對通用控件的注冊並沒有被後面的FreeLibrary取消.因此,程序在這以後就可以生成通用控件的窗口了.

至於,MFC為何時而采用直接調用,時而采用取函數地址的方法調用InitCommonControlsEx,這個在函數的注釋裡說的很清楚,是為了適應各種不同的鏈接選項而已.

水平有限,如有不當之處,請大家指正。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved