HWND 句柄分配算法浅析
在使用Win32 API进行程序开发的时候,句柄的概念处处可见。但是此句柄非彼句柄,各种不同的句柄实现方法很容易让人搞混。下面将源代码级粗略的分析一下HWND这种典型句柄的分配及使用方法,让我们对此类句柄有一个更透彻的了解。
HWND一般是通过User32.dll的CreateWindowEx函数建立窗口时返回的。此函数依照如下的调用路径完成实际功能。
ntos/w32/ntuser/client
CreateWindowEx (cltxt.h:38)
_CreateWindowEx (ntstub.c:1197)
ntos/w32/ntuser/kernel
NtUserCreateWindowEx (ntstub.c:8087)
xxxCreateWindowEx (createw.c:33)
HMAllocObject (handtabl.c:828)
xxxCreateWindowEx函数中调用HMAllocObject(handtabl.c:828)完成实际的句柄的分配
以下为引用:
/*
* Allocate memory for regular windows.
*/
pwnd = HMAllocObject(
ptiCurrent, pdesk, TYPE_WINDOW, sizeof(WND) + pcls->cbwndExtra);
handtabl.c中的函数维护着一张全局唯一的对象句柄表,HWND/HMENU等等类型的句柄都在此保存,只需在HMAllocObject函数调用时指明句柄类型和结构大小即可自动完成分配。支持的句柄类型列表可以在ntos/w32/ntuser/client/nt6/user.h:899找到,如下
以下为引用:
/*
* Object types
*
* NOTE: Changing this table means changing hard-coded arrays that depend
* on the index number (in security.c and in debug.c)
*/
#define TYPE_FREE 0 // must be zero!
#define TYPE_WINDOW 1 // in order of use for C code lookups
#define TYPE_MENU 2
#define TYPE_CURSOR 3
#define TYPE_SETWINDOWPOS 4
#define TYPE_HOOK 5
#define TYPE_CLIPDATA 6 // clipboard data
#define TYPE_CALLPROC 7
#define TYPE_ACCELTABLE 8
#define TYPE_DDEACCESS 9
#define TYPE_DDECONV 10
#define TYPE_DDEXACT 11 // DDE transaction tracking info.
#define TYPE_MONITOR 12
#define TYPE_KBDLAYOUT 13 // Keyboard Layout handle (HKL) object.
#define TYPE_KBDFILE 14 // Keyboard Layout file object.
#define TYPE_WINEVENTHOOK 15 // WinEvent hook (EVENTHOOK)
#define TYPE_TIMER 16
#define TYPE_INPUTCONTEXT 17 // Input Context info structure
#define TYPE_CTYPES 18 // Count of TYPEs; Must be LAST + 1
#define TYPE_GENERIC 255 // used for generic handle validation
HMAllocObject函数的功能主要分为以下几部分:
1.根据句柄类型不同,验证其创建标志有效性
2.尝试搜索一个空闲的可用句柄槽
3.当没有空闲句柄槽时扩展句柄表
4.根据句柄类型分配内存并填充句柄表槽
5.更新统计数据 (Debug版)
对我们来说重要的是第2-4部分。其中搜索句柄表可用槽的算法如下
以下为引用:
/*
* Find the next free handle
* Window handles must be even; hence we try first to use odd handles
* for all other objects.
* Old comment:
* Some wow apps, like WinProj, require even Window handles so we''ll
* accomodate them; build a list of the odd handles so they won''t get lost
* 10/13/97: WinProj never fixed this; even the 32 bit version has the problem.
*/
fEven = (bType == TYPE_WINDOW);
piheFreeHead = NULL;
do {
php = gpHandlePages;
for (i = 0; i < gcHandlePages; ++i, ++php) {
if (fEven) {
if (php->iheFreeEven != 0) {
piheFreeHead = &php->iheFreeEven;
break;
}
} else {
if (php->iheFreeOdd != 0) {
piheFreeHead = &php->iheFreeOdd;
break;
}
}
} /* for */
/*
* If we couldn''t find an odd handle, then search for an even one
*/
fEven = ((piheFreeHead == NULL) && !fEven);
} while (fEven);
gcHandlePages是全局句柄表的索引表项数,gpHandlePages全局句柄表的索引表。
以下为引用:
typedef struct _HANDLEPAGE {
ULONG_PTR iheLimit; /* first handle index past the end of the page */
ULONG_PTR iheFreeEven; /* first even free handle in the page -- window objects */
ULONG_PTR iheFreeOdd; /* first even odd handle in the page */
} HANDLEPAGE, *PHANDLEPAGE;
每个索引表项定义了一个句柄表的页,以及页内可用句柄的末端。因为历史原因,需要尽量保证HWND类型句柄为偶数。因此索引表里将最后可用的奇数和偶数句柄单独列出,并根据请求分配句柄类型不同,选择不同的优先搜索算法。如果实在无法保证HWND的偶数性才尝试分配其它奇数句柄。
当前句柄表如果完全用完,则调用HMGrowHandleTable (handtabl.c:704)函数扩展句柄表,并从新分配的句柄表页中分配句柄。实现算法如下:
以下为引用:
/*
* If there are no free handles we can use, grow the table
*/
if (piheFreeHead == NULL) {
HMGrowHandleTable();
/*
* If the table didn''t grow, get out.
*/
if (i == gcHandlePages) {
RIPMSG0(RIP_WARNING, "HMAllocObject: could not grow handle space");
return NULL;
}
/*
* Because the handle page table may have moved,
* recalc the page entry pointer.
*/
php = &gpHandlePages ;
piheFreeHead = (bType == TYPE_WINDOW ? &php->iheFreeEven : &php->iheFreeOdd);
if (*piheFreeHead == 0) {
UserAssert(gpsi->cHandleEntries == (HMINDEXBITS + 1));
RIPMSG0(RIP_WARNING, "HMAllocObject: handle table is full");
return NULL;
}
}
在填充完句柄表项之后,将使用HMHandleFromIndex把一个索引转换成最终返回给用户的句柄。
以下为引用:
/*
* Change HMINDEXBITS for bits that make up table index in handle
* Change HMUNIQSHIFT for count of bits to shift uniqueness left.
* Change HMUNIQBITS for bits that make up uniqueness.
*
* Currently 64K handles can be created, w/16 bits of uniqueness.
*/
#define HMINDEXBITS 0x0000FFFF // bits where index is stored
#define HMUNIQSHIFT 16 // bits to shift uniqueness
#define HMUNIQBITS 0xFFFF // valid uniqueness bits
#define HMHandleFromIndex(i) ((HANDLE)(i | (gSharedInfo.aheList.wUniq << HMUNIQSHIFT)))
#define HMIndexFromHandle(h) (((ULONG_PTR)h) & HMINDEXBITS)
#define _HMPheFromObject(p) (&gSharedInfo.aheList[HMIndexFromHandle((((PHEAD)p)->h))])
#define _HMObjectFromHandle(h) ((PVOID)(gSharedInfo.aheList[HMIndexFromHandle(h)].phead))
#define HMUniqFromHandle(h) ((WORD)((((ULONG_PTR)h) >> HMUNIQSHIFT) & HMUNIQBITS))
#define HMObjectType(p) (HMPheFromObject(p)->bType)
#define HMObjectFlags(p) (gahti[HMObjectType(p)].bObjectCreateFlags)
#define HMIsMarkDestroy(p) (HMPheFromObject(p)->bFlags & HANDLEF_DESTROY)
从这段宏定义我们可以发现,返回给最终用户的句柄,实际上是由两部分组成的。低16bit是实际的句柄表索引;高16bit是一个唯一编号wUniq。这个句柄表项的唯一编号wUniq在函数HMInitHandleEntries (handtabl.c:529)初始化句柄表时会被初始化为1,而每次调用函数HMFreeObject (handtabl.c:1069)释放一个句柄时,此编号会被加一。这样一来,自动累加的每句柄表项使用计数器,加上全局唯一的对象句柄表索引,和起来就是一个在相当长时间内不会重复的32bit句柄。而在使用的时候也只需要使用HMIndexFromHandle宏轻松即可获得实际索引。MS相当完美的解决了索引重用的问题,无论是在时间还是空间上
在使用Win32 API进行程序开发的时候,句柄的概念处处可见。但是此句柄非彼句柄,各种不同的句柄实现方法很容易让人搞混。下面将源代码级粗略的分析一下HWND这种典型句柄的分配及使用方法,让我们对此类句柄有一个更透彻的了解。
HWND一般是通过User32.dll的CreateWindowEx函数建立窗口时返回的。此函数依照如下的调用路径完成实际功能。
ntos/w32/ntuser/client
CreateWindowEx (cltxt.h:38)
_CreateWindowEx (ntstub.c:1197)
ntos/w32/ntuser/kernel
NtUserCreateWindowEx (ntstub.c:8087)
xxxCreateWindowEx (createw.c:33)
HMAllocObject (handtabl.c:828)
xxxCreateWindowEx函数中调用HMAllocObject(handtabl.c:828)完成实际的句柄的分配
以下为引用:
/*
* Allocate memory for regular windows.
*/
pwnd = HMAllocObject(
ptiCurrent, pdesk, TYPE_WINDOW, sizeof(WND) + pcls->cbwndExtra);
handtabl.c中的函数维护着一张全局唯一的对象句柄表,HWND/HMENU等等类型的句柄都在此保存,只需在HMAllocObject函数调用时指明句柄类型和结构大小即可自动完成分配。支持的句柄类型列表可以在ntos/w32/ntuser/client/nt6/user.h:899找到,如下
以下为引用:
/*
* Object types
*
* NOTE: Changing this table means changing hard-coded arrays that depend
* on the index number (in security.c and in debug.c)
*/
#define TYPE_FREE 0 // must be zero!
#define TYPE_WINDOW 1 // in order of use for C code lookups
#define TYPE_MENU 2
#define TYPE_CURSOR 3
#define TYPE_SETWINDOWPOS 4
#define TYPE_HOOK 5
#define TYPE_CLIPDATA 6 // clipboard data
#define TYPE_CALLPROC 7
#define TYPE_ACCELTABLE 8
#define TYPE_DDEACCESS 9
#define TYPE_DDECONV 10
#define TYPE_DDEXACT 11 // DDE transaction tracking info.
#define TYPE_MONITOR 12
#define TYPE_KBDLAYOUT 13 // Keyboard Layout handle (HKL) object.
#define TYPE_KBDFILE 14 // Keyboard Layout file object.
#define TYPE_WINEVENTHOOK 15 // WinEvent hook (EVENTHOOK)
#define TYPE_TIMER 16
#define TYPE_INPUTCONTEXT 17 // Input Context info structure
#define TYPE_CTYPES 18 // Count of TYPEs; Must be LAST + 1
#define TYPE_GENERIC 255 // used for generic handle validation
HMAllocObject函数的功能主要分为以下几部分:
1.根据句柄类型不同,验证其创建标志有效性
2.尝试搜索一个空闲的可用句柄槽
3.当没有空闲句柄槽时扩展句柄表
4.根据句柄类型分配内存并填充句柄表槽
5.更新统计数据 (Debug版)
对我们来说重要的是第2-4部分。其中搜索句柄表可用槽的算法如下
以下为引用:
/*
* Find the next free handle
* Window handles must be even; hence we try first to use odd handles
* for all other objects.
* Old comment:
* Some wow apps, like WinProj, require even Window handles so we''ll
* accomodate them; build a list of the odd handles so they won''t get lost
* 10/13/97: WinProj never fixed this; even the 32 bit version has the problem.
*/
fEven = (bType == TYPE_WINDOW);
piheFreeHead = NULL;
do {
php = gpHandlePages;
for (i = 0; i < gcHandlePages; ++i, ++php) {
if (fEven) {
if (php->iheFreeEven != 0) {
piheFreeHead = &php->iheFreeEven;
break;
}
} else {
if (php->iheFreeOdd != 0) {
piheFreeHead = &php->iheFreeOdd;
break;
}
}
} /* for */
/*
* If we couldn''t find an odd handle, then search for an even one
*/
fEven = ((piheFreeHead == NULL) && !fEven);
} while (fEven);
gcHandlePages是全局句柄表的索引表项数,gpHandlePages全局句柄表的索引表。
以下为引用:
typedef struct _HANDLEPAGE {
ULONG_PTR iheLimit; /* first handle index past the end of the page */
ULONG_PTR iheFreeEven; /* first even free handle in the page -- window objects */
ULONG_PTR iheFreeOdd; /* first even odd handle in the page */
} HANDLEPAGE, *PHANDLEPAGE;
每个索引表项定义了一个句柄表的页,以及页内可用句柄的末端。因为历史原因,需要尽量保证HWND类型句柄为偶数。因此索引表里将最后可用的奇数和偶数句柄单独列出,并根据请求分配句柄类型不同,选择不同的优先搜索算法。如果实在无法保证HWND的偶数性才尝试分配其它奇数句柄。
当前句柄表如果完全用完,则调用HMGrowHandleTable (handtabl.c:704)函数扩展句柄表,并从新分配的句柄表页中分配句柄。实现算法如下:
以下为引用:
/*
* If there are no free handles we can use, grow the table
*/
if (piheFreeHead == NULL) {
HMGrowHandleTable();
/*
* If the table didn''t grow, get out.
*/
if (i == gcHandlePages) {
RIPMSG0(RIP_WARNING, "HMAllocObject: could not grow handle space");
return NULL;
}
/*
* Because the handle page table may have moved,
* recalc the page entry pointer.
*/
php = &gpHandlePages ;
piheFreeHead = (bType == TYPE_WINDOW ? &php->iheFreeEven : &php->iheFreeOdd);
if (*piheFreeHead == 0) {
UserAssert(gpsi->cHandleEntries == (HMINDEXBITS + 1));
RIPMSG0(RIP_WARNING, "HMAllocObject: handle table is full");
return NULL;
}
}
在填充完句柄表项之后,将使用HMHandleFromIndex把一个索引转换成最终返回给用户的句柄。
以下为引用:
/*
* Change HMINDEXBITS for bits that make up table index in handle
* Change HMUNIQSHIFT for count of bits to shift uniqueness left.
* Change HMUNIQBITS for bits that make up uniqueness.
*
* Currently 64K handles can be created, w/16 bits of uniqueness.
*/
#define HMINDEXBITS 0x0000FFFF // bits where index is stored
#define HMUNIQSHIFT 16 // bits to shift uniqueness
#define HMUNIQBITS 0xFFFF // valid uniqueness bits
#define HMHandleFromIndex(i) ((HANDLE)(i | (gSharedInfo.aheList.wUniq << HMUNIQSHIFT)))
#define HMIndexFromHandle(h) (((ULONG_PTR)h) & HMINDEXBITS)
#define _HMPheFromObject(p) (&gSharedInfo.aheList[HMIndexFromHandle((((PHEAD)p)->h))])
#define _HMObjectFromHandle(h) ((PVOID)(gSharedInfo.aheList[HMIndexFromHandle(h)].phead))
#define HMUniqFromHandle(h) ((WORD)((((ULONG_PTR)h) >> HMUNIQSHIFT) & HMUNIQBITS))
#define HMObjectType(p) (HMPheFromObject(p)->bType)
#define HMObjectFlags(p) (gahti[HMObjectType(p)].bObjectCreateFlags)
#define HMIsMarkDestroy(p) (HMPheFromObject(p)->bFlags & HANDLEF_DESTROY)
从这段宏定义我们可以发现,返回给最终用户的句柄,实际上是由两部分组成的。低16bit是实际的句柄表索引;高16bit是一个唯一编号wUniq。这个句柄表项的唯一编号wUniq在函数HMInitHandleEntries (handtabl.c:529)初始化句柄表时会被初始化为1,而每次调用函数HMFreeObject (handtabl.c:1069)释放一个句柄时,此编号会被加一。这样一来,自动累加的每句柄表项使用计数器,加上全局唯一的对象句柄表索引,和起来就是一个在相当长时间内不会重复的32bit句柄。而在使用的时候也只需要使用HMIndexFromHandle宏轻松即可获得实际索引。MS相当完美的解决了索引重用的问题,无论是在时间还是空间上
