What did MakeProcInstance do? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20080207-00/?p=23533
Raymond Chen 2008年02月07日
MakeProcInstance 做了什么?
MakeProcInstance
宏实际上什么也不做。
#define MakeProcInstance(lpProc,hInstance) (lpProc)
一个什么也不做的宏有什么意义呢?
在16位Windows系统中,MakeProcInstance
是有作用的。
回想一下,在16位Windows系统中,HINSTANCE
是用来标识数据段的机制;也就是说,是代表模块使用中的变量集合的一块内存。如果你运行了两个记事本的副本,代码只有一个副本,但变量有两套(每份副本一套)。正是这第二套变量建立了记事本的第二个副本。
当你设置一个回调函数,比如窗口过程时,回调函数需要知道它被调用是为了哪一套变量。例如,如果一个记事本副本调用 EnumFonts
并传递一个回调函数,这个函数需要知道它正在哪个记事本副本中运行,以便它可以访问正确的变量集合。这就是 MakeProcInstance
函数的作用。
MakeProcInstance
的参数是一个函数指针和一个实例句柄。MakeProcInstance
函数会即时生成代码,将数据段寄存器设置为实例句柄,然后跳转到原始函数指针。MakeProcInstance
的返回值是指向那个动态生成的代码片段(称为 thunk)的指针,并且你在使用该代码片段作为函数指针时,需要其他函数回调你。这样,当你的函数被调用时,它的变量就会正确设置。
当你不再需要代码片段时,你可以使用 FreeProcInstance
函数释放它。
那些使用过ATL的人已经在 CStdCallThunk
类中看到过这种代码片段生成。操作与 MakeProcInstance
完全类似。你使用函数指针和 this
参数初始化 CStdCallThunk
,它即时生成代码,通过在调用你初始化thunk时使用的函数之前设置 this
指针,将静态函数转换为 C++ 成员函数。
在16位Windows上创建这些代码片段必须由内核完成,因为8086处理器没有内存管理单元。没有通过转换表的间接寻址;所有地址都是物理的。因此,如果内存管理器需要移动内存,它还必须知道所有移动内存的引用位置,以便可以更新指针。如果数据段移动了,内核必须去修复所有的 MakeProcInstance
thunks,以便它们使用新的实例句柄而不是旧的。
是 Michael Geary 发现所有这些 MakeProcInstance
工作是不必要的。如果回调函数位于DLL中,那么函数可以硬编码其实例句柄,并在函数开始时加载它;这种技术最终被称为 loadds。由于DLL是单实例的,DLL已经知道它应该使用哪一套变量,因为从一开始就只有一套DLL变量!
当然,硬编码的值必须作为修正记录,因为实例句柄是在运行时确定的。另外,内核需要知道如果实例句柄的值改变,需要更新哪些值。
另一方面,如果回调函数位于可执行文件中,那么它可以从堆栈选择器中获取其实例句柄;这种技术最终被称为 export。每个程序在一个堆栈上运行(这里没有多线程),堆栈、数据段和本地堆都按照惯例位于同一个选择器中。
在我写这篇回忆录时,我发现了一个奇怪的回环,Michael Geary 的 FixDS 程序的原始自述文件中,有一段介绍与我有关,链接回我本人…