Lazarus

Programming => Operating Systems => Windows => Topic started by: rick2691 on December 16, 2017, 06:29:35 pm

Title: Make a Single-Instance Application
Post by: rick2691 on December 16, 2017, 06:29:35 pm
This is a windows dependent method for making a single-instance application.
Its main virtue is that it protects a database or document file from dual use.
But it also protects any configuration files from being changed during use.

My application is named CmdData. It manipulates a custom database.
I place "CmdData... " into the application's MainForm.Caption. I also append
that string with the database file name. These procedures search for the
"CmdData... " string within the caption.

It won't always work if there is another program active with the same
string in their caption. It catches the first app in the windows catalog of
active files. If yours was after it, it would take the first. So you want to be
careful, and try to have a unique search string. Outside of the "CmdData"
name, the "... " is what makes mine reasonably unique.

Prior to this method I had built a file-dependent method that would work
with any operating system. Unfortunately it would mess up if there was a
crash or you ended the program abruptly. The file would still be there, and
you would have to manually delete the file in order to launch your software.

I think the following is a safer method than using a temp-file.

Code: Pascal  [Select]
  1. // add these at the top of your project as global variables:
  2.  
  3.   var
  4.   AppInstanceLaunch: boolean;
  5.   AppInstanceFound: boolean;  
  6.   AppInstanceName: string;
  7.   AppInstanceHandle: hWnd;
  8.  
  9.  
  10. // add this function at the top of your program -- no need to register it
  11. // I found it on the internet and it did not have an author disclosed
  12. // I also modified it so it would suite my needs
  13. // it searches for a string within the caption of a first instance app:
  14.  
  15. function TMainFrm.FindWindowExtd(partialTitle: string): HWND;  // get with wildcard
  16. var                                   // by Dorin Duminica, September 10, 2009
  17.   hWndTemp: hWnd;
  18.   iLenText: Integer;
  19.   cTitletemp: array [0..254] of Char;
  20.   sTitleTemp: string;
  21. begin
  22.   hWndTemp:= FindWindow(nil, nil);
  23.   if AppInstanceLaunch then
  24.      begin
  25.      AppInstanceFound:= false;
  26.      AppInstanceName:= '';
  27.      AppInstanceHandle:= 0;
  28.      end;
  29.   while hWndTemp <> 0 do
  30.         begin
  31.         iLenText:= GetWindowText(hWndTemp, cTitletemp, 255);
  32.         //sTitleTemp:= cTitletemp;  // I omitted this step and modified the next line
  33.         sTitleTemp:= UpperCase(copy(cTitleTemp, 1, iLenText));  // it was copy(sTitleTemp,
  34.         partialTitle:= UpperCase(partialTitle);
  35.         if pos(partialTitle, sTitleTemp) > 0 then  // was <>  // this section is what I added
  36.            begin
  37.            if (not AppInstanceFound) and (AppInstanceLaunch) then  // these two are safeguards
  38.               begin
  39.               windows.beep(200,600);  // tone,duration -- first of two tone alarm
  40.               AppInstanceFound:= true;  // _vvv_globals_vvv_  // these are globals if needed for checking
  41.               AppInstanceName:= sTitleTemp;                   // AppInstanceFound is the only critical variable
  42.               AppInstanceHandle:= hWndTemp;
  43.               end;
  44.            Break;
  45.            end;
  46.         hWndTemp:= GetWindow(hWndTemp, GW_HWNDNEXT);
  47.         end;
  48.   result:= hWndTemp;
  49. end;
  50.  
  51.  
  52.   // this makes a single-instance app
  53.   // add this code at the top of your FormCreate procedure
  54.   // it is what closes down your app if it is a second instance:
  55.  
  56.   MainFrm.Caption:= 'CmdData Extant-Search';  // rename caption so SwitchApp will skip this app
  57.   AppInstanceLaunch:= true;  // this variable isn't necessary -- only a safeguard
  58.   AppInstanceFound:= false;  // this variable is critical and its setting is also a safeguard
  59.   if AppInstanceLaunch then
  60.      begin
  61.      SwitchApp('CmdData... ');  // 'CmdData... ' is my search string -- my app appends this with my database name
  62.      AppInstanceLaunch:= false; // the search string doesn't have to be a prefix -- it can be anywhere within a caption
  63.      end;
  64.   if AppInstanceFound then
  65.      begin
  66.      windows.beep(500,600);  // tone,duration -- second of two tone alarm
  67.      halt; // alternate methods: FormClose(self); application.terminate; Halt;
  68.      end else
  69.          begin
  70.          MainFrm.Caption:= 'CmdData...   no project assigned';
  71.          end;
  72.  
  73.  
  74. // the two tone alarm tells me that each function is working,
  75. // and it informs the user that my app is already launched and active
  76. // after the tones it switches focus to the first instance app.
  77. // the reason for a tone alert is that your app is not visible during OnCreate.
  78.  
  79. // all of this works best in Win10 if your app is not minimized
  80. // Win10 resists raising a minimized app, but will allow you to switch focus to an active one
  81. // WinXP will allow you to raise a minimized app, but you need to add that code
  82.  
  83. // the FindWindowExtd function can additionally be used for switching to other active apps, such as NotePad and others.
  84. // the safeguards permit you to do so without interfering with the single instance check, and vice-versa
  85.  
  86.  
Title: Re: Make a Single-Instance Application
Post by: GetMem on December 16, 2017, 06:44:55 pm
Did you try component Unique instance? http://wiki.freepascal.org/UniqueInstance
Title: Re: Make a Single-Instance Application
Post by: Thaddy on December 16, 2017, 08:23:39 pm
Yup, someone (OP) is inventing the wheel again. Happens all the time and it ends up square or (optimistic) hexagonal....

https://en.wikipedia.org/wiki/Reinventing_the_wheel
and:
https://en.wikipedia.org/wiki/Not_invented_here

Merry Christmas.  :-* :D 8-) O:-) >:D
Title: Re: Make a Single-Instance Application
Post by: RAW on December 16, 2017, 08:53:20 pm
There are a lot of WINDOWS dependent examples out there...
MUTEX, SEMAPHORE, FILE MAPPING ... (but take care with the FileMapping versions...  :)).

BTW:
Quote
begin
     windows.beep(500,600);  // tone,duration -- second of two tone alarm
     halt; // alternate methods: FormClose(self); application.terminate; Halt;
     end else......
What's wrong with "EXIT" or "END." (LPR) instead of "HALT"...
Title: Re: Make a Single-Instance Application
Post by: Cyrax on December 16, 2017, 11:43:36 pm
Please do not use halt. It will force application to terminate without executing normal resource cleaning up procedures.
Title: Re: Make a Single-Instance Application
Post by: rick2691 on December 17, 2017, 01:36:19 pm
Quote
Please do not use halt. It will force application to terminate without executing normal resource cleaning up procedures.

I have used halt because my application does not (to my knowledge) need any clean by the OnCreate procedure. I only initiate variables to default values, and that includes reading a disk file for custom default values ... nothing is saved. That only happens with the OnClose event.

As to reinventing the wheel... it permits customization, and I know what is happening. Reinventing is a process for expanding knowledge and finding flexibility. In my case, I want to discover a simple and effective solution that provides control for additional programmers of like-mind.

Thank you all for your generous support.

Rick
Title: Re: Make a Single-Instance Application
Post by: MacWomble on December 17, 2017, 02:36:30 pm
Please do not use halt. It will force application to terminate without executing normal resource cleaning up procedures.

In my opinion the destroy-parts will be executed after halt, so recource cleaning will be done - except you use an old version of Lazarus. Am I right?
Title: Re: Make a Single-Instance Application
Post by: Cyrax on December 17, 2017, 03:00:12 pm
Please do not use halt. It will force application to terminate without executing normal resource cleaning up procedures.

In my opinion the destroy-parts will be executed after halt, so recource cleaning will be done - except you use an old version of Lazarus. Am I right?

Oh, it seems that you are right.  :-[
Title: Re: Make a Single-Instance Application
Post by: rick2691 on December 20, 2017, 01:11:43 pm
I have edited the comments in the code sample as previously posted. The primary comment is that the OnCreate section can be inserted at the top of your OnCreate procedure. I had previously placed mine at the bottom during some debugging exercises. Then I had left it there, but have now found that my improvements do not necessitate its being at the bottom. It can actually be anywhere. I like the top better, but it depends on what you are actually doing with the OnCreate event.

Rick
Title: Re: Make a Single-Instance Application
Post by: ASerge on December 21, 2017, 12:53:45 am
An example solution (only for Windows) that uses system event to make sure the application is unique and wake up the first instance. And also uses page file to interact between different copies of the program.
Unlike UniqueInstance, correctly activates the first instance.
Title: Re: Make a Single-Instance Application
Post by: rick2691 on January 22, 2018, 01:45:40 pm
@ASerge... Thank you for posting your Single-Instance method. But I was not able to implement it. So I am still using my own method.

@All... I have found that the FindWindowExtd function in my initial post doesn't really find a string. It finds a word, string of words, or a string in a word.

So doing SwitchApp('CmdData...') only finds 'CmdData' because it treats the first '.' of '...' as a word terminator. It happened to work with my first post because there wasn't a third app with that string that was active.

I discovered this when making a second project single-instance. The project CmdBlue had 'CmdBlue' in the Lazarus caption. So my project would not run or launch.

I fixed this by changing my caption search. I did SwitchApp('CmdBlue ... '). The spaces before and after '...' made it treat the '...' as another word, and not a punctual terminator. So it searches for two whole words that are in series.

If anyone is interested, I have also made the second instance pass its attached file parameter to the first instance, and the first instance loads the file (such as when you double-click an '.RTF' file and you are the default app for loading it).

Rick
Title: Re: Make a Single-Instance Application
Post by: vhanla on March 10, 2018, 04:12:30 am
I have tried @ASerge 's example, and it works, but params cannot be passed from the second instance to the first one.
Title: Re: Make a Single-Instance Application
Post by: taazz on March 10, 2018, 06:55:49 am
I have tried @ASerge 's example, and it works, but params cannot be passed from the second instance to the first one.
the proper (old?) way of handling startup params was to implement a DDE server on your application that handles it and the OS will pass them to you. The poor mans way is to have a tcpserver in your application and before you exit you send the parameters to it. The windows way is to use either a user message and wait until it has been processed or mailslots for IPC some eve use memory mapped files (a complete list of IPC mechanisms (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365574%28v=vs.85%29.aspx))
Title: Re: Make a Single-Instance Application
Post by: ASerge on March 10, 2018, 05:06:42 pm
I have tried @ASerge 's example, and it works, but params cannot be passed from the second instance to the first one.
Sorry, I forgot again that CreateFileMapping and OpenFileMapping use different access constants.
Replace in uPageFileStream.pp line 75
Code: Pascal  [Select]
  1.   FMapHandle := OpenFileMappingW(PAGE_READONLY, False, Pointer(MapName));
with
Code: Pascal  [Select]
  1.   FMapHandle := OpenFileMappingW(FILE_MAP_READ, False, Pointer(MapName));
Archive with correction and test project included.
Title: Re: Make a Single-Instance Application
Post by: rick2691 on March 10, 2018, 07:02:54 pm
The way that I handle passing a file parameter is that I have the secondary application create a text file and write the parameter string to the file. I call the file CmdParameter.000, and it is stored in the application folder.

When the focus is received by the primary instance (by ApplicationActivate) it checks to see if that file exists, and if it does then it processes the file string (and deletes the file).

As an added protection I have FormCreate delete the file if it exists, since it is a first-instance procedure. The secondary-instance will do the same (because it is the same program), but then it creates the parameter file. This way a problem where a crash or power-out event might leave the file on the disk is avoided.

Rick
Title: Re: Make a Single-Instance Application
Post by: vhanla on March 10, 2018, 11:15:31 pm
Thank you @ASerge, now it works great (at least on Windows). Does that code has any license? I will be using it on a MPL 2.0 licensed project, is it compatible and/or allowed?

Title: Re: Make a Single-Instance Application
Post by: han on May 05, 2019, 03:42:54 pm
This is an old topic.

Nevertheless I tried the UniqueInstance. It works well.  The component is however not standard available in Lazarus. The same setup is easy to implement with the simpleIPCserver. This allows customization. Attached my simple implementation including the sending of paramstr(1) from the second instance to the first instance prior to termination.

I don't understand why in UniqueInstance the OnMessage event in combination with a timer is used. With an onMessageQueued event it is possible to implement it without a timer. I will post an issue at Github, https://github.com/blikblum/luipack/tree/master/uniqueinstance (https://github.com/blikblum/luipack/tree/master/uniqueinstance).


Later:
  Sending parameters only worked in Windows. For Linux there is  no  onMessageQueued event. If have do a SimpleIPCServer1.PeekMessage(1,True) driven by timer.  :(
  Attached an version which works both for Linux and Windows. The Linux part is timer driven.

Han