Avoiding Multiple INSTANCES OF An Applicationby INSEPH M. Newcomer Learn The Right Way To Limit Your Application To Run Only One Instance.
Abstract
For a variety of reasons it is often desirable to limit the number of running instances of a program to exactly one on a particular computer. There is a lot of folklore around about how to do this, and much of it is wrong. There are a Few Good Methods, And A Lot of Flawed Ones.
I know. I used a flawed one for years. I Even Put The Technique IN OUR Book, "Win32 Programming". I Feel Suitably Embarrassed.
This Essay Examines Some of the Many Solutions, and Points Out The Problems In Those That Will Fail, And The Conditions Under Which they fail.
What is real dangerous is this Microsoft Not Only Does Not Document The Correct Method, But Offers Two Code Examples of a Fatally-Flawed Solution, Which i Discuss in Detail.
What is a "multiple instance"?
One of the interesting points that was raised by one correspondent, Daniel Lohmann (mailto:. (Daniel@uni-koblenz.de) is that the term "multiple instance" was not well-defined in an earlier version of this essay He points out that "multiple instance" can mean different things in the multiuser environment of NT. He points out that it is possible to use API calls such as LogonUser, CreateProcessAsUser, CreateDesktop and SwitchDesktop, and therefore it is possible to have multiple users and multiple desktop sessions Running under diffounts.
Therefore, There Area Several Issues About Why One Would Not Want Multiple Instances of An Application. He Categorized Them AS
Avoiding multiple instances started in the same user session, no matter how many desktops that user session has, but allowing multiple instances to run concurrently for different user sessions. Avoiding multiple instances started in the same desktop, but allowing multiple instances to run as long as each one is in a separate desktop. Avoiding multiple instances started for the same user account, no matter how many desktops or sessions running under this account exist, but allowing multiple instances to run concurrently for sessions running under a different user account. Avoiding multiple instances started on the same machine. This means that no matter how many desktops are used by an arbitrary number of users, there can be at most one instance of the program running.He observes that many programmers actually mean (a), and sometimes mean ( b) OR (c). The Technique Outlined in The earlier version of this Essay always interpreted the questions as (d). Note That in the case of Win9x / Milleni Um, ONLY (D) exists, Since There is Only One User Session and One Desktop Per Machine.
Thus, as you read the essay below, be aware that we are answering only (d) using this technique Afterwards, I will present his proposed solutions for being able to extend this technique to include (a) -.. (C) He observes that certain "service-oriented" applications that fit into the class of (d) are often better done as System Services. The service may be started on-demand or at system startup, but all users and all desktops share the same common service.
The Correct Solution: Using A Kernel Object
The following code is an adaptation of an example posted in the microsoft.public.mfc newsgroup by David Lowndes. His example had an option that used FindWindow, which for reasons I explain below will not work reliably. Instead, my version uses what I consider a more reliable technique to locate the window of the other instance. His code also used a shared (inter-process) variable which introduces another problem which I discuss later. However, if you deem the problem unimportant, you may choose to use the simpler . version This solution is the general solution on which the multi-desktop and multi-user solutions are based; the key difference is in how the unique ID is formed, a topic discussed later.In the example below, you must declare the function CMyApp :: SEARCHER AS A Static Class MEMBER, for Example
Static Bool Callback Searcher (HWND HWND, LPARAM LPARAM);
You Have to Declare A Registered Window Message, for Example, UWM_ARE_YOU_ME. For the details of this, See My Essay On Message Management. In The CMAINFRAME CLASS DEFINITION, ADD A HANDLER For this message:
AFX_MSG LRESULT OnAreYoume (WPARAM, LPARAM);
And add a message_map entry
ON_REGISTERED_MESSAGE (UWM_ARE_YOU_ME, INAREYOUME)
Implement IT As Follows:
LResult CMAINFRAME :: Onreyoume (WPAREYOUME (WPARAM, LPARAM)
{
Return UWM_ARE_YOU_ME;
} // cmainframe :: onreyoume
Now You Can Implement The Searcher Method Which Looks for The Target Window.
Bool Callback CMYAPP :: Searcher (HWND HWND, LPARAM LPARAM)
{
DWORD RESULT;
LResult OK = :: SendMessageTimeout (hwnd,
UWM_ARE_YOU_ME,
0, 0,
SMTO_BLOCK | SMTO_ABORT_IF_HUNG,
200, & result);
IF (ok == 0) Return True; // ignore this and continue
IF (result == uWM_ARE_YOU_ME)
{/ * Found IT * /
HWND * TARGET = (hwnd *) lparam; * target = hwnd;
Return False; // Stop Search
} / * Found IT * /
Return True; // Continue Search
} // CMYAPP :: Searcher
/ / -------------------------------------------------------------------------------------------- ------------
Bool CmyApp :: InitInstance ()
{
Bool alreadyrunning;
Handle HmutexoneInstance = :: Createmutex (Null, False,
_T ("MyAppname-088FA840-B10D-11D3-BC36-006067709674"));
// What changes for the alternative solutions
// is the uid in the above call
// Which Will Be Replaced by a call on
// createExClusionname
Alreadyrunning = (:: getLastError () == Error_Already_exists ||
:: getLastError () == Error_Access_denied;
// the call fails with error_access_denied if the Mutex WAS
// CREATED IN A DIFFERENT USSION BECAUSE OF PASSING
// null for the security_attributes on mutex code;
AlreadyRunning
{/ * kill this * /
HWND HOTHER = NULL;
Enumwindows (Searcher, (LPARAM) & HOTHER);
IF (HOTHER! = NULL)
{/ * POP UP * /
:: setForegroundWindow (HOTHER);
ISICONIC (HOTHER)
{/ * restore * /
:: ShowWindow (HOTHER, SW_RESTORE);
} / * restore * /
} / * POP Up * /
Return false; // Terminates the creation
} / * kill this * /
// ... Continue with initInstance
Return True;
} // CMYApp :: InitInstance
After you read the section on Race Conditions you may see that this code has a race condition. If the Mutex was created by instance 1, which is still coming up (it has not created its main window yet), and instance 2 finds that the Mutex already exists, it tries to find the window for Instance 1. But since Instance 1 is not yet there, this code does not pop up the window for Instance 1. But that's OK. Because Instance 1 has not yet created its window, IT Will Proceed To Create ITS WINDOW SUCCESSFULLY AND POP UP UPUST LIKE You'd Expect.The Basic Problem: Race Conditions
There is, in any system with potential parallel execution threads, or pseudo-parallel execution threads, a phenomenon known as the "race condition". This is a situation in which the correctness of the cooperation between two (or more, generally) processes is DETERMINED by The Timing Relationship of the Execution of Certain Paths in the program. if Program A Reaches A Certain Point of Execution Before ProGram B Has Set Things Up Properly, Program a Could Fail.
Many of the "Find The Other Executing Program Instance" Solutions Fail Because of Race Conditions. We'll Examine these After WE Look At A Way That Actually Seems To Work.
Why FindWindow Doesn't Work
One of the popular pieces of folklore is to that you can use the FindWindow API call to avoid multiple instances. This API is very dangerous to use. There is an excellent chance that your application will forever hang while executing this call. In any case, even if the conditions that would cause it to hang do not exist, the techniques surrounding its use are so deeply flawed that such a technique can not possibly work So it has two failure modes:. no instance comes up, or multiple instances come up Neither. of these is terribly satisfactory.Unfortunately, this folklore is so popular that even Microsoft believes it. In their KB article Q109175, they suggest a download of a program onetime.exe which is a self-extracting zipfile that contains an example that does precisely this . KB Q141752 and ITS Accompanying Onet32.exe Propagate this Myth. Trust Me. Been there.. Do Not Use this technique.
Why does not it work? Well, it works if everything is perfect. But when there is a tiny, minor, little thing wrong, it fails catastrophically, at least as far as the end user is concerned. Furthermore, the thing that is "WRONG" does Not Mean there is a malfunction. A Completely Correctly-Function Program Cause The Failure.
The FindWindow Call IS:
HWND CWND :: FindWindow (LPCTSTR CAPTION)
The classname parameter can either NULL, which means that all windows classes will match, or a window class name (The underlying API call can also accept an HATOM cast to an LPCTSTR, but it is rare in MFC to have the HATOM from a.: :.. RegisterWindowClass call) The caption parameter is a string which is compared to the caption of each window that is enumerated The function returns the HWND of the first window whose class name matches classname and whose caption matches caption.Why will not this work ?
Well, Unless the WOUR App Using AFXREGISTERCLASS, you don't know, "That's OK, I'll Just Use Null, But I Know The Caption Name".
You don't.
Well, Maybe you do. This week. In this country. As long as it isn't an mdi app.
Well, for one thing, you do not want to hardwire a constant value for the caption value. This would be a colossally losing idea. Because if you internationalize the application, you will change the caption name. And so you lose.
Aha! You will put the caption name in the resource file. Then you'll use LoadString to get it. Well, that's fine, but then you have to make sure that the same LoadString is used to set the caption. OK, maybe this Works. for a dialog-based app. But for other apps, the file name of the input file will be incorporated input, and you can't predict what this isless, so already findwindow is readyless.
SO You can't use FindWindow if you are looking for the caption.
But things get worse. FindWindow really does an EnumWindows call, and for each top-level window handle found, it performs a GetWindowText operation. This is done by sending a WM_GETTEXT message to the window handle. But if the thread that owns the handle is blocked, on a Semaphore, a Mutex, an Event, an I / O operation, or some other manner, the SendMessage will block until thread frees up and runs. But this may never happen that. So FindWindow will block forever, and your application will never come up.When you have to drive out some considerable distance because your best client can not get the app to start on his machine, you want to make sure that (a) this never happens to him again and (b) it Certainly Will Never happen to any of his customers! it Took a long time to find this one.
So, You Say, That's Clearly a Losing IDEA. I'll Use The Class Name. After All, That's What Microsoft Does In Their Examples, and Their Examples, And Theme Must Know What They Are Doing.
The code Microsoft supplies as an example is flawed. But the superficial flaws mask the deep flaws. For example, the class name is given as a string name. What is not readily apparent is that you really, really have to modify this name to be .
Now here's a little story, which you should all take to heart: a long time ago, I wrote a Win16 application It registered a window class "generic" because I cloned it from the Microsoft generic example The complaint was "your program fails.. ". Guess what? It tried to register the window class" generic ", which someone else who had cloned their application from the generic example had also used. Back in those days, window class names were systemwide global names, so it failed to register the class.Today, you say, those names are not globally unique;.. it is perfectly valid to have a program A that registers a class "MainWindow" and a program B that registers a class "MainWindow" This is true But only if neither ever cares about the other. If Program B starts asking what a window's class name is, it can not tell whether the name "MainWindow" is registered by an instance of itself, or by some other program that never heard of it.
So the first thing to do is make sure your class name is unique, because even though the names are not global, you are about to search for that class name, and you can not tell which of the many registered class instances of the same Name you are talking to.
OK, so you have read my other essays and know how to do this by using GUIDGEN. This creates a 128-bit number, expressed as a hex character string, which is known to be globally unique. So you just append it to the human -readable class name. Cool. So you know the name is unique, and you're going to search using that class name. Since you're not falling into the WM_GETTEXT trap, it will not matter if the other task is hung in Some Way, Because After, It Doesn't Have To Be Running For You to Get The Class Information About The Window Class.guess What. You've Just Fallen Into The Race Condition Trap.
Here's a skeleton of the code from one of the microsoft example. I've incruded Only The Good Parts.
LPCTSTR LPSZUNIQUECLASS = _T ("MyNewClass");
/ / -------------------------------------------------------------------------------------------- ---
Bool CMAINFRAME :: PrecreateWindow (CreateStruct & Cs)
{
// use the specific class name We Established Earlier
Cs.lpszclass = lpszuniqueclass;
// [a]
Return CmdiframeWnd :: PrecreateWindow (CS);
}
/ / -------------------------------------------------------------------------------------------- ----------------
Bool conet32app :: initInstance ()
{
// if a previous instance of the application is already running,
// Then Activate it and return false from initInstance to end
// Execution of this instance.
IF (! firstinstance ()) // [b]
Return False;
// register ur unique class name what we wish to us
WNDCLASS WNDCLS;
MEMSET (& WNDCLS, 0, SIZEOF (WNDCLASS)); // Start with Null Defaults
Wndcls.style = ...;
... Other Wndcls Variable INITS Here, IrRrelevant for
... this discussion
// Specify Our OWN Class Name for Using FindWindow Later
Wndcls.lpszclassname = lpszuniqueclass;
// register new class and exit if it failsif (! AFXREGISTERCLASS (& WNDCLS)) // [c]
{
Return False;
}
... rest of initinstance here ...
CMAINFRAME * PMAINFRAME = New cmainframe; // [d]
... more here
Return True;
}
/ / -------------------------------------------------------------------------------------------- ----------------
BOOL Cont32app :: firstinstance ()
{
CWND * PWNDPREV, * PWNDCHILD;
// DETERMINE IF ANOTHER WINDOW with OUR Class Name EXISTS ...
IF (PWNDPREV = CWnd :: FindWindow (LPSZUNIQUECLASS, NULL) // [e]
/ / -------------------------------------------------------------------------------------------- ----------------
An application will normally initialize by executing the code in such a way that the sequence, as indicated in the // [x] comments (which are alphabetical top-to-bottom, although the code does not execute that way) is as follows :
[B] - [E] - [C] - [D] - [A]
Consider The Sequence Shown Below of Two Applications Initializing.
TimeApplication instance 1Application instance 21B FirstInstance () 2E FindWindow () => FALSE 3 B FirstInstance () 4 E FindWindow () => FALSE5C AfxRegisterClass () 6 C AfxRegisterClass () 7D new CMainFrame 8 D new CMainFrame9A (in PreCreateWindow) 10 A ( In PrecreateWindow)
Note that this successfully passes the test! At time 2, when Application instance 1 calls FindWindow, there is no other window instance. So FindWindow returns FALSE, indicating there is no second instance. So the initialization sequence proceeds. But meanwhile Application instance 2 is initializing. At time 4, when it executes FindWindow, there is no other window instance of the desired type. So FindWindow returns FALSE, and Application instance 2 knows it is the only instance. So it continues to initialize. So by the time Application instance 1 creates the window that FindWindow would have found, the test is long since past when Application instance 2 would have found it. And we get two running instances! If you think this can not happen, you are living in a different world than the Real One. I SAW IT Happen. Repeatedly. ApproxImately One Time Out of Three.
I found this out when a client had a very fast Pentium and had configured the Win98 desktop to launch-on-single-click. Years of conditioning had taught the user to double-click the icon, so whenever he double-clicked the icon, He Launched Two Copies of the app. And far too off! Both Came Up!
So why does the CreateMutex method work? Does not it suffer from the same problem? No, because the creation of a Mutex is an atomic operation in the kernel. The creation of a Mutex is guaranteed to complete before any other thread can successfully create a Mutex. Therefore, we are absolutely guaranteed that the creation of the object and the test for its existence are a single operation, not separated by thousands or tens of thousands preemptible instructions as the FindWindow or (as I discuss in the next section) SendMessage Methods.
Daniel Lohmann, who has made significant contributions to this article, also points out that in terms of "uniqueness", FindWindow has a problem in that it enumerates only windows in the same desktop as the calling thread. Therefore, if there is another instance running ON Another Desktop You Won't Find It To Pop IT Up! SendMessage: Race Conditions
One of the most common folkloristic methods (and one I used for years, alas) is to use EnumWindows, do a SendMessage to each window, and look at the return result from SendMessage. What you send is a Registered Window Message (see my essay On Message Management, Andiff You Receive This Message You Return True. All Other Windows Will Not Understand this and return false. This Turns Out to be deeply flawed in a variety of ways.
Note That this method always workted in Win16 Because. it is the referenceive multitasking of win32 That Makes this Method fail. And it does.
The SendMessage can hang indefinitely. But if the thread that owns the handle is blocked, on a Semaphore, a Mutex, an Event, an I / O operation, or some other manner, the SendMessage will block until that thread frees up and runs. But this may never happen .. so you have't Gained Any Reliability.
You can Solve this by using sendmessagetimeout. This more-or-legs works. You will Typically Choose A Short Timeout, for Example About 200ms.
But it gets Worse.
Microsoft, in violation of all known Windows specifications, has created an application that does not pass messages it does not understand to DefWindowProc, which would return 0 for any message it does not understand. Instead, they have a component, apparently associated with Personal Web Server, which has the truly antisocial property of returning 1 for every message sent to its top-level window, whether it understands it or not. So you can not rely on a zero meaning it is not your app.Well, this could BE SOLVED. WHEN I Needed to do this for Another Reason, I ended to return The registered window Message Value, Which Avoids The Microsoft Blunder.
So Weame Solved The Problem of Timeouts and Bogus Messages. We know Not to Depend On The Caption Contents, And Probably Want To Avoid Worrying About The Window Class Name. So why doesn't this work?
BECAUSE OF A MUCH More Fundamental Problem: a Race Condition. Just Like The One Described in The Previous Section.
The code did an EnumWindows loop and for each HWND it did a SendMessage. So what happened was that application instance 1 searched for another instance of itself, did not find one, and proceeded to come up. Meanwhile, application instance 2 searched for an instance of itself, but since instance 1 had not yet come up and created its own main window, instance 2 did not find a conflicting instance, and it proceeded to come up. Using a scheme similar to the table I used to demonstrate why the basic Findwindow Method Doesn't Work, You Can show That Method Will Fail for the Same Reason.
This Is The Most Fundamental Failure of this Mechanism, And A Reason It cannot be used.
Shared Variable: a Different Problem
Another method which has been proposed is to use a shared variable between all instances of the application This can be done by creating a shared data segment The technique is of the form:.. #Pragma comment (linker, "/SECTION:.shr, RWS ")
#pragma data_seg (". shr")
HWND HGLOBAL = NULL;
#pragma data_seg ()
// in The Startup Code:
// g_hwnd is set when the main window is created.
Bool CmyApp :: InitInstance ()
{
Bool alreadyrunning;
Handle hmutexoneInstance = :: Createmutex (Null, True,
_T ("MyAppname-088FA840-B10D-11D3-BC36-006067709674"));
Alreadyrunning = (getLastError () == error_already_exists;
IF (hmutexoneinstance! = null)
{
:: ReleaseMutex (HMutexoneInstance);
}
AlreadyRunning
{/ * kill this * /
HWND HOTHER = g_hwnd;
IF (HOTHER! = NULL)
{/ * POP UP * /
:: setForegroundWindow (HOTHER);
ISICONIC (HOTHER)
{/ * restore * /
:: ShowWindow (HOTHER, SW_RESTORE);
} / * restore * /
} / * POP Up * /
Return False;
// Terminates the Creation
} / * kill this * /
// ... Continue with initInstance
Return True;
} // CMYApp :: InitInstance
This almost works. It avoids the fundamental race condition, because the CreateMutex call is an atomic operation. No matter what the relative timings of the two processes are, exactly one of them will create the Mutex first, and the other will get the ERROR_ALREADY_EXISTS. Note That I used guidgen to get a guarance-unique ID.
The use of the shared variable presents a problem. This shared variable is only shared with other instances from the same executable. This means that if you run a version of the debug executable, and a version of the release executable, one can not find the other's window to pop it up. Thus, when an instance finds that it is a duplicate (they still share the same Mutex name), it can not find its other instance to pop it up. This will confuse you.The code, however, is simpler .
I do not understand why the Mutex is created in owned mode (the second parameter is TRUE). The Microsoft documentation even says that when doing a CreateMutex from separate threads that this parameter must always be FALSE because otherwise it is impossible to determine which thread Actually Owns The Mutex. Since this Mutex Is Not Used in Any Way In The Code, The Use of The True Parameter Doesn't Seem To have any value.
Daniel Lohmann has observed that shared memory is shared even if the processes run under different user accounts, as long as the instances are on the same machine. Of course it's also shared if the instances reside on different desktops. Therefore, the use of shared variables HAS Marginal Value - And May Even Be Harmful - WHEN You Generalize The Notion As He Indicates in His Suggestions In The next section.
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 ITSSS Only ONE OF THEE 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 .
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- "."
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 At The Top of this Article.ExClusion.h
#define unique_to_system 0
#define unique_to_desktop 1
#define unique_to_session 2
#define unique_to_trustee 3
CString CreateExClusionName (LPCTSTR GUID, UINT KIND = Unique_to_System);
Exclusion.cpp
#include "stdafx.h"
#include "exclusion.h"
/ ************************************************** ***********************************
* CreateExClusionname
* INPUTS:
* LPCTSTR GUID: The Guid for the Exclusion
* Uint Kind: Kind of Exclusion
* Unique_to_system
* Unique_to_desktop
* Unique_to_Session
* Unique_to_Trustee
* Result: cstring
* A name to use for the Exclusion Mutex
* Effect:
* Creates The Exclusion Mutex Name
* Notes:
* The Guid IS CREATED BY A DECLATION SUCH AS
* #Define unique _t ("myappname- {44e678f7-da79-11d3-9fe9-006067718d04}")
*********************************************************** ********************************** /
CString CreateExClusionName (LPCTSTR GUID, UINT KIND)
{
Switch (kind)
{
/ * Kind * /
Case unique_to_system:
Return CSTRING (GUID);
Case unique_to_desktop:
{/ * desktop * /
Cstring s = guid;
DWORD LEN;
HDESK Desktop = GetThreadDesktop (); Bool Result = GetUserObjectInformation (Desktop, Uoi_Name, Null, 0, & Len);
DWORD ERR = :: getLastError ();
IF (! Result && err == error_insuffect_buffer)
{/ * NT / 2000 * /
LPBYTE DATA = New Byte [LEN];
Result = GetUserObjectInformation (Desktop, Uoi_name, Data, Len, & Len);
S = _T ("-");
S = (LPCTSTR) DATA;
DELETE [] DATA;
} / * NT / 2000 * /
Else
{/ * Win9x * /
S = _t ("- win9x");
} / * Win9x * /
Return S;
} / * desktop * /
Case Unique_to_Session:
{/ * session * /
Cstring s = guid;
Handle token;
DWORD LEN;
Bool Result = OpenProcesstoken (GetCurrentProcess (), Token_Query, & token;
IF (Result)
{/ * Nt * /
GetTokenInformation (Token, Tokenstatistics, NULL, 0, & LEN);
LPBYTE DATA = New Byte [LEN];
GetTokenInformation (Token, Tokenstatistics, Data, Len, & Len);
Luid Uid = ((ptoken_statistics) data) -> AuthenticationID;
DELETE [] DATA;
CString T;
T.Format ("-% 08x% 08x"), uid.highpart, uid.lowpart;
Return S T;
} / * Nt * /
Else
{/ * 16-bit OS * /
Return S;
} / * 16-bit OS * /
} / * session * /
Case Unique_to_Trustee:
{/ * trustee * /
Cstring s = guid;
#define namelength 64 tchar
Username [nameLength];
DWORD Usernamelength = Namelength;
Tchar DomainName [nameLength];
DWORD DOMAINNAMALELENGTH = Namelength;
IF (GetUserName (Username, & UsernameLength))
{/ * Get Network Name * /
// the netapi calls are very Time Consuming
// this technique gets the domain name via an
// environment variable
DomainNameLength = ExpandenVironmentStrings (_T ("% UserDomain%"), DomainName, Namelength;
CString T;
T.Format ("-% S-% S"), DomainName, UserName; s = T;
} / * get network name * /
Return S;
} / * trustee * /
DEFAULT:
Assert (false);
Break;
} / * Kind * /
Return CSTRING (GUID);
} // createExClusionname
Back in the original example, replace the string which I hardwired into the :: CreateMutex call with a call on createExclusionName with the desired specification to get a correctly-formatted unique name to use for the Mutex.
Summary
The traditional methods of multiple-instance detection, including those documented by Microsoft, are deeply flawed. There is only one method that is known to work correctly, creating a kernel object, and it is documented here in two of the many possible forms that this Technique Could Use.
The notion of "unique" should be well-defined; in most cases, it means "unique to a session" and perhaps "unique to a desktop", and the naive approach can actually keep independent users from running concurrently on an NT system.
Acknowledgements
A special Wave Of The Flounder Fin to Daniel Lohmann for his excellent suggestions to the article! A pure-C version of his code, which is slightly different from what I implemented, can be found here. His Web Site is worth looking at if you Are Into Multiple Desktops and Terminal Server.
.
Send mail to newcomer@flounder.com with quintions or Comments About this article.copyright? 1999 CompanyLongname All Rights Reserved.http://www.pgh.net/~newcomer/mvp_tips.htm