Recent

Author Topic: Make a Single-Instance Application  (Read 6922 times)

rick2691

  • Sr. Member
  • ****
  • Posts: 375
Make a Single-Instance Application
« 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.  
« Last Edit: December 20, 2017, 01:05:52 pm by rick2691 »
Windows 10, LAZ 1.6.4, FPC 3.0.2, SVN 54278, i386-win32-win32/win64, forms use windows unit

GetMem

  • Hero Member
  • *****
  • Posts: 3501
Re: Make a Single-Instance Application
« Reply #1 on: December 16, 2017, 06:44:55 pm »
Did you try component Unique instance? http://wiki.freepascal.org/UniqueInstance

Thaddy

  • Hero Member
  • *****
  • Posts: 8912
Re: Make a Single-Instance Application
« Reply #2 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
« Last Edit: December 16, 2017, 08:51:34 pm by Thaddy »
Most people that want to use threading should learn to patch their jeans first: use a needle.

RAW

  • Hero Member
  • *****
  • Posts: 794
Re: Make a Single-Instance Application
« Reply #3 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"...
« Last Edit: December 16, 2017, 09:01:57 pm by RAW »
Windows 7 Pro (x64 Sp1) And Windows XP Pro (x86 Sp3) - LAZARUS 2.0.4 FPC 3.0.4 - TRUNK 2.1.0 FPC 3.3.1

Cyrax

  • Hero Member
  • *****
  • Posts: 755
Re: Make a Single-Instance Application
« Reply #4 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.

rick2691

  • Sr. Member
  • ****
  • Posts: 375
Re: Make a Single-Instance Application
« Reply #5 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
Windows 10, LAZ 1.6.4, FPC 3.0.2, SVN 54278, i386-win32-win32/win64, forms use windows unit

MacWomble

  • New Member
  • *
  • Posts: 37
Re: Make a Single-Instance Application
« Reply #6 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?
Mint 18.3 Cinnamon, Codetyphon 6.4 64Bit

Cyrax

  • Hero Member
  • *****
  • Posts: 755
Re: Make a Single-Instance Application
« Reply #7 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.  :-[

rick2691

  • Sr. Member
  • ****
  • Posts: 375
Re: Make a Single-Instance Application
« Reply #8 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
Windows 10, LAZ 1.6.4, FPC 3.0.2, SVN 54278, i386-win32-win32/win64, forms use windows unit

ASerge

  • Hero Member
  • *****
  • Posts: 1406
Re: Make a Single-Instance Application
« Reply #9 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.

rick2691

  • Sr. Member
  • ****
  • Posts: 375
Re: Make a Single-Instance Application
« Reply #10 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
Windows 10, LAZ 1.6.4, FPC 3.0.2, SVN 54278, i386-win32-win32/win64, forms use windows unit

vhanla

  • New Member
  • *
  • Posts: 13
Re: Make a Single-Instance Application
« Reply #11 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.

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: Make a Single-Instance Application
« Reply #12 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)
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

ASerge

  • Hero Member
  • *****
  • Posts: 1406
Re: Make a Single-Instance Application
« Reply #13 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.

rick2691

  • Sr. Member
  • ****
  • Posts: 375
Re: Make a Single-Instance Application
« Reply #14 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
Windows 10, LAZ 1.6.4, FPC 3.0.2, SVN 54278, i386-win32-win32/win64, forms use windows unit