蝎子

在今天的时代,它们两者已经是同一个东西了,但是在过去的一段时间里,它们确实指代不同的东西。这个过去的时间,实际是远古时期的16位Windows时代。那个时候,一个模块(module)表示一个从磁盘加载到内存的文件,而模块句柄(HMODULE)则表示指向文件内存数据结构的句柄,这个数据结构表达了文件的存放位置,已经被加载到了内存的哪个位置(如果有的话)。

另一方面,一个实例(instance)则代表了一个变量集合。

一个类比就是:模块有点像C 类的代码,它描述了如何构造一个对象,如何实现对象的方法,还描述了类对象的各种行为。

而实例有点像C 类的对象,它描述了特定对象的特定状态。在C#术语中,一个模块有点像类型,而一个实例有点像对象。下图中展示了模块和实例的不同之处。

句柄在线程中创建如何退出(说说实例句柄和模块句柄的区别)(1)

在16位Windows时代,所有的进程都在同一个地址空间,如果一个DLL被5个应用程序使用了,则它只会被加载到内存中一次。特别的,它只会有一个数据段的拷贝。(在C /C#术语中,一个DLL有点像单例类。) 的确如此,DLL是系统全局的,而不是属于单个进程。DLL不会在每个加载它的进程中获得一份数据的拷贝。如果这个对于你的DLL来说很重要,则你不得不自己动手记录这些信息。如果以一种更加技术性的方式来描述,系统中只会有一个DLL的实例。

相对的,如果你运行两个记事本程序,则它们中的每一个会保持一份自己的变量集合,也即它们是两个实例,请看下图:

句柄在线程中创建如何退出(说说实例句柄和模块句柄的区别)(2)

这两个记事本的实例共享一个NOTEPAD模块(因此,代码和资源得到了共享),但是每个实例都会有自己的一份变量的拷贝(独立的数据段)。所以我们说有两个记事本实例。

在上图中,实例句柄指的是数据段,系统通过实例句柄来识别每个运行中的程序,这个时候,你不可以使用模块句柄,因为两个记事本的模块句柄是同一个东西(记事本的代码都是一样的)。使它们互相区分的一个东西是它们各自都拥有一份自己的全局变量结合。这就是为什么WinExec和ShellExecute这两个函数返回实例句柄的原因。它们是16位Windows系统的残留,那个时候,实例用来识别运行中的程序。

到了Win32的时代,设计者们碰到了一问题:对于Win32来说,应该如何处理实例句柄和模块句柄呢?因为在Win32中,每个程序都运行在各自独立的地址空间中,实例句柄根本不会在进程边界可见。因此,设计者选择了唯一可以选择的东西:模块的加载地址。这个地址有点类似模块句柄,因为文件头描述了文件的内容和结构。而且,因为它也十分类似实例句柄,因为数据被保留在了进程的数据段。所以,在Win32中,实例句柄和模块句柄都是模块的加载地址。

总结

所以,大家写程序的时候,不用太关心HINSTANCE和HMODULE的区别,因为它们也没啥大区别。

最后

Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。本文来自:《What is the difference between HINSTANCE and HMODULE?》

句柄在线程中创建如何退出(说说实例句柄和模块句柄的区别)(3)

,