0

A user logged into machine and run my application. without closing the application he just did switch user. The second user logged in and again run my application.

In this case, I want to kill the first instance from the second instance without any intimation to the first user.

When running my application I am writing the PID of the instance in a text file (in common path for the users). Then, I've have placed a timer to keep track of changes in PID from text file. If it differs from the instance's PID and then the instance will kill by itself.

To improve the performance of code, I wanna remove the timer logic.

Note: Both the users are normal users, don't have admin rights

I tried the followings, nothing worked out.

Approach 1: Kill by Process ID

Tried to kill the first instance by using PID from the second user's instance. Able to get the first process(ProcEntry) and terminateprocess is not killing the first instance, even it doesn't throw any error.

Tried to set the privilege(SE_DEBUG_NAME) and it is failed due to the user is not an admin.

Approach 2: Kill by Mutex I didn't get enough information on this to kill the process.

Approach 3:Kill by Passing WIN API message

It is not possible to pass messages between applications of different users by using PostMessage method.

Approach 4: Using named pipes

I'm not familiar with this approach.

Any code snippets/suggestions for the above would be greatly appreciated.

Community
  • 1
  • 1
  • If they're the same application, why not make that other instance kill its self? Much easier. – Jerry Dodge Jun 26 '14 at 22:18
  • 1
    I'd make some sort of session manager service for this purpose. However, it's unfair to kill an app. to the other user, isn't it ? Aren't you just "fixing" some problem that you couldn't properly resolve ? – TLama Jun 26 '14 at 22:30
  • Currently, my application is self killing if another instance found as you suggested. I have used timer and to that my application is very big. To avoid performance issue, need to improve the code. @JerryDodge – user3880804 Jun 26 '14 at 22:55
  • To avoid performance issue, you can do it inside of a thread instead of a timer. Also, sharing a file can introduce some read/write issues. Use the registry instead. – Jerry Dodge Jun 26 '14 at 23:25

1 Answers1

1

One way to do it is to save the process ID in the registry (instead of a file) and then monitor it for any changes. Do this monitoring from within a thread (instead of a timer) so that you're not colliding these checks with your main application thread. When the thread detects the PID has changed, it kills its self.

Here's a complete example:

unit uMain;

interface

uses
  Winapi.Windows, System.SysUtils, System.Classes,
  Vcl.Forms, TlHelp32, Registry;

const
  REG_KEY = 'Software\MyApp\';

type
  TKillThread = class(TThread)
  private
    FPID: Integer;
    FOnKill: TNotifyEvent;
  protected
    procedure Execute; override;
    procedure SYNC_OnKill;
  public
    constructor Create(const PID: Integer);
    property OnKill: TNotifyEvent read FOnKill write FOnKill;
  end;

  TfrmMain = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FKillThread: TKillThread;
    procedure SavePID(const PID: Integer);
    procedure Killed(Sender: TObject);
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

{ TKillThread }

constructor TKillThread.Create(const PID: Integer);
begin
  inherited Create(True);
  try
    FPID:= PID;
  finally
    Resume;
  end;
end;

procedure TKillThread.Execute;
var
  P: Integer;
  R: TRegistry;
begin
  R:= TRegistry.Create(KEY_READ);
  try
    R.RootKey:= HKEY_LOCAL_MACHINE;
    while not Terminated do begin
      if R.KeyExists(REG_KEY) then begin
        if R.OpenKey(REG_KEY, False) then begin
          P:= R.ReadInteger('PID');
          R.CloseKey;
        end;
      end;
      if P <> FPID then begin
        Synchronize(SYNC_OnKill);
      end;
      Sleep(100);
    end;
  finally
    R.Free;
  end;
end;

procedure TKillThread.SYNC_OnKill;
begin
  if Assigned(FOnKill) then
    FOnKill(Self);
end;

{ TfrmMain }

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  ReportMemoryLeaksOnShutdown:= True;
  SavePID(GetCurrentProcessID);
  FKillThread:= TKillThread.Create(GetCurrentProcessID);
  FKillThread.OnKill:= Killed;
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  FKillThread.Free;
end;

procedure TfrmMain.Killed(Sender: TObject);
begin
  Application.Terminate;
end;

procedure TfrmMain.SavePID(const PID: Integer);
var
  R: TRegistry;
begin
  R:= TRegistry.Create(KEY_READ or KEY_WRITE);
  try
    R.RootKey:= HKEY_LOCAL_MACHINE;
    if R.OpenKey(REG_KEY, True) then begin
      R.WriteInteger('PID', PID);
      R.CloseKey;
    end;
  finally
    R.Free;
  end;
end;

end.
Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
  • It might be more elegant to wait for a named event created in the global namespace. You'd signal it from the other user session and re-create it in the new process. Also note, that your solution requires elevated privileges since you're writing to the `HKLM` key path. But still, I don't like the idea of a suicide application... – TLama Jun 27 '14 at 09:42
  • If I were you I wouldn't poll for registry changes (even from a separate thread) but use the [RegNotifyChangeKeyValue](http://msdn.microsoft.com/en-us/library/windows/desktop/ms724892(v=vs.85).aspx) API to trigger a callback if another instance modifies my registry key. – mg30rg Jun 27 '14 at 10:36