Generalizing the solution for NT
Daniel Lohmann pointed out the fundamental defect of the above mechanism. Although
it is reliable, it is not complete, in that it addresses only one
of the three possible meanings of "unique instance".
- Avoiding multiple instances started in the same user session.
- Avoiding multiple instances started in the same desktop.
- Avoiding multiple instances started in any session of the same user account.
- Avoiding multiple instances started on the same machine.
In particular, he points out that my way of creating the Mutex name uses a
system-global name, guaranteed to be unique for the application, but which is
known to all users, sessions, and desktops. Note that this raises the same issue
with respect to any use of global names for Mutexes, Semaphores, Events, and
even shared memory-mapped files: the assumption that a single name is valid depends
upon your interpretation of the above three points. Thus, if you are building
a system that uses synchronization primitives and which requires a solution other
than (d), you will have to apply the techniques below to the synchronization
primitive naming as well.
He points out that in the Terminal Server edition of NT (which is built into
Windows 2000), the kernel no longer has a single "global" namespace,
but in fact each Terminal Server session has a private namespace. System services
share a common namespace for what is called the "console session".
He points out that "this all results in consuming much more memory and making
some programming tasks quite tricky, but the result is that every user logged
into the Terminal Server is able to start its E-Mail client".
There's another little fix he made to my code:
The CreateMutex() call fails with ERROR_ACCESS_DENIED if the
Mutex was created in another user's session. This comes from passing NULL
for the SECURITY_ATTRIBUTES which results in default security settings.
The typical default DACL allows only CREATOR/OWNER and SYSTEM access to the
object.
His proposed solution is to extend the name of the Mutex beyond the GUID technique
I use, to address the solutions of (a)-(c). He writes:
"I start with (b) because it is simpler. Using GetThreadDesktop()
you get a handle to the desktop your thread is running on. Passing this to GetUserObjectInformation(),
you get the name of the desktop, which is unique".
"Even (c) is quite easy. The solution is to add the current users account
name. Using GetUserName() you get the current users account
name. You should qualify it with the current users domain, which can be determined
using GetEnvironmentVariable() with USERDOMAIN
as variable name."
"For (a) it's a little bit more complicated. You have to open the process
token using OpenProcessToken(). Pass this token to GetTokenInformation()
to retrieve a TOKEN_STATISTICS structure. The AuthenticationId
member of this structure, a 64-bit number (coded as an LUID), contains
the unique id of the login session. Convert this into a string".
Based on his description, I created the following subroutine and header file.
Note that for any given application, you must decide at compile time which exclusion
option you want; for example, if you want to have the application unique to a
desktop, choose the UNIQUE_TO_DESKTOP option to generate the key. If you
have an application that chooses this dynamically, you can have one running in
the system, thinking it is unique, and one running on the desktop, thinking it
is unique. I built a little project to test this code, which you can download.