The next content is that I reship somebody's article.
通用控件 初始化 comctl32.dll
这是我在阅读某源代码时无意中想到的一个问题,进行了一番研究,现在把结果贴出来,希望对感兴趣的人能有所帮助。
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,这个在函数的注释里说的很清楚,是为了适应各种不同的链接选项而已.
Windows系统提供的IP控件
Windows中有两个很重要的动态联结库:commctrl.dll和comctl32.dll,他们是Windows的自定义控制库(Windows Common Controls)。自定义控制库中包含了许多常用的Windows控件,如Statusbar,Coolbar,HotKey等;在C Builder中,这些控件大多数都已被包装成可视化控件了。在Microsoft推出Internet Explorer 3之后,自定义控制库中新增了一些控件,其中就包括Windows的IP控件(IP Address edit control)。
●初始化Windows自定义控制库●
Windows提供了两个API函数,InitCommonControls和InitCommonControlsEx,用来初始化自定义控制库。从名字我们不难看出这两个API函数的关系:后者是前者的增强。假如您希望在程式中使用IP控件,您必须用InitCommonControlsEx来完成对自定义控制库连同类的初始化。函数InitCommonControlsEx的原型如下:
typedef struct tagINITCOMMONCONTROLSEX {
DWORD dwSize; // size of this structure
DWORD dwICC; // flags indicating which classes to be initialized
} INITCOMMONCONTROLSEX, *LPINITCOMMONCONTROLSEX;
WINCOMMCTRLAPI BOOL WINAPI InitCommonControlsEx(LPINITCOMMONCONTROLSEX);
IP控件属于ICC_INTERNET_CLASSES类别的控件,假如要使用该控件,您应该在程式中包含如下的初始化代码:
TInitCommonControlsEx ICC;
ICC.dwSize = sizeof(TInitCommonControlsEx);
ICC.dwICC = ICC_INTERNET_CLASSES;
if(!InitCommonControlsEx(&ICC))
return; //初始化组件失败
●创建IP控件●
Windows API函数CreateWindow或CreateWindowEx都能够用来创建一个IP控件实例。IP控件的窗口类名为"SysIPAddress32",C Builder的commctrl.pas单元为其定义了一个符号常量WC_IPADDRESS。下面这条语句将在Form1上创建一个IP控件。
HWND hIpEdit = CreateWindow(WC_IPADDRESS,NULL,WS_CHILD|WS_VISIBLE,10,10,135,47,Handle,0,HInstance,NULL);
●使用IP控件●
在程式中,我们通过向IP控件发送消息来和他通讯。IP控件能够响应的消息有以下6个,这些消息及他们的含义见下表:
消息常量 消息值 作用 参数及返回值
IPM_CLEARADDRESS WM_USER 100 清除IP控件中的IP串 参数无
IPM_SETADDRESS WM_USER 101 配置IP控件的IP串 Lparam为32位的IP值
IPM_GETADDRESS WM_USER 102 获取IP控件中的IP串所对应的IP值(32位整数) Lparam为一个指向Integer变量的指针。返回值等于IP控件中非控的字段数目;获取到的IP值存放在lparam 所指向的Integer变量中。
IPM_SETRANGE WM_USER 103 配置IP控件4个部分的其中一个的IP取值范围 Wparam指明要配置取值范围的部分;lparam的低16位字为该字段的范围:高字节为上限,低字节为下限。
IPM_SETFOCUS WM_USER 104 设输入焦点 Wparam指明哪个部分获取焦点
IPM_ISBLANK WM_USER 105 IP串是否为空 参数无。返回值:若为空,返回非0;不为空,返回0
★㈠清空IP串(IPM_CLEARADDRESS)★
SendMessage(hIpEdit,IPM_CLEARADDRESS,0,0);
★㈡配置IP串(IPM_SETADDRESS)★
int nIP;
nIP=MAKEIPADDRESS(192,168,0,1);
SendMessage(hIpEdit,IPM_SETADDRESS,0,nIP);
此例将IP控件的内容设为"192.168.0.1",其中MAKEIPADDRESS是个Win32宏,定义在commctrl.h头文档中,他用来合成一个32位的IP值:
#define MAKEIPADDRESS(b1,b2,b3,b4)
((LPARAM)(((DWORD)(b1)<<24) ((DWORD)(b2)<<16) ((DWORD)(b3)<<8) ((DWORD)(b4))))
★㈢获取IP值(IPM_GETADDRESS)★
int nIP;
SendMessage(hIpEdit,IPM_GETADDRESS,0,int(&nIP));
//nIP ;
//SendMessage(hIpEdit,IPM_SETADDRESS,0,nIP); //将IP加1再赋给IP控件。
若想要获取IP控件中IP串所对应的IP值,您应该向IP控件发送IPM_GETADDRESS消息,并且需要把一个32位整数的地址作为SendMessage的最后一个参数。
★㈣配置取值范围(IPM_SETRANGE)★
SendMessage (hIpEdit, IPM_SETRANGE, 0, 200<<8|100);
此语句将IP控件的第一部分的范围限制为100~200。在IPM_SETRANGE消息中,Wparam指明要配置的字段, 而lparam的低16位字为该字段的范围:高字节为上限,低字节为下限。
★㈤配置输入焦点(IPM_SETFOCUS)★
SendMessage(hIpEdit,IPM_SETFOCUS,3,0);
//将输入焦点设在IP控件的第四部分。
㈥判断IP串是否为空(IPM_ISBLANK)
if(!SendMessage(hIpEdit,IPM_ISBLANK,0,0))
{
//IP控件中的IP串为空
}
else
{
//IP控件中的IP串至少有一部分不是空的
}
●IP控件的通知消息●
当IP串被改变后或输入焦点发生了转移,IP控件就会向他的父窗口发送通知消息IPN_FIELDCHANGED。在大多数情况下,我们都能够忽略此通知消息。以下是处理通知消息IPN_FIELDCHANGED的一个示例:
void __fastcall TForm1::WndProc(TMessage &Msg)
{
LPNMHDR p=(LPNMHDR)Msg.LParam;
if(Msg.Msg==WM_NOTIFY)
{
if(p->code==IPN_FIELDCHANGED)
{
//处理IP控件的IPN_FIELDCHANGED通知消息
}
}
TForm::WndProc(Msg);
}