在一个.exe应用程序中,WinMain
入口点有一个HINSTANCE
参数,它应该是一个伪句柄(因为根据MSDN,相当于返回伪句柄的GetModuleHandle(NULL)
)。我认为它是伪,因为既有特殊值(例如NULL
到平均入口点模块),也有用于返回错误的常量(小于32的任何值)。
MSDN明确地将其描述为指向模块基地址的指针(现在相当于HMODULE
);我们知道这对16位应用程序可能有完全不同的意义,但在32/64位的世界中,每个进程都有自己的地址空间,那么它的精确值是无用的,对于每个实例可能总是相同的,在其进程之外绝对没有意义。
所有这些都表明,这是我的第一个问题:我们能(正式地,尽管MSDN似乎是矛盾的)假设HINSTANCE
是一个指针吗(即使老实说,我认为这没有任何用处),还是最好假设它是一个(伪)句柄(其值为不透明)?
让我们假设它的值是不透明的,我的第二个问题:它的值对每个进程有效还是对每个线程有效?
我们可能认为进程句柄对每个进程都有效,但很少有(角落)情况让我认为我们应该假设它对每个线程都有效。如果存在这些角落案例,那么(即使通常每个流程都像预期的那样运行)我们依赖于一个实现细节,一个未定义的行为,可能会因不同的体系结构、版本和环境而发生变化。
让我们看看我看到一个问题的例子(代码太琐碎了,我只描述了场景):DLL通常有一个共享代码部分,但它们也可能有(即使这很不常见)共享数据部分(例如,在进程之间共享扩展的数据,或实现快速而肮脏的IPC机制)。它可能不常见,但也是可能的(而且很容易实现,例如在VC++中,只有很少的#pragma
、data_seg
和comment(linker)
指令)。在这种情况下,我们知道(在我们的DLL中)我们无法比较HINSTANCE
s(因为它们可能具有相同的值),但我们也不能信任我们从线程A中获得的HINSTANCE
(在DLL中)与线程B中的HINSTANCE
具有可比性。简而言之:每次我们需要HINSTANCE
时,我们都必须调用GetModuleHandle(NULL)
来获得每个线程的实际有效值。
作为奖金,我也理解这是如何应用于HINSTANCE
的,当它来自WinMain
时,我们将其作为参数。理论上,它是每个线程的,但例如,CreateWindow()
会正确解析它(因为作用域是当前执行进程,当然假设调用线程有自己的消息循环)?
void createToolbox(const char* windowName, HINSTANCE hInstance) {
// ...
// Do we need this?
// HINSTANCE hInstance = GetModuleHandle(NULL);
CreateWindow(windowClass, windowName,
WS_OVERLAPPEDWINDOW,
0, 0, 640, 480,
0, 0, hInstance, NULL);
// ...
}
EDIT我似乎完全错了,我确实记得HMODULE的线程亲和力,但它适用于Windows Mobile。。。
如果此参数为NULL,GetModuleHandle将向当前进程返回一个伪句柄。[…]伪句柄是一个特殊的常量,它被解释为当前线程句柄。当需要线程句柄时,调用线程可以使用此句柄来指定自己。
显然,对于桌面应用程序(以及其他差异)来说,这不是真的。
流程中的每个模块都有一个模块句柄,该句柄也是该模块的基地址。传递给WinMain
的hInstance
自变量是进程主模块的基地址。因此,它在整个进程中都是有效的,因为进程只有一个虚拟地址空间。
传递给WinMain
的hInstance
自变量总是等于GetModuleHandle(NULL)
。
如果愿意,通常可以将模块句柄视为不透明。也就是说,通常不需要取消引用指针,只需将其传递给需要HMODULE
参数的API函数。这些都不会改变该值在不同线程中是否有效。该值是每个进程的值。
我搞不懂你的奖金问题。我怀疑这源于一个错误的假设,即模块句柄是每个线程的。一旦您接受模块句柄在进程中的所有线程中都具有相同的含义,那么您的绝大多数问题就迎刃而解了。
HINSTANCE记录为:
一个实例的句柄。这是内存中模块的基地址。
传递给WinMain
的hInstance
参数是用于创建进程的模块的基地址。它在流程的整个生命周期中都是有效的。由于它是一个指针,所以它不具有线程亲和性,并且可以在线程之间自由传递。事实上,在操作系统在进程中创建单个线程之前,这个指针就已经存在了。
"特殊值">不是HINSTANCE
数据类型的属性。它们是ShellExecute:合同的一部分
为了与16位Windows应用程序向后兼容,返回值被强制转换为HINSTANCE。然而,这并不是一个真正的HINSTANCE。
额外阅读:如何处理ShellExecute函数返回的HINSTANCE?
我在MSDN中没有提到GetModuleHandle()
在任何情况下都会返回伪句柄(与GetCurrentProcess()
/GetCurrentThread()
不同)。因此,模块句柄不存在线程亲和性这一概念。它们都是进程范围的,这就是GetModuleHandle()
的文档警告线程问题的具体原因。
作为一个指向模块基地址的指针只能证实这一点,因为你提到了一些原因。
所以,对于你的第一个问题,我回答说,如果它们被记录为指针,那么假设它们是指针是安全的。否则,总是安全的,将它们视为不透明句柄,并知道它们从来都不是伪手柄。
对于你的第二个问题,我确认这是按流程提出的。
对于您的场景,我认为由于GetModuleHandle(NULL)
从DLL中是无用的,因此最简单的方法是将传递给DllMain()
的模块句柄存储在该DLL的非共享全局变量中。
至于你的奖金问题,是的。
我不知道是什么让你相信模块句柄具有线程亲和性