One form of multitasking is the execution of two or more instances of the same application. Any application can generally be executed by a user in more than one instance, and it needs to be able to check for a previous instance already running, in order to disable this default behavior and allow for one instance at most. This section demonstrates several ways of implementing such a check, allowing me to discuss some interesting Windows programming techniques.
To find a copy of the main window of a previous instance, use the FindWindow API function and pass it the name of the window class (the name used to register the form's window type, or WNDCLASS, in the system) and the caption of the window for which you are looking. In a Delphi application, the name of the WNDCLASS window class is the same as the Object Pascal name for the form's class (for example, TForm1). The result of the FindWindow function is either a handle to the window or zero (if no matching window was found).
The main code of your Delphi application should be written so that it will execute only if the FindWindow result is zero:
var Hwnd: THandle; begin Hwnd := FindWindow ('TForm1', nil); if Hwnd = 0 then begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end else SetForegroundWindow (Hwnd) end.
To activate the window of the application's previous instance, you can use the SetForegroundWindow function, which works for windows owned by other processes. This call produces its effect only if the window passed as parameter hasn't been minimized. When the main form of a Delphi application is minimized, it is hidden, and for this reason the activation code has no effect.
Unfortunately, if you run a program that uses the FindWindow call just shown from within the Delphi IDE, a window with that caption and class may already exist: the design-time form. Thus, the program won't start even once. However, it will run if you close the form and its corresponding source code file (closing only the form simply hides the window), or if you close the project and run the program from the Windows Explorer. Consider also that having a form called Form1 is quite likely to not work as expected, as many Delphi applications might have a form with the same name. This will be fixed in the following versions of the code.
A completely different approach is to use a mutex, or mutual exclusion object. This is a typical Win32 approach, commonly used for synchronizing threads. Here you will use a mutex to synchronize two different applications or, to be more precise, two instances of the same application.
Once an application has created a mutex with a given name, it can test whether this object is already owned by another application by calling the WaitForSingleObject Windows API function. If the mutex has no owner, the application calling this function becomes the owner. If the mutex is already owned, the application waits until the time-out (the function's second parameter) elapses. It then returns an error code.
To implement this technique, you can use the following project source code:
var hMutex: THandle; begin HMutex := CreateMutex (nil, False, 'OneCopyMutex'); if WaitForSingleObject (hMutex, 0) <> wait_TimeOut then begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end; end.
If you run this example twice, you'll see that it creates a new, temporary copy of the application (the icon appears in the Taskbar) and then destroys it when the time-out elapses. This approach is certainly more robust than the previous one, but it lacks a feature: How do you enable the existing instance of the application? You still need to find its form, but you can use a better technique.
When you want to search for a specific main window in the system, you can use the EnumWindows API functions. Enumeration functions are peculiar in Windows, because they usually require another function as a parameter. These enumeration functions require a pointer to a function (often described as a callback function) as parameter. This function is applied to each element of the list (in this case, the list of main windows), until the list ends or the function returns False. Here is the enumeration function from the OneCopy example:
function EnumWndProc (hwnd: THandle; Param: Cardinal): Bool; stdcall; var ClassName, WinModuleName: string; WinInstance: THandle; begin Result := True; SetLength (ClassName, 100); GetClassName (hwnd, PChar (ClassName), Length (ClassName)); ClassName := PChar (ClassName); if ClassName = TForm1.ClassName then begin // get the module name of the target window SetLength (WinModuleName, 200); WinInstance := GetWindowLong (hwnd, GWL_HINSTANCE); GetModuleFileName (WinInstance, PChar (WinModuleName), Length (WinModuleName)); WinModuleName := PChar(WinModuleName); // adjust length // compare module names if WinModuleName = ModuleName then begin FoundWnd := Hwnd; Result := False; // stop enumeration end; end; end;
This function, which is called for each nonchild window of the system, checks the name of each window's class, looking for the name of the TForm1 class. When it finds a window with this string in its class name, it uses GetModuleFilename to extract the name of the executable file of the application that owns the matching form. If the module name matches that of the current program (which was extracted previously with similar code), you can be sure that you have found a previous instance of the same program. Here is how you can call the enumerated function:
var FoundWnd: THandle; ModuleName: string; begin if WaitForSingleObject (hMutex, 0) <> wait_TimeOut then ... else begin // get the current module name SetLength (ModuleName, 200); GetModuleFileName (HInstance, PChar (ModuleName), Length (ModuleName)); ModuleName := PChar (ModuleName); // adjust length // find window of previous instance EnumWindows (@EnumWndProc, 0);
I've mentioned that the SetForegroundWindow call doesn't work if the main form of the program has been minimized. Now you can solve this problem. You can ask the form of another application—the previous instance of the same program, in this case—to restore its main form by sending it a user-defined window message. You can then test whether the form is minimized and post a new user-defined message to the old window. Here is the code; in the OneCopy program, it follows the last fragment shown in the preceding section:
if FoundWnd <> 0 then begin // show the window, eventually if not IsWindowVisible (FoundWnd) then PostMessage (FoundWnd, wm_App, 0, 0); SetForegroundWindow (FoundWnd); end;
The PostMessage API function sends a message to the message queue of the application that owns the destination window. In the form's code, you can add a special function to handle this message:
public procedure WMApp (var msg: TMessage); message wm_App; procedure TForm1.WMApp (var msg: TMessage); begin Application.Restore; end;
|Copyright © 2004-2023 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide||