A common need for programmers is to prevent running of the application more than once. For this purpose we mainly use mutex in Windows programming and close the application if an already active mutex is in memory. In Firemonkey applications this approach is not valid in OSX side as it is in Windows. So the first alternative way that comes to mind is to search for all running applications at start-up and close the application if it has got two (2) instances. So the question is how we will get the list of running applications? We will need this also for Windows as well as OSX, because doing the things using the same strategy for both Windows and OSX is a common pattern in Firemonkey.
As you can see in one of my earlier articles, I have started a new class named TPlatformExtensions, to host these kind of extra utilities which will expand platform specific usage. So I will add a new function to this class to solve this specific platform related problem.
Getting the Running Applications list in OSX
On OSX api, NSWorkspace class is used to deal with issues about the workspace that the application is hosted. And this class has got two selectors (methods) that is related with the subject.
- (NSArray *)launchedApplications - (NSArray *)runningApplications
Both functions can be used to get the list of running Applications, however the first one is a deprecated one. So the second one should be used to get rid of future problems about compatibility, bu this function is not fully supported in Delphi port. Because this functions returns an array of NSRunningApplication objects which originally we can get any information about the application instance. However, in Firemonkey port this class includes a very brief list of methods which we can only learn the processId of the application that wont mean anything for us to check the double instantiation. So I decided to use the deprecated function hoping that soon I will replace it with the second one after a possible hot-fix enriching the NSRunningApplication interface.
To be able to use workspace instance methods first we should have got an instance of our workspace. So we will call the
+ (NSWorkspace *)sharedWorkspace
class method to get the shared workspace. The launchedApplications methods returns an NSArray object which includes the list of NSDictionary objects that each of them represents a bundle of information of each running application. So we should typecast the items in the list to NSDictionary and get the values of the keys we know. Possible keys that we can use to get information about the application are NSApplicationBundleIdentifier, NSApplicationPath and NSApplicationName. Here how we do all in Delphi side.
class procedure TPlatformExtensionsMac.GetRunningAplications(Applist: TStringlist); var fWorkSpace:NSWorkSpace; list:NSArray; i: Integer; lItem:NSDictionary; key,value: NSString; begin fWorkSpace := TNsWorkspace.Wrap(TNsWorkSpace.OCClass.sharedWorkspace); list := fWorkspace.launchedApplications; if (List <> nil) and (List.count > 0) then begin for i := 0 to list.count-1 do begin lItem := TNSDictionary.Wrap(List.objectAtIndex(i)); key := NSSTR(String(PAnsiChar(UTF8Encode('NSApplicationBundleIdentifier')))); // You can also use NSApplicationPath or NSApplicationName value := TNSString.Wrap(lItem.valueForKey(key)); Applist.Add(String(value.UTF8String)); end; end; end;
Getting the Running Applications list in Windows (32bit and 64bit)
In windows though the standard api introduced in Windows.pas doesn’t provide a support for getting the list of running applications, Microsoft has got a support in kernel32.dll called tool help library and fortunately Delphi has got a port of this group of functions under the name Winapi.TlHelp32.pas. So we will use this library to get the list of running applications by searching the heap with given functions. Don’t be mistaken by the name. It is used both in 32 and 64 bit modes. Here you can see how it is also easy to get the running applications list in Windows.
class procedure TPlatformExtensionsWin.GetRunningAplications(Applist: TStringlist); var PE: TProcessEntry32; Snap: THandle; fName: String; begin pe.dwsize:=sizeof(PE); Snap:= CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0); if Snap <> 0 then begin if Process32First(Snap, PE) then begin fName := String(PE.szExeFile); Applist.Add(fName); while Process32Next(Snap, PE) do begin fName := String(PE.szExeFile); Applist.Add(fName); end; end; CloseHandle(Snap); end; end;
You can get the source code of the PlatformExtensions classes with the demo application from this SVN link. For non-programmers the compiled Win32, Win64, MacOSX (Thanks to Firemonkey) applications are also available to download.