There can be only few of us (instances of an application)!
Let's say you want your application to be instanced only a limited number of times - not once, as described before, but only twice, for example!File mapping
The last technique we are to discuss is about file mappings in Windows. Technically, file mapping is the association of a file's contents with a portion of the virtual address space of a process. The system creates an in-memory file mapping object to maintain this association. What file mapping offers is sharing memory between two or more applications - or in our case, sharing memory between two or more application instances. The file mapping functions allow a process to create file mapping objects and file views to easily access and share data.
CheckPrevious.RestoreIfRunning
Let's go from the beginning. To "run-once" enable our application, we'll place all the code required in a separate unit. Your first task is to add an empty code unit to the project. Name (save it as) the unit 'CheckPrevious'.
You'll also need to alter the source code of your project to look like:
program Project1; uses Forms, Unit1 in 'Unit1.pas' {Form1}, CheckPrevious in 'CheckPrevious.pas'; {$R *.res} begin if not CheckPrevious.RestoreIfRunning(Application.Handle, 2) then begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end; end. |
The RestoreIfRunning function accepts two parameters; first parameter is the Handle of the application; the second (default) parameter is the number of allowed instances, of the application, running simultaneously. Since the second parameter is optional, if you want to enable only one instance, the following calls will have the same result:
CheckPrevious.RestoreIfRunning(Application.Handle, 1); //equals to: CheckPrevious.RestoreIfRunning(Application.Handle); |
The function returns True if the specified (maximum) number of instances is already running. If this is the case, the last instance is made active and the main form of the application is placed on top of other windows (restored if minimized). The function returns False if the number of running instances is lower that the allowed number, and the new instance gets executed.
So much about the project source, we now move to the code in the CheckPrevious unit. When using file mappings to prevent the second copy of an application to be executed , the following steps can be used:
- Create a file mapping object. While calling the create method save your custom data (Application.Handle).
- If the function succedes, the result is the handle to the file-mapping object.
- If the object existed before, we have a situation where the next instance of the application is trying to start running.
- If the object is created and there is no previous mapping, this is the first instance of the application.
- When creating the file-mapping from the first instance of an aplication, we have to make sure the Handle of the application is saved within the mapped object.
- If the mapping object existed before, meaning the second instance is to be executed, we need stop the process and bring the first instance "to life".
- When finished (application end), we must unmap the mapped object and close the handle it returned.
Only two instances, please!
We'll now go step-by-step through the code of the CheckPrevious unit.
First, the unit exposes only one function, RestoreIfRunning, in its interface section (making it public):
unit CheckPrevious; interface uses Windows, SysUtils; function RestoreIfRunning( const AppHandle : THandle; MaxInstances : integer = 1) : boolean; ... continues below ... |
Since we want to save the number of running instances in the mapped object (along with the last executed application handle), we need to create a record type:
implementation type PInstanceInfo = ^TInstanceInfo; TInstanceInfo = packed record PreviousHandle : THandle; RunCounter : integer; end; ... continues below ... |
The TInstanceInfo is a record type variable consisting of two fields: PreviousHandle holds the handle to the instance last started; RunCounter holds the number of instances (currently) running.
We'll now look in the RestoreIfRunning function source code:
var MappingHandle: THandle; InstanceInfo: PInstanceInfo; MappingName : string; RemoveMe : boolean = True; function RestoreIfRunning( const AppHandle : THandle; MaxInstances : integer = 1) : boolean; begin Result := True; MappingName := StringReplace( ParamStr(0), '\', '', [rfReplaceAll, rfIgnoreCase]); MappingHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, SizeOf(TInstanceInfo), PChar(MappingName)); if MappingHandle = 0 then RaiseLastOSError else begin ... continues below ... |
First, we define several local variables (MappingHandle, ..., RemoveMe). Next we dive into the main source...
Second, and as explained above, when dealing with file-mapping, we first need to create a file mapping object. The CreateFileMapping API call is responsible for this task.
Among other parameters the function requires you to specify the name of the mapping object (MappingName). Since this value must be unique, I've decided to use the full path of the application exe file. Since the MappingName must not contain the backslash character (\) I've used the StringReplace RTL function to remove it from the stirng.
Another important parameter is the size of the object we are to write in the mapped file. The SizeOf function is used to return the number of bytes occupied by a variable of type TInstanceInfo.
Finally, we check the result of the CreateFileMapping function: if the function fails and the return value is 0, we have an error situation (it might be that MappingName string has a backslash in it, for example). If all went well, we need to check for an existing mapped object:
if GetLastError <> ERROR_ALREADY_EXISTS then begin InstanceInfo := MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TInstanceInfo)); InstanceInfo^.PreviousHandle := AppHandle; InstanceInfo^.RunCounter := 1; Result := False; end ... continues below ... |
So, if the object did not exist before the function call, the GetLastError API function is different from the ERROR_ALREADY_EXISTS (it's a NO_ERROR) and we can be sure this is the first instance of this application executed.
The second file-mapping function, MapViewOfFile, comes handy now. The MapViewOfFile function returns a pointer to the file view. By using this pointer an application can read data from the file and write data to the file.
In our case, if this is the first instance of the application, we assign AppHandle (Application.Handle) to the PreviousHandle field of the InstanceInfo variable; we also set the RunCounter field to one (1).
And now, the interesting part. If the mapping object already exists, we have an "application already running" situation:
else //already runing begin MappingHandle := OpenFileMapping( FILE_MAP_ALL_ACCESS, False, PChar(MappingName)); if MappingHandle <> 0 then begin InstanceInfo := MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TInstanceInfo)); if InstanceInfo^.RunCounter >= MaxInstances then begin RemoveMe := False; if IsIconic(InstanceInfo^.PreviousHandle) then ShowWindow(InstanceInfo^.PreviousHandle, SW_RESTORE); SetForegroundWindow(InstanceInfo^.PreviousHandle); end else begin InstanceInfo^.PreviousHandle := AppHandle; InstanceInfo^.RunCounter := 1 + InstanceInfo^.RunCounter; Result := False; end end; end; end; end; (*RestoreIfRunning*) ... continues below ... |
So, if there already is a mapped object with the same name, we have a situation of an already running instance. This is where the third mapping function, OpenFileMapping, opens a mapped object. In case of a no-error situation, we call the MapViewOfFile to get our hands on the information inside the mapped object.
Now, if we have run out of the allowed instances (MaxInstances), we make sure the last instance gets restored and made active. If RunCounter is smaller then MaxInstances we simply set new value(s) for the InstanceInfo variable.
Note: the RemoveMe boolean variable is used to make sure we have the correct instance counter - in case of an application termination.
Finally, when the application ("this" instance) ends, we need to make sure that the view of the mapped file is unmapped; and the memory is freed. This is done in the finalization section of the unit (executed when the application terminates)
initialization //nothing special here //we need this section because we have the //finalization section finalization //remove this instance if RemoveMe then begin MappingHandle := OpenFileMapping( FILE_MAP_ALL_ACCESS, False, PChar(MappingName)); if MappingHandle <> 0 then begin InstanceInfo := MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TInstanceInfo)); InstanceInfo^.RunCounter := -1 + InstanceInfo^.RunCounter; end else RaiseLastOSError; end; if Assigned(InstanceInfo) then UnmapViewOfFile(InstanceInfo); if MappingHandle <> 0 then CloseHandle(MappingHandle); end. (*unit CheckPrevious*) |
And we are done! That's all folks. You now have the ultimate source to run-once (or twice, or...) enable your applications.
If you don't understanding something from the code, or if you simply want to add your view of the whole problem, I encourage you to post your questions and problems on the Delphi Programming Forum.
Tickling your imagination..
Stop for a moment! Take a look at the InstanceInfo record. We can store *anything* we want inside it. How about letting the next instance run only after a specified time period: store a TDateTime value with the current (Now function) date and time, and prevent the instance from running if "enough" time has not passed after the previous instance was executed. Enything else? No problem, just make sure you handle the "situation" properly."I'll be happier with a component to drop on a form"
Yes, in some situations it's quite easier to simply drop a component on a form and forget about all the hard coding that needs to be done if you want to limit the number of your application instances.Ok, you've won; in the next part of this article I'll make a component from this code, till then explore the source and learn!
On the last page of this article, you'll find the entire source...