SimpleIPC

From Free Pascal wiki
Jump to navigationJump to search

Simple IPC is a Free Pascal unit (and Lazarus components) that allows interprocess communication (IPC) between Free Pascal programs.

It allows two executables to communicate.

Advantages of SimpleIPC

You can create communication systems without requiring low level sockets programming of your own. SimpleIPC does the grunt work of IPC for you and makes a nice high level wrapper around lower level IPC mechanisms, so you don't have to program them yourself.

SimpleIPC can be used to communicate between programs to make simple communication systems, plugin systems and much more,

Hint : Multiuser Systems

On multiuser systems, eg, Unix systems, in Global Mode, note that the pipe that IPC creates must have a distinctive name between individual users. Its usual to set the ServerID to the application name so, add the user name to that -

CommsServer  := TSimpleIPCServer.Create(Nil);
CommsServer.ServerID:='MyAppName' {$ifdef UNIX} + '-' + GetEnvironmentVariable('USER'){$endif}; 
CommsServer.OnMessageQueued:=@CommMessageReceived;
CommsServer.Global:=True;                  
CommsServer.StartServer({$ifdef WINDOWS}False{$else}True{$endif});  // start listening, threaded

Without the user name being added, you risk all instances of the App trying to use the same named pipe and, obviously permissions do not allow that.

Use Cases

The Lazarus help system itself, uses SimpleIPC.

Some fpGUI tools and demos use IPC, for example, to detect if the program is already running (single instance)

Example: Single Instance Application

Assume your application is a text editor and the user can open files using Open With from a file manager.

If the application is already running, instead of launching a second instance, the new instance should send the file name to the running instance and then exit. The running instance receives the file name and opens it.

Below is a minimal example demonstrating this approach.

1. A helper class returning the unique SimpleIPC server name

On Unix systems it is recommended to append the current user name to avoid conflicts on multi-user systems.

unit o_App;

{$mode DELPHI}{$H+}

interface

uses
  Classes
  , SysUtils
  ;

type
  App = class
  public
    class function GetSimpleIpcServerName(): string;
  end;

implementation

class function App.GetSimpleIpcServerName(): string;
begin
  Result := 'SlatePad_Main_Instance';

  {$ifdef UNIX}
  Result := Result + '-' + GetEnvironmentVariable('USER');
  {$endif}
end;

end.

2. In the program (.lpr) file

The program first checks whether another instance is already running. If another instance exists, the file name is sent to that instance and the new instance exits.

program SlatePad;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}
  cthreads,
  {$ENDIF}
  {$IFDEF HASAMIGA}
  athreads,
  {$ENDIF}
  Interfaces,
  SimpleIPC,
  Forms, SysUtils, f_MainForm, o_App;

{$R *.res}

var
  Client: TSimpleIPCClient;
  FilePath: string;
begin
  if ParamCount > 0 then
    FilePath := ExpandFileName(ParamStr(1))
  else
    FilePath := '';

  Client := TSimpleIPCClient.Create(nil);
  try
    Client.ServerID := App.GetSimpleIpcServerName();

    if Client.ServerRunning then
    begin
      if (FilePath <> '') and FileExists(FilePath) then
      begin
        Client.Connect;
        try
          Client.SendStringMessage(FilePath);
        finally
          Client.Disconnect;
        end;
      end;

      Halt;
      Exit;
    end;
  finally
    Client.Free;
  end;

  RequireDerivedFormResource := True;
  Application.Scaled := True;
  {$PUSH}{$WARN 5044 OFF}
  Application.MainFormOnTaskbar := True;
  {$POP}
  Application.Initialize;
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end.

3. In the main form

The main instance creates a TSimpleIPCServer and periodically checks for incoming messages. When a message arrives, the file name is read and opened.

uses
  SimpleIPC;

private
  IpcServerTimer: TTimer;
  IpcServer: TSimpleIPCServer;

constructor TMainForm.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  IpcServer := TSimpleIPCServer.Create(Self);
  IpcServer.ServerID := App.GetSimpleIpcServerName();
  IpcServer.MaxQueue := 1;
  IpcServer.Global := True;
  IpcServer.SynchronizeEvents := True;
  IpcServer.Threaded := False;
  IpcServer.OnMessage := IpcServer_OnMessage;

  IpcServerTimer := TTimer.Create(Self);
  IpcServerTimer.Interval := 250;
  IpcServerTimer.OnTimer := IpcServerTimer_OnTimer;
  IpcServerTimer.Enabled := True;

  IpcServer.StartServer;
end;

procedure TMainForm.IpcServerTimer_OnTimer(Sender: TObject);
begin
  while IpcServer.PeekMessage(0, True) do
    IpcServer.ReadMessage;
end;

procedure TMainForm.IpcServer_OnMessage(Sender: TObject);
var
  FilePath: string;
begin
  FilePath := Trim(IpcServer.StringMessage);
  if (FilePath <> '') and FileExists(FilePath) then
    OpenDoc(FilePath);
end;

Send Messages to Other Programming Languages

For IPC between Free Pascal programs and other programs written in any language (C++, Delphi, GoLang, etc.) see SimpleIPC Library which allows you to use SimpleIPC not just in FPC and Lazarus, but in multiple programming languages.

See also