HINSTANCE 跨线程有效吗?
Is HINSTANCE valid across threads?
在单个 .exe 应用程序中 WinMain
入口点有一个 HINSTANCE
参数,它应该是一个伪句柄(因为等同于 GetModuleHandle(NULL)
其中 return 是一个伪句柄-句柄,根据 MSDN)。我想它是 伪 因为有两个 特殊值 (例如 NULL
表示入口点模块)和常量用于 return 错误(小于 32)。
MSDN 将其明确描述为指向模块基地址的 指针 (现在相当于 HMODULE
);我们知道这对于 16 位应用程序可能具有完全不同的含义,但在 32/64 位世界中每个进程都有自己的地址 space 然后它的 exact 值是无用的,可能总是每个实例都相同,在其进程之外绝对没有意义。
综上所述,这是我的第一个问题:我们可以(正式地,尽管 MSDN 似乎自相矛盾)假设 HINSTANCE
是一个指针(即使老实说我看不出这有什么用)或者最好假设它是一个(伪)句柄(它的值是 opaque)?
我们假设它的值是不透明的,我的第二个问题:它的值对每个进程或每个线程有效吗?
我们可能认为进程句柄对每个进程都是有效的,但很少(极端)情况让我认为我们应该假设它对每个线程都有效。如果这些极端情况存在,那么(即使通常它似乎按预期每个进程工作)我们依赖于一个实现细节,一个未定义的行为 可能会因不同的体系结构、版本、环境而发生变化。
让我们看一个我看到问题的案例(代码太简单了,我只描述场景):DLL 通常有一个共享代码部分,但它们也可能有(即使它很不常见)一个共享数据部分(例如跨进程共享 扩展 数据或实现快速和肮脏的 IPC 机制)。它可能不常见但可能(并且很容易实现,例如在 VC++ 中,几乎没有 #pragma
s data_seg
和 comment(linker)
指令)。在这种情况下,我们知道(在我们的 DLL 中)我们无法比较 HINSTANCE
s(因为它们可能具有相同的值)而且在 IMO 中我们也不能 trust我们从线程 A(在我们的 DLL 中)获得的 HINSTANCE
与我们在线程 B 中获得的 HINSTANCE
相当。简而言之:每次我们需要一个 HINSTANCE
我们必须调用 GetModuleHandle(NULL)
才能获得 actual 每个线程有效的
作为 bonus 我也会理解这如何适用于 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 ...
If this parameter is NULL, GetModuleHandle returns a pseudo handle to the current process. [...] A pseudo handle is a special constant that is interpreted as the current thread handle. The calling thread can use this handle to specify itself when a thread handle is required.
显然,桌面应用程序并非如此(除其他差异外)。
进程中的每个模块都有一个模块句柄,它也是该模块的基地址。传递给 WinMain
的 hInstance
参数是进程主模块的基地址。因此,它在整个过程中都是有效的,因为该过程只有一个虚拟地址 space.
传递给 WinMain
的 hInstance
参数总是等于 GetModuleHandle(NULL)
。
如果您愿意,您通常可以将模块句柄视为不透明的。也就是说,您通常不需要取消对指针的引用,只需将其传递给需要 HMODULE
个参数的 API 函数即可。 None 更改该值在不同线程中是否有效。该值是每个进程的值。
我无法理解你的奖金问题。我怀疑这源于模块句柄是每个线程的错误假设。一旦您接受模块句柄在进程内的所有线程中具有相同的含义,您的绝大多数问题就会消失。
我在 MSDN 中没有看到任何地方提到 GetModuleHandle()
在任何情况下都返回伪句柄(不同于 GetCurrentProcess()
/GetCurrentThread()
)。因此,不存在模块句柄的线程亲和性。它们都是进程范围的,这就是 GetModuleHandle()
的文档警告线程问题的具体原因。
由于您提到的原因,作为指向模块基址的指针只能证实这一点。
因此,对于您的第一个问题,我的回答是可以安全地假设它们是指针,如果它们被记录为指针的话。否则,总是安全地将它们视为不透明句柄,并且知道它们永远不会伪句柄。
关于你的第二个问题,我确认它是按进程的。
对于你的场景,我说因为 GetModuleHandle(NULL)
在 DLL 中是无用的,最简单的方法是将传递给 DllMain()
的模块句柄存储在这个 DLL 的非共享全局变量中。
至于你的奖金问题,是的,它会起作用。
我不知道是什么让您相信模块句柄具有线程亲和性。
A HINSTANCE 记录为:
A handle to an instance. This is the base address of the module in memory.
传递给WinMain
的hInstance
参数是用于创建进程的模块的基址。它在进程的整个生命周期内都有效。既然是指针,就没有线程亲和性,可以在线程之间自由传递。事实上,这个指针在OS在进程中创建单个线程之前就已经存在了。
"special values" 不是 HINSTANCE
数据类型的 属性。它们是 ShellExecute:
合同的一部分
The return value is cast as an HINSTANCE for backward compatibility with 16-bit Windows applications. It is not a true HINSTANCE, however.
阅读奖励:What can I do with the HINSTANCE returned by the ShellExecute function?
在单个 .exe 应用程序中 WinMain
入口点有一个 HINSTANCE
参数,它应该是一个伪句柄(因为等同于 GetModuleHandle(NULL)
其中 return 是一个伪句柄-句柄,根据 MSDN)。我想它是 伪 因为有两个 特殊值 (例如 NULL
表示入口点模块)和常量用于 return 错误(小于 32)。
MSDN 将其明确描述为指向模块基地址的 指针 (现在相当于 HMODULE
);我们知道这对于 16 位应用程序可能具有完全不同的含义,但在 32/64 位世界中每个进程都有自己的地址 space 然后它的 exact 值是无用的,可能总是每个实例都相同,在其进程之外绝对没有意义。
综上所述,这是我的第一个问题:我们可以(正式地,尽管 MSDN 似乎自相矛盾)假设 HINSTANCE
是一个指针(即使老实说我看不出这有什么用)或者最好假设它是一个(伪)句柄(它的值是 opaque)?
我们假设它的值是不透明的,我的第二个问题:它的值对每个进程或每个线程有效吗?
我们可能认为进程句柄对每个进程都是有效的,但很少(极端)情况让我认为我们应该假设它对每个线程都有效。如果这些极端情况存在,那么(即使通常它似乎按预期每个进程工作)我们依赖于一个实现细节,一个未定义的行为 可能会因不同的体系结构、版本、环境而发生变化。
让我们看一个我看到问题的案例(代码太简单了,我只描述场景):DLL 通常有一个共享代码部分,但它们也可能有(即使它很不常见)一个共享数据部分(例如跨进程共享 扩展 数据或实现快速和肮脏的 IPC 机制)。它可能不常见但可能(并且很容易实现,例如在 VC++ 中,几乎没有 #pragma
s data_seg
和 comment(linker)
指令)。在这种情况下,我们知道(在我们的 DLL 中)我们无法比较 HINSTANCE
s(因为它们可能具有相同的值)而且在 IMO 中我们也不能 trust我们从线程 A(在我们的 DLL 中)获得的 HINSTANCE
与我们在线程 B 中获得的 HINSTANCE
相当。简而言之:每次我们需要一个 HINSTANCE
我们必须调用 GetModuleHandle(NULL)
才能获得 actual 每个线程有效的
作为 bonus 我也会理解这如何适用于 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 ...
If this parameter is NULL, GetModuleHandle returns a pseudo handle to the current process. [...] A pseudo handle is a special constant that is interpreted as the current thread handle. The calling thread can use this handle to specify itself when a thread handle is required.
显然,桌面应用程序并非如此(除其他差异外)。
进程中的每个模块都有一个模块句柄,它也是该模块的基地址。传递给 WinMain
的 hInstance
参数是进程主模块的基地址。因此,它在整个过程中都是有效的,因为该过程只有一个虚拟地址 space.
传递给 WinMain
的 hInstance
参数总是等于 GetModuleHandle(NULL)
。
如果您愿意,您通常可以将模块句柄视为不透明的。也就是说,您通常不需要取消对指针的引用,只需将其传递给需要 HMODULE
个参数的 API 函数即可。 None 更改该值在不同线程中是否有效。该值是每个进程的值。
我无法理解你的奖金问题。我怀疑这源于模块句柄是每个线程的错误假设。一旦您接受模块句柄在进程内的所有线程中具有相同的含义,您的绝大多数问题就会消失。
我在 MSDN 中没有看到任何地方提到 GetModuleHandle()
在任何情况下都返回伪句柄(不同于 GetCurrentProcess()
/GetCurrentThread()
)。因此,不存在模块句柄的线程亲和性。它们都是进程范围的,这就是 GetModuleHandle()
的文档警告线程问题的具体原因。
由于您提到的原因,作为指向模块基址的指针只能证实这一点。
因此,对于您的第一个问题,我的回答是可以安全地假设它们是指针,如果它们被记录为指针的话。否则,总是安全地将它们视为不透明句柄,并且知道它们永远不会伪句柄。
关于你的第二个问题,我确认它是按进程的。
对于你的场景,我说因为 GetModuleHandle(NULL)
在 DLL 中是无用的,最简单的方法是将传递给 DllMain()
的模块句柄存储在这个 DLL 的非共享全局变量中。
至于你的奖金问题,是的,它会起作用。
我不知道是什么让您相信模块句柄具有线程亲和性。
A HINSTANCE 记录为:
A handle to an instance. This is the base address of the module in memory.
传递给WinMain
的hInstance
参数是用于创建进程的模块的基址。它在进程的整个生命周期内都有效。既然是指针,就没有线程亲和性,可以在线程之间自由传递。事实上,这个指针在OS在进程中创建单个线程之前就已经存在了。
"special values" 不是 HINSTANCE
数据类型的 属性。它们是 ShellExecute:
The return value is cast as an HINSTANCE for backward compatibility with 16-bit Windows applications. It is not a true HINSTANCE, however.
阅读奖励:What can I do with the HINSTANCE returned by the ShellExecute function?