参考资料:html
1. http://www.codeforge.cn/read/146318/WinDef.h__html程序员
windef.h头文件编程
2. http://www.codeforge.cn/read/146318/WinNT.h__htmlwindows
winnt.h头文件编程语言
3. https://msdn.microsoft.com/en-us/library/windows/desktop/aa383681%28v=vs.85%29.aspx函数
微软官网中关于STRICT的内容url
C语言宏中"#"和"##"的用法.net
5. http://www.cnblogs.com/kerwinshaw/archive/2009/02/02/1382428.html指针
typedef和#define的用法与区别
6. http://blog.csdn.net/geekcome/article/details/6249151
void及void指针含义的深入解析
写在前面:
本文是对在下上一篇文章《图解说明——究竟什么是Windows句柄》的扩充。一样地,本文依然是面向初学者的。让编程语言变得平易,让初学者学起来你更加舒服,在交流中与广大读者同勉共进,是在下的一向宗旨和追求。因此,在对源码的解释中,在下会尽量作到详细,具体到句,对于与句柄不是特别相关的内容,也会加以解释说明。和上一篇同样,咱们仍然把窗口、位图、画笔等统称为对象。
还有一点必需要交代,在下对Windows句柄这一细节的研究,还存在一些疑问,这将在文末进一步说明。
先看winnt.h中关于HANDLE(句柄)的定义:
typedef void *PVOID;
#ifdef STRICT
typedef void *HANDLE;
#define DECLARE_HANDLE(name) struct name##__ {
int unused;
};
typedef struct name##__ *name
#else
typedef PVOID HANDLE;
#define DECLARE_HANDLE(name) typedef HANDLE name
#endif
以上代码typedef void *PVOID;来自winnt.h(参考资料2)中的第178行,其他代码来自winnt.h中的第285~293行,考虑到易读性,在下对代码格式稍稍作了调整。
在分析源代码以前,再说一点,那就是typedef和#define的区别的问题。typedef用来定义一个标识符及关键字的别名,而#define是宏定义,简单说,就是字符串替换。若是有读者还不是很明白,能够参阅参考资料5。在下面的叙述中,咱们将二者都译为“定义”。由于在下以为这样能够带来叙述上的方便,而且若是你们理解了typedef和#define的区别,这样作并不会形成理解上的误会。
下面咱们开始逐句分析代码。
首先,typedef void *PVOID;,这里将PVOID定义为void*型,之后,PVOID a,b;就至关于void *a,*b;(注意不是void *a,b;)。这里再简单说一下void*,简单说,void *就是“无类型指针”,能够指向任何数据类型,详情可参阅参考资料6。
下面的一段整体上是if——else结构。咱们先看if部分。
#ifdef STRICT
若是定义了STRICT,就执行后面的代码。关于STRICT,在后面咱们还会进行详细的讲解,这里咱们暂时将其跳过,先看条件成立时的代码。
#define DECLARE_HANDLE(name) struct name##__ {
int unused;
};
这里是一个带参数的宏定义,name是参数,##为粘贴符号,表示把左右两边的内容链接起来。关于带参数的宏定义和##,读者能够参阅参考资料4。
这里将结构体
struct name##__
{
int unused;
};
定义为
DECLARE_HANDLE(name)。
接下来,
typedef struct name##__ *name
定义一个指针name,指向上面的结构体name##__。
下面咱们以窗口句柄HWND为例,进一步说明。
在windef.h头文件(见参考资料1)的第196行有代码
DECLARE_HANDLE (HWND);
咱们将宏展开,就是
struct HWND__
{
int unused;
};
一样,根据typedef struct name##__ *name
有typedef struct HWND__ *HWND。
即句柄HWND是一个指针,指向结构体struct HWND__。
其它句柄的定义与HWND相似,这里再也不赘述,读者能够参阅参考资料1中从195行日后的代码。
注意这里咱们忽略了一个细节,那就是结构体中的int unused。关于这一点,咱们先暂时忽略,在后面的“还没有解决”板块,在下将对这一问题做出交代。
有了前边的经验,分析else部分的代码就变得容易了,让咱们一块儿来看。
typedef PVOID HANDLE;
因为前边有typedef void *PVOID;,因此这里HANDLE被定义为void*型。
接着,
#define DECLARE_HANDLE(name) typedef HANDLE name
这里将
typedef HANDLE name
定义为
DECLARE_HANDLE(name)。
还以HWND为例,在这种状况下,
DECLARE_HANDLE (HWND);
宏展开为
typedef HANDLE HWND,
即此时HWND为void*型。
好了,说完这些,咱们着重说一下STRICT。相关内容请参阅参考资料3。
在windef.h头文件的第13~17行定义了STRICT,源代码以下:
#ifndef NO_STRICT
#ifndef STRICT
#define STRICT 1
#endif
#endif /* NO_STRICT */
这里仅仅是将STRICT定义为数值1,看不出什么名堂。关键在于编译器(注意不是系统)对STRICT的“解释”。
顾名思义,STRICT是“严格”、“严厉”的意思。当编译器“看到”定义了STRICT后,就会对Windows 应用程序中使用的句柄进行严格的类型检查。Windows官网中的原文为Enabling STRICT redefines certain data types so that the compiler does not permit assignment from one type to another without an explicit cast.
也就是说,若是定义了STRICT,除非显式强制类型转换,不然不容许将数据从一种类型转化到另外一种类型。换句话说,定义STRICT能够禁止隐式类型转换。
那么,这是怎么实现的,又有什么用处呢? 以窗口句柄HWND和钩子句柄HHOOk为例。在windef.h头文件的第19六、197行定义了HWND和HHOOK:
DECLARE_HANDLE (HWND);
DECLARE_HANDLE (HHOOK);
经过前面的分析,咱们知道,若是定义了STRICT,那么天然执行#ifdef STRICT后的代码,这样,HWND就是HWND__*型的指针,而HHOOK就是HHOOK__*型的指针,二者类型不一样。若是没有定义STRICT,那么将执行#else后的代码,能够发现,这段代码直接将全部句柄都定义为HANDLE,即PVOID,也就是void*型。在这种状况下,上面的HWND和HHOOK都是void*型,类型相同。那么,两种状况下有什么差异呢?咱们举例说明。
如今一个函数要求一个HHOOK类型的参数,而咱们传给它一个HWND类型的参数。在没有define STRICT的状况下,这将是合法的,由于HHOOK和HWND都是void*类型。而若是咱们定义了STRICT,HHOOK和HWND就是两个不一样类型的指针,上面的参数传递将变为不合法,而且在编译阶段就会报错,这就避免了直到程序出现了运行时错误,程序员才知道代码有错的状况。
顺便说一句,如今VC、VS都define了STRICT,即都默认进行严格的类型检查。
至此,相信你们已经明白了STRICT的做用以及为何不直接用int unused而要用结构体将其封装起来。
最后,让咱们一言以蔽之,来总结一下Windows句柄的本质:
Windows句柄本质上就是一个指向结构体的指针(define STRICT的状况下)。
而所谓“指针的指针”的说法并不正确,这只是一个逻辑上的理解。
还没有解决:
如今,让咱们回到前面忽略掉的关于int unused的问题上来。
若是有读者看过了在下的上一篇文章《图解说明——究竟什么是Windows句柄》,那么相信有人会和在下最初的想法同样,认为这里的unused就是咱们说的区域A。句柄指向一个结构体,而这个结构体中惟一的数据unused中存放着对象的地址(虽然unused不是指针类型,但int和指针同为4个字节,将对象的地址存到unused里,未来再用某种方式经过unused找到该对象,这也是能够实现的),这与咱们先前的图示刚好吻合。但稍加琢磨,咱们发现这样解释在某些地方仍是有些说不过去的。理由起码有2:
①首先是名字问题,相信稍微细心的读者就会发现这一问题。经过前边的源代码看,每个名字都恰如其分地反映了它应有的意义,照这么看,结构体中的int变量存放了一个有用的地址,那它就不该该叫unused。
②若是unused至关于区域A的话,在define STRICT的状况下,句柄指向了区域A,而在没有define STRICT的状况下,并无定义结构体,句柄被定义为void*型,那么,这种状况下的区域A又在哪里,句柄又如何指向它?
因此,综合前面的分析,在下认为,unused并非区域A。关于int unused,在下的一个猜测是:
进程建立时,系统在内存的一个地方存放各个对象的地址,同时系统为各个对象指定句柄,存放在内存中另外一个地方,并使各个句柄指向相应对象的地址(即区域A)。至于如何指向,极可能是这样:
对于未define STRICT的状况,直接指向就能够,由于此时句柄是void*型。而对于define了STRICT的状况,可能采用强制类型转换或是类似的手段来使原先指向结构体的句柄指向一个32位的地址。注意到,原先句柄指向一个结构体,而这个结构体中只有一个int型数据,从内存的角度看,句柄其实指向了一段4字节的内存,然后来,句柄指向32位的地址,一样是指向一段4字节的内存。而若是咱们去掉intunused,只保留一个空结构体,咱们知道,空结构体占1个字节(对这一点有疑问的读者,能够写一个空结构体,用sizeof()函数实测一下),此时句柄指向1个字节的内存,而如今要让它指向4个字节的内存(32位地址),极可能会没法“转化”。然而,若是转化先后,句柄都指向4个字节的内存,那极可能就可以转化。因此,int unused的做用就是使句柄指向一个4字节的内存,以便未来句柄指向对象的地址时可以顺利“转化”。而从始至终,unused历来没有被显式地使用过,因此取名为unused。显然,这里unused的意思是“未被使用的”,而非“没用的”。
至此,全部关于windows句柄这一细节的内容都讲解完了。
写在后面:
1.在下知识浅薄、能力有限,讲解过程当中不免有错误疏漏之处,这里恳请你们务必批评指正,在下先行谢过。
2.遗憾的是,到最后,仍是有一些疑问没有解决。这里在下请求路过的大神不吝赐教,也诚望广大读者各抒己见,让咱们一块儿思考,共同进步。