Adriano Repetti
Adriano Repetti

Reputation: 67128

Is HINSTANCE valid across threads?

In a single .exe application WinMain entry point has an HINSTANCE parameter which should be a pseudo-handle (because equivalent to GetModuleHandle(NULL) which returns a pseudo-handle, according to MSDN). I suppose it's pseudo because there are both special values (such as NULL to mean entry-point module) and constants used to return an error (anything less than 32).

MSDN explicitly describes it as a pointer (nowadays equivalent to an HMODULE) to module base address; we know this may have a completely different meaning for 16 bit applications but in 32/64 bits world each process has its own address space then its exact value is useless, probably always the same for each instance and absolutely meaningless outside its process.

All these said, this is my first question: can we (formally, despite MSDN seems to be contradictory) assume HINSTANCE is a pointer (even if honestly I don't see any use for this) or it's better assume it's a (pseudo) handle (where its value is opaque)?

Let's assume it's value is opaque, my second question: is its value valid per-process or per-thread?

We may think a process handle is valid per-process but few (corner) cases make me think we should assume it's valid per-thread. If these corner cases exist then (even if usually it seems to work as expected per-process) we're relying on an implementation detail, an undefined behavior subject to change for different architectures, versions, environments.

Let's see a case where I see an issue (code is so trivial that I just describe scenario): DLLs has usually a shared code section but they may also have (even if it's pretty uncommon) a shared data section (for example to share expansive data across processes or to implement a quick-and-dirty IPC mechanism). It may be uncommon but possible (and pretty easy to implement, for example in VC++, with few #pragmas data_seg and comment(linker) directives). In this case we're aware (inside our DLL) that we can't compare HINSTANCEs (because they may have same value) but also IMO we can't trust HINSTANCE we had from thread A (within our DLL) is comparable to HINSTANCE we have in thread B. In short: each time we need an HINSTANCE we have to call GetModuleHandle(NULL) to get actual per-thread valid one.

As bonus I would also understand how this applies to HINSTANCE we get as parameter when it comes from WinMain. In theory it's per-thread but, for example, CreateWindow() will resolve it correctly (because scope is current executing process, of course assuming calling thread has its own message loop)?

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 it seems I was completely wrong, I did remember thread affinity for HMODULE but it applies to 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.

Obviously this isn't true for desktop applications (among other differences).

Upvotes: 4

Views: 1130

Answers (3)

IInspectable
IInspectable

Reputation: 51506

A HINSTANCE is documented as:

A handle to an instance. This is the base address of the module in memory.

The hInstance parameter passed to WinMain is the base address of the module used to create the process. It is valid throughout the lifetime of a process. Since it is a pointer, it doesn't have thread affinity, and can be freely passed between threads. In fact, this pointer already exists before the OS has created a single thread in a process.

The "special values" are not a property of the HINSTANCE data type. They are part of the contract of ShellExecute:

The return value is cast as an HINSTANCE for backward compatibility with 16-bit Windows applications. It is not a true HINSTANCE, however.

Bonus reading: What can I do with the HINSTANCE returned by the ShellExecute function?

Upvotes: 2

Medinoc
Medinoc

Reputation: 6608

I see no mention anywhere in MSDN of GetModuleHandle() ever returning a pseudo-handle in any circumstances (unlike GetCurrentProcess()/GetCurrentThread()). Therefore, there is no such thing as thread affinity for module handles. They're all process-wide, which is specifically why GetModuleHandle()'s documentation warns about threading issues.

Being a pointer to the module's base address only confirms it, for the reasons you evoked.

So, to your first question, I answer it's safe to assume them to be pointers if they are documented to be so. Otherwise, it's always safe treat them as opaque handles, and know they are never pseudo handles.

To your second question, I confirm it's per-process.

To your scenario, I say since GetModuleHandle(NULL) is useless from a DLL, the simplest way is to store the module handle passed to DllMain() in a non-shared global variable for this DLL.

As for your bonus question, yes it would work.

I have no idea what led you to believe module handles had thread affinity.

Upvotes: 1

David Heffernan
David Heffernan

Reputation: 613412

Each module in the process has a module handle that is also the base address of that module. The hInstance argument passed to WinMain is the base address of the main module of the process. As such, it is valid throughout the process because the process has a single virtual address space.

It is always the case that the hInstance argument passed to WinMain is equal to GetModuleHandle(NULL).

You can usually treat module handles as opaque if you wish. That is, you don't generally need to de-reference the pointer and simply pass it along to API functions that require HMODULE arguments. None of that changes whether or not the value is valid in different threads. The value is a per-process value.

I cannot make any sense of your bonus question. I suspect that stems from a mistake assumption that module handles are per-thread. Once you accept that a module handle has the same meaning in all threads within a process, the vast majority of your question dissolves.

Upvotes: 2

Related Questions