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++ 中,几乎没有 #pragmas data_segcomment(linker) 指令)。在这种情况下,我们知道(在我们的 DLL 中)我们无法比较 HINSTANCEs(因为它们可能具有相同的值)而且在 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.

显然,桌面应用程序并非如此(除其他差异外)。

进程中的每个模块都有一个模块句柄,它也是该模块的基地址。传递给 WinMainhInstance 参数是进程主模块的基地址。因此,它在整个过程中都是有效的,因为该过程只有一个虚拟地址 space.

传递给 WinMainhInstance 参数总是等于 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.

传递给WinMainhInstance参数是用于创建进程的模块的基址。它在进程的整个生命周期内都有效。既然是指针,就没有线程亲和性,可以在线程之间自由传递。事实上,这个指针在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?