Recent

Author Topic: How to use TCustomApplication.SingleInstanceEnabled?  (Read 2426 times)

Sören

  • Newbie
  • Posts: 5
How to use TCustomApplication.SingleInstanceEnabled?
« on: June 03, 2020, 08:52:28 pm »
Hi there,
I want to allow only one instance of my application.
I tried adding
Code: Pascal  [Select][+][-]
  1. Application.SingleInstanceEnabled:=true;
in the project's .lpr file, but I also have to set a SingleInstanceClass to Application.SingleInstance and that's where I got stuck.

The only source of information that I found was the docs : https://lazarus-ccr.sourceforge.io/docs/fcl/custapp/tcustomapplication.singleinstanceenabled.html
Any help would be appreciated.


jamie

  • Hero Member
  • *****
  • Posts: 6834
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #1 on: June 03, 2020, 10:57:03 pm »
I could be wrong but I believe you do not need to set the class, it already has a default one in there..

 Try first reading the SingleInstance property in the main file and if not set then you can define SingleInstanceEnabled := True;

 If it is already defined then you Skip the remainder of the Application code and just exit.

 That is going by what I have seen in the help file..

 Its not totally automatic because it will allow you to do things differently with multiple instances instead of just terminating automatically
The only true wisdom is knowing you know nothing

Sören

  • Newbie
  • Posts: 5
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #2 on: June 03, 2020, 11:55:38 pm »
Makes sense, but if I try to check Application.SingleInstance I get the error:

Quote
No single instance provider class set! Include a single-instance class unit such as advsingleinstance

and I didn't find any advsingleinstance unit. 

In the fcl-base/custapp.pp file the error is raised in the private function GetSingleInstance when the single instance class is nil.
Unfortunately I only use Lazarus occasionally, so I could be missing something really obvious.
« Last Edit: June 04, 2020, 12:01:59 am by Soeren »

tetrastes

  • Hero Member
  • *****
  • Posts: 636
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #3 on: June 04, 2020, 12:58:21 am »
Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   {$IFDEF UNIX}{$IFDEF UseCThreads}
  7.   cthreads,
  8.   {$ENDIF}{$ENDIF}
  9.   AdvancedSingleInstance, // !!!
  10.   Dialogs,
  11.   Interfaces,  // this includes the LCL widgetset
  12.   Forms, Unit1;
  13.  
  14. {$R *.res}
  15.  
  16. begin
  17.     Application.Initialize;
  18.  
  19.     Application.SingleInstanceEnabled := true;
  20.     Application.SingleInstance.Start;
  21.     if Application.SingleInstance.IsServer then
  22.     begin
  23.         Application.CreateForm(TForm1, Form1);
  24.         Application.Run;
  25.     end
  26.     else
  27.         ShowMessage('Only one instance allowed!');
  28. end.
  29.  

eljo

  • Sr. Member
  • ****
  • Posts: 468
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #4 on: June 04, 2020, 01:00:36 am »
Makes sense, but if I try to check Application.SingleInstance I get the error:

Quote
No single instance provider class set! Include a single-instance class unit such as advsingleinstance

and I didn't find any advsingleinstance unit. 

In the fcl-base/custapp.pp file the error is raised in the private function GetSingleInstance when the single instance class is nil.
Unfortunately I only use Lazarus occasionally, so I could be missing something really obvious.
you can find the advanced single instance unit in
<Lazarus Install Folder>\fpc\3.0.4\source\packages\fcl-base\src\advancedsingleinstance.pas

I guess in your application you need to add the fcl-base package in your required packages.

jamie

  • Hero Member
  • *****
  • Posts: 6834
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #5 on: June 04, 2020, 01:09:30 am »
You can use FindWindow('Window', 'Your Unique Window Caption');

If that returns the Handle of the form then to increase the check point you could then
SendMessage(TheHandleIGot, WM_USER+100, 1,0);

and your form should be processing this message and thus return with a apply.

This of course only works in windows and for the life of me I can't figure out why they didn't create the window using the TFORM name class instead of window because this weakens the attempt to find the exact one..

The only true wisdom is knowing you know nothing

Sören

  • Newbie
  • Posts: 5
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #6 on: June 04, 2020, 09:16:47 am »
Many thanks @tetrastes for the sample code - problem solved!
In my case I only had to add the AdvancedSingleInstance unit, but I wasn't aware of .Start and .IsServer either, so your example made everything clear.

Thank you all for your help!
« Last Edit: June 04, 2020, 09:41:41 am by Sören »

Jonny

  • Full Member
  • ***
  • Posts: 144
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #7 on: February 17, 2025, 02:14:30 pm »
Sorry for reviving an old topic, thought it best to keep the conversation together as it is relevant.

The snippet posted by tetrastes works well, but how can I activate or bring the original existing "server" instance to the foreground? Is there a mechanism to communicate between client/server instance and send it a command? There is very little documentation available.

Zvoni

  • Hero Member
  • *****
  • Posts: 2914
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #8 on: February 17, 2025, 02:38:42 pm »
Look at the source code
\fpc\3.2.2\source\packages\fcl-base\src\advancedsingleinstance.pas

You'll find "interesting" Methods
Code: Pascal  [Select][+][-]
  1.  public
  2.     function Start: TSingleInstanceStart; override;
  3.     procedure Stop; override;
  4.     procedure ServerCheckMessages; override;
  5.     procedure ClientPostParams; override;
  6.   public
  7.     function ClientPostCustomRequest(const aMsgType: Integer; const aStream: TStream): Integer;
  8.     function ClientSendCustomRequest(const aMsgType: Integer; const aStream: TStream): Boolean; overload;
  9.     function ClientSendCustomRequest(const aMsgType: Integer; const aStream: TStream; out outRequestID: Integer): Boolean; overload;
  10.     procedure ServerPostCustomResponse(const aRequestID: Integer; const aMsgType: Integer; const aStream: TStream);
  11. //........
  12.     property OnServerReceivedCustomRequest: TSingleInstanceReceivedCustomMessage read FOnServerReceivedCustomRequest write FOnServerReceivedCustomRequest;
  13.  
« Last Edit: February 17, 2025, 02:44:53 pm by Zvoni »
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

Jonny

  • Full Member
  • ***
  • Posts: 144
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #9 on: February 17, 2025, 03:02:31 pm »
Thank you @Zvoni.

I have been trying Application.SingleInstance.ClientPostParam() and Application.SingleInstanceClass.ClientPostParam() but they give and error of Identifier idents no member "ClientPostParam"

Which is strange since Application.SingleInstance.Start and Application.SingleInstance.Stop both work.

Could it be because I am running FPC 3.3.1 (trunk)?

Is there an example of the usage?

Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   {$IFDEF UNIX}{$IFDEF UseCThreads}
  7.   cthreads,
  8.   {$ENDIF}{$ENDIF}
  9.   AdvancedSingleInstance, // !!!
  10.   Dialogs,
  11.   Interfaces,  // this includes the LCL widgetset
  12.   Forms, Unit1;
  13.      
  14. begin
  15.   Application.Initialize;
  16.   Application.SingleInstanceEnabled := true;
  17.   Application.SingleInstance.Start;
  18.   if Application.SingleInstance.IsServer then
  19.   begin
  20.     Application.CreateForm(TForm1, Form1);
  21.     Application.Run;
  22.   end
  23.   else
  24.     ShowMessage('Only one instance allowed!');
  25.     Application.SingleInstance.ClientPostParam(); // Error: Identifier idents no member "ClientPostParam"
  26.   end.
  27.  

Lazarus 4.99 (rev main_4_99-920-g0d73603378) FPC 3.3.1 x86_64-linux-gtk2

Zvoni

  • Hero Member
  • *****
  • Posts: 2914
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #10 on: February 17, 2025, 03:26:58 pm »
Untested
Code: Pascal  [Select][+][-]
  1. if Application.SingleInstance.IsServer then
  2.   begin
  3.     Application.CreateForm(TForm1, Form1);
  4.     Application.Run;
  5.   end
  6.   else
  7.     ShowMessage('Only one instance allowed!');
  8.     TAdvancedSingleInstance(Application.SingleInstance).ClientPostCustomRequest(-5,nil);  //Instead of Nil a real TStringStream containing "Get to the Front"
  9. //The -5 is to get into the "Else"-Part of the Case Of in ServerCheckMessages
  10.   end.
« Last Edit: February 17, 2025, 03:30:32 pm by Zvoni »
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

Thaddy

  • Hero Member
  • *****
  • Posts: 16653
  • Kallstadt seems a good place to evict Trump to.
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #11 on: February 17, 2025, 03:54:38 pm »
What is not clear in the official documentation?
Which you did not bother to consult first, since you did not "find" it, which seems clueless to me:
https://www.freepascal.org/docs-html/fcl/custapp/tcustomapplication.singleinstance.html

RTFM!
But I am sure they don't want the Trumps back...

Jonny

  • Full Member
  • ***
  • Posts: 144
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #12 on: February 17, 2025, 06:56:17 pm »
Quote from: Zvoni
Untested
Code: Pascal  [Select][+][-]
  1.     TAdvancedSingleInstance(Application.SingleInstance).ClientPostCustomRequest(-5,nil);  //Instead of Nil a real TStringStream containing "Get to the Front"
  2. //The -5 is to get into the "Else"-Part of the Case Of in ServerCheckMessages
  3.  

Thank you again @Zvoni for the example and for specifying that it is untested. I still need a little more guidance after my testing please.

I found the TAdvancedSingleInstance.ClientPostCustomRequest procedure:

Code: Pascal  [Select][+][-]
  1. function TAdvancedSingleInstance.ClientPostCustomRequest(
  2.   const aMsgType: Integer; const aStream: TStream): Integer;
  3. begin
  4.   if not Assigned(FClient) then
  5.     raise ESingleInstance.Create(SErrSingleInstanceNotClient);
  6.  
  7.   Result := FClient.PostRequest(aMsgType, aStream);
  8. end;
  9.  

Which leads to FClient.PostRequest:

Code: Pascal  [Select][+][-]
  1. function TIPCClient.PostRequest(const aMsgType: TMessageType;
  2.   const aStream: TStream): Integer;
  3. var
  4.   xRequestFileStream: TFileStream;
  5. begin
  6.   xRequestFileStream := nil;
  7.   try
  8.     Result := CreateUniqueRequest(xRequestFileStream);
  9.     DoPostMessage(xRequestFileStream, aMsgType, aStream);
  10.   finally
  11.     xRequestFileStream.Free;
  12.   end;
  13.   FLastRequestID := Result;
  14. end;
  15.  

If I understand correctly, it seems that I can freely create a message for ServerCheckMessages after checking the values of the xMsgType messages (I see why you said -5 because the others are -1, -3 and -5):

Code: Pascal  [Select][+][-]
  1. procedure TAdvancedSingleInstance.ServerCheckMessages;
  2. var
  3.   xMsgID: Integer;
  4.   xMsgType: Integer;
  5.   xStream: TStream;
  6.   xStringStream: TStringStream;
  7. begin
  8.   if not Assigned(FServer) then
  9.     raise ESingleInstance.Create(SErrSingleInstanceNotServer);
  10.  
  11.   if not FServer.PeekRequest(xMsgID, xMsgType) then
  12.     Exit;
  13.  
  14.   case xMsgType of
  15.     MSGTYPE_CHECK:
  16.     begin
  17.       FServer.DeleteRequest(xMsgID);
  18.       FServer.PostResponse(xMsgID, MSGTYPE_CHECKRESPONSE, nil);
  19.     end;
  20.     MSGTYPE_PARAMS:
  21.     begin
  22.       xStringStream := TStringStream.Create('');
  23.       try
  24.         FServer.ReadRequest(xMsgID, xStringStream);
  25.         DoServerReceivedParams(xStringStream.DataString);
  26.       finally
  27.         xStringStream.Free;
  28.       end;
  29.     end;
  30.     MSGTYPE_WAITFORINSTANCES:
  31.       FServer.DeleteRequest(xMsgID);
  32.   else
  33.     xStream := TMemoryStream.Create;
  34.     try
  35.       FServer.ReadRequest(xMsgID, xStream);
  36.       DoServerReceivedCustomRequest(xMsgID, xMsgType, xStream);
  37.     finally
  38.       xStream.Free;
  39.     end;
  40.   end;
  41.  

If I understand correctly, I need to create a TStream containing plain text which I then act upon in ServerCheckMessages. What I fail to implement s how to assign a TSingleInstanceReceivedCustomMessage handler to OnServerReceivedCustomRequest. This is what I have:

Code: Pascal  [Select][+][-]
  1. procedure SingleInstanceServerReceivedCustomRequest(Sender: TBaseSingleInstance; MsgID: Integer; MsgType: Integer; MsgData: TStream);
  2. begin
  3. end;
  4.  
  5. TAdvancedSingleInstance(Application.SingleInstance).OnServerReceivedCustomRequest := SingleInstanceServerReceivedCustomRequest(Application.SingleInstance, MsgID?? MsgType??);
  6.  

Where are the Msg items available?



Jonny

  • Full Member
  • ***
  • Posts: 144
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #13 on: February 17, 2025, 06:59:05 pm »
Quote from: Thaddy
What is not clear in the official documentation?
Which you did not bother to consult first, since you did not "find" it, which seems clueless to me:
https://www.freepascal.org/docs-html/fcl/custapp/tcustomapplication.singleinstance.html

RTFM!

Hello @Thaddy. Nice to see you as direct as ever. Please allow me to answer:

> What is not clear in the official documentation?

There is no documentation. The page that you linked to is rather bare. There are no details or examples. It seems that to understand the behaviour is to study the source code. Which I did. And to search the forum. Which I also did. And to test and experiment and try things. Which I did too. Then to reach out here for help. Which is what I am doing.

Understanding the source code is a huge undertaking. Not a simple task for casual developers. It requires knowledge of inner workings and intricacies of fpc/lazarus which are incomprehensible to most. Although we do try.

> RTFM!

Reading the manual - when one can find a manual - and understanding the manual are two different things. A forum is a place to ask for help, to seek guidance and hopefully obtain examples. I do understand your frustration when our questions seem trivial to answer, but triviality comes from wisdom and knowledge. Are you able to assist in this instance? Even your blunt comments may shed a little light for me. Thank you.



Zvoni

  • Hero Member
  • *****
  • Posts: 2914
Re: How to use TCustomApplication.SingleInstanceEnabled?
« Reply #14 on: February 18, 2025, 08:19:57 am »
As i said: Never worked with it, just deriving what i read from the Source-Code

You need a prodecure (of Object), say, in your Form, matching the Signature
Code: Pascal  [Select][+][-]
  1. type
  2.   TSingleInstanceReceivedCustomMessage = procedure(Sender: TBaseSingleInstance; MsgID: Integer; MsgType: Integer; MsgData: TStream) of object;
In your OnCreate of the Form, you assign the Address of
TAdvancedSingleInstance(Application.SingleInstance).OnServerReceivedCustomRequest
to that event.
You don't have to catch anything in ServerCheckMessages.
The "Else"-Part of the Case Of triggers the Event.
And in that Event you have to decode the Message
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

 

TinyPortal © 2005-2018