Otwarcie jednej instancji tylko jedenej spośród czterech aplikacji

0

Projekt nad którym pracuję składa się z czterech aplikacji (czterech osobnych programów); Każda z nich może być uruchomiona tylko w jednej instancji oraz samotnie - w jednym czasie nie mogą działać razem dwie lub więcej; Musi to tak działać globalnie, dla wszystkich zalogowanych użytkowników;

Do stworzenia takiego zabezpieczenia chcę wykorzystać globalne muteksy - według mnie nadają się idealnie; Jednak mam problem z tymi muteksami, bo nie do końca rozumiem jak one działają oraz jak, kiedy i kto ma zająć się ich zwalnianiem; W sieci jest bardzo dużo przykładów (jeśli chodzi o wykrywanie kolejnych instancji tego samego programu) jednak każdy z nich jest inny i ciężko mi się połapać;

Aby zabezpieczyć program przed wieloma instancjami, można użyć czegoś w ten deseń:

function CanRunApplication(): Boolean;
var
  hMutex: THandle;
begin
  hMutex := CreateMutex(nil, False, 'Global\MutexName');
  Result := (hMutex <> 0) and (GetLastError() = ERROR_SUCCESS);
end;

No i dobra - jeśli muteks zostanie stworzony pomyślnie, funkcja zwróci True, w przeciwnym razie False; Tak utworzonego muteksu nie trzeba nigdzie zwalniać - system się tym zajmie po wyłączeniu programu; Każdy z tych czterech programów wchodzących w skład pakietu, tworzy sobie swój unikalny muteks, aby nie dało się go uruchomić kolejny raz i to działa super;

Główny program sprawdza muteksy przy rozruchu i w razie kolizji po prostu nie uruchamia się; Pozostałe trzy muszą wykonać sprawdzenie podczas działania, zanim zaczną grzebać i modyfikować pliki całego pakietu;

Napisałem sobie funkcję, która testuje utworzenie kolejno trzech mutekstów (takich jak pozostałe trzy programy z pakietu) i w razie kolizji któregoś z tych trzech nie rozpoczyna operacji na plikach pakietu; Użyłem do tego celu funkcji CreateMutex i wykrywa uruchomiony inny program; Jednak nawet jeśli ten drugi zamknę (muteks zostanie automatycznie zwolniony), to kolejny test nadal wykrywa ten muteks i nadal nie przechodzi do kolejnego kroku (czyli coś spaprałem);

Problem w tym, że nie wiem czy do takiego testu użyć CreateMutex czy OpenMutex (czy może jeszcze czegoś innego), a także nie rozumiem jakie ma znaczenie parametr bInitialOwner tej pierwszej (czytałem dokumentację);

Funkcja która mnie interesuje, powinna wyglądać w ten sposób:

const
  { nazwy mutekstów pozostałych aplikacji z pakietu }
  MUTEX_NAME_FIRST  = 'Global\MutexNameFirst';
  MUTEX_NAME_SECOND = 'Global\MutexNameSecond';
  MUTEX_NAME_THIRD  = 'Global\MutexNameThird';

type
  { pierwsze trzy oznaczają uruchomioną inną aplikację z pakietu }
  TPackageAppKind = (pkaFirst, pkaSecond, pkaThird, pkaAlone);

function PackageAppIsRunning(): TPackageAppKind;
begin
  {
    sprawdzenie muteksu pierwszej innej aplikacji z pakietu
    Exit(pkaFirst) - jeśli pierwsza jest uruchomiona

    sprawdzenie muteksu drugiej innej aplikacji z pakietu
    Exit(pkaSecond) - jeśli druga jest uruchomiona

    sprawdzenie muteksu trzeciej innej aplikacji z pakietu
    Exit(pkaThird) - jeśli trzecia jest uruchomiona
  }

  { jeśli żadna nie jest uruchomiona zwracamy ostatni enum }
  Result := pkaAlone;
end;

I teraz w jaki sposób poprawnie sprawdzić, czy któraś z pozostałych aplikacji jest uruchomiona? Użyć CreateMutex czy OpenMutex? Jeśli uda się utworzyć muteks którejś z pozostałych aplikacji to jak i w jakich warunkach go zwolnić? Brać pod uwagę uchwyt, GetLastError czy i to i to?

W razie gdyby coś jeszcze nie było jasne to postaram się podać; Choć mam wrażenie, że i tak za dużo napisałem; Dzięki z góry za pomoc.

0

Jeśli każda z trzech aplikacji sprawdza sama czy nie jest uruchomiona "podwójnie", to wystarczy że główna aplikacja będzie sprawdzać sama siebie.
Tak mi się przynajmniej wydaje.

0

Nie o coś takiego chodzi...

Mocno upraszczając - główna aplikacja tworzy przy rozruchu globalny muteks np. o nazwie Global\MutexMainApp; Używa go do zablokowania kolejnych instancji głównej aplikacji; Drugi program (poboczny) tworzy przy rozruchu muteks Global\MutexSecondary i także używa go do wykrycia swoich kolejnych instancji;

I teraz klucz - program poboczny musi sprawdzić czy główna aplikacja działa, więc musi spróbować utworzyć Global\MutexMainApp; Jeżeli uda się go utworzyć - główny program nie jest uruchomiony, a że muteks został przed chwilą utworzony to należy go zwolnić; Jeżeli próba utworzenia nie powiedzie się - program główny działa;

To potrzebuję wykonać prawidłowo, a nie za bardzo mi to wychodzi.

1
unit UniInstance;

interface

uses
  Windows,
  Messages,
  Classes,
  Forms;

type
  TParamsEvent=procedure(Sender:TObject;Params:TStringList)of Object;

  {TUniInstance=}

  TUniInstance=class(TComponent)
  private
    FOnParams:TParamsEvent;
    FHandle:HWND;
    FCreateMap:Boolean;
  protected
    procedure SendParams(IsNextInstance:Boolean);
    procedure SetUniqueName(Value:String);
    function GetUniqueName:String;
    function IsAppExeName:Boolean;
  public
    constructor Create(AOwner:TComponent);override;
    destructor Destroy;override;
    procedure Loaded;override;
    procedure WndProc(var Msg:TMessage);virtual;
    procedure NewParams(Params:TStringList);
    property Handle:HWND read FHandle;
  published
    property OnParams:TParamsEvent read FOnParams write FOnParams;
    property UniqueName:String read GetUniqueName write SetUniqueName stored IsAppExeName;
  end;

implementation

uses
  SysUtils;

const MaxParamSize=1024;
type
  PSharedArea=^TSharedArea;
  TSharedArea=record
    XHandle:HWND;
    XParam:array[0..MaxParamSize]of Char;
  end;

var SharedAreaName:String;
var MapHandle:THandle;
var ParamMessage:Integer;
var ParamsBuffer:TStringList;
var UniInstanceList:TList;

function AppName:String;
var Temp:TWIN32FindData;
var SearchHandle:THandle;
var LastSlash,TempPathPtr:PChar;
var Part:String;
begin
  SetLength(Result,0);
  TempPathPtr:=PChar(ParamStr(0));
  LastSlash:=StrRScan(TempPathPtr,'\');
  while LastSlash<>nil do
  begin
    SearchHandle:=FindFirstFile(TempPathPtr,Temp);
    try
      if SearchHandle<>ERROR_INVALID_HANDLE then
      begin
        if Temp.cFileName[0]=#0 then
        begin
          Part:=String(Temp.cAlternateFileName);
        end
        else
        begin
          Part:=String(Temp.cFileName);
        end;
      end
      else
      begin
        SetLength(Part,0);
      end;
    finally
      Windows.FindClose(SearchHandle);
    end;
    Result:='\'+Part+Result;
    if LastSlash<>nil then
    begin
      LastSlash^:=#0;
      LastSlash:=StrRScan(TempPathPtr,'\');
    end;
  end;
  Result:=TempPathPtr+Result;
end;

{TUniInstance.}

constructor TUniInstance.Create(AOwner:TComponent);
begin
  inherited Create(AOwner);
  FCreateMap:=false;
  FHandle:=AllocateHWnd(WndProc);
  UniInstanceList.Add(Self);
end;

destructor TUniInstance.Destroy;
begin
  UniInstanceList.Remove(Self);
  DeallocateHWnd(FHandle);
  if FCreateMap then CloseHandle(MapHandle);
  inherited Destroy;
end;

procedure TUniInstance.Loaded;
begin
  inherited Loaded;
  if csDesigning in ComponentState then Exit;
  if MapHandle=0 then
  begin
    FCreateMap:=true;
    ParamMessage:=RegisterWindowMessage(PChar(SharedAreaName));
    MapHandle:=CreateFileMapping($FFFFFFFF,nil,PAGE_READWRITE,0,SizeOf(TSharedArea),PChar(SharedAreaName));
    SendParams(GetLastError()=ERROR_ALREADY_EXISTS);
  end;
end;

procedure TUniInstance.WndProc(var Msg:TMessage);
var I:Integer;
var SharedArea:PSharedArea;
begin
  with Msg do
  begin
    if Integer(Msg)=ParamMessage then
    begin
      if Boolean(WParam) then
      begin
        SharedArea:=MapViewOfFile(MapHandle,FILE_MAP_ALL_ACCESS,0,0,0);
        try
          with SharedArea^ do
          begin
            ParamsBuffer.Add(StrPas(XParam));
          end;
        finally
          UnmapViewOfFile(SharedArea);
        end;
      end
      else
      begin
        with UniInstanceList do
        begin
          for I:=Count-1 downto 0 do
          begin
            TUniInstance(Items[I]).NewParams(ParamsBuffer);
          end;
          ParamsBuffer.Clear;
        end;
      end;
      with Application do
      begin
        if IsIconic(Handle) then ShowWindow(Handle,SW_RESTORE);
        SetForegroundWindow(Handle);
      end;
    end
    else
    begin
      Result:=DefWindowProc(FHandle,Msg,WParam,LParam);
    end;
  end;
end;

procedure TUniInstance.NewParams(Params:TStringList);
begin
  if Assigned(FOnParams) then FOnParams(Self,Params);
end;

procedure TUniInstance.SendParams(IsNextInstance:Boolean);
var I,M:Integer;
var SharedArea:PSharedArea;
var Window:HWND;
begin
  SharedArea:=MapViewOfFile(MapHandle,FILE_MAP_ALL_ACCESS,0,0,0);
  try
    with SharedArea^ do
    begin
      if IsNextInstance then
      begin
        Window:=XHandle;
      end
      else
      begin
        XHandle:=FHandle;
        Window:=FHandle;
      end;
      M:=ParamCount;
      if M>0 then
      begin
        for I:=1 to M do
        begin
          StrLCopy(XParam,PChar(ParamStr(I)),MaxParamSize);
          SendMessage(Window,ParamMessage,Longint(true),I);
        end;
        PostMessage(Window,ParamMessage,Longint(false),0);
      end;
    end;
  finally
    UnmapViewOfFile(SharedArea);
  end;
  if IsNextInstance then
  begin
    SetForegroundWindow(Window);
    SetActiveWindow(Window);
    SetWindowPos(Window,HWND_TOP,0,0,0,0,SWP_NOMOVE or SWP_NOSIZE);
    with Application do
    begin
      ShowMainForm:=false;
      Terminate;
    end;
  end;
end;

function TUniInstance.IsAppExeName:Boolean;
begin
  Result:=(SharedAreaName<>ParamStr(0));
end;

function TUniInstance.GetUniqueName:String;
begin
  Result:=SharedAreaName;
end;

procedure TUniInstance.SetUniqueName(Value:String);
begin
  if SharedAreaName<>Value then
  begin
    SharedAreaName:=Value;
  end;
end;

function RepSlash(const Str:String):String;
begin
  Result:=StringReplace(Str,'\','/',[rfReplaceAll]);
end;

initialization
  MapHandle:=0;
  SharedAreaName:=RepSlash(AppName);
  ParamsBuffer:=TStringList.Create;
  UniInstanceList:=TList.Create;

finalization
  ParamsBuffer.Free;
  UniInstanceList.Free;

end.

Kładziesz kontrolkę na główne formatki i dajesz nazwę taką samą.
Mało tego że druga się nie odpali to jeszcze informuję już odpaloną o tym, jakie parametrami próbowała się odpalić, przy drobnej korekcie może wysłać razem ze swoją nazwą.

0

Za dużo tego... Serio - jedyne co potrzebuję to tylko i wyłącznie sprawdzenie, czy dany program istnieje (nie inna instancja, a zupełnie inna aplikacja) i to wszystko; Muteksy wystarczą, bo samo sprawdzenie tego to kilka linijek kodu w WinAPI; Jednak WinAPI nie jest moją mocną stroną, dlatego też pytam;

Po to są te muteksy, żeby można było się komunikować w obrębie systemu i sądzę, że to sensowne rozwiązanie w tym przypadku.

0

zamiast tworzyć cztery RÓŻNE muteksy niech każda próbuje tworzyć TAKI SAM - problem z głowy :p

0

@abrakadaber, też tak można, jednak nadal pozostaje pytanie - jak prawidłowo sprawdzić, czy muteks już istnieje? To jest głównym problemem, z którym walczę i przegrywam :]

Edit: Moment, moment - mam jeszcze jeden sposób, już sprawdzam;
Edit2: Jednak nie, nie mam innego sposobu...

0

Może tak:

  { TMutexClass }

  TMutexClass = class
    protected
      fLastError: DWORD;
      fMutex: THANDLE;
    public
      constructor Create(MutexName: PChar);
      destructor Destroy; override;
      function IsAnotherInstanceRunning(): boolean;
  end;

{ TMutexClass }

constructor TMutexClass.Create(MutexName: PChar);
begin
   fMutex:=CreateMutex(nil,false,MutexName);
   fLastError:=GetLastError;
end;

destructor TMutexClass.Destroy;
begin
  if (fMutex<>THANDLE(nil)) then begin
     CloseHandle(fMutex);
     fMutex:=THANDLE(nil);
  end;
  inherited Destroy;
end;

function TMutexClass.IsAnotherInstanceRunning: boolean;
begin
   Result := fLastError = ERROR_ALREADY_EXISTS;
end;

4

OpenMutex i sprawdzasz co zwróci - jak jakiś uchwyt to już ktoś stworzył mutex (znaczy sprawdzany program działa), jak null to znaczy, że nie ma. Dla pewności możesz jeszcze sprawdzić GetLastError czy na pewno jest ERROR_FILE_NOT_FOUND (mutex nie istnieje)

0

A co jest złego w rozwiązaniu @_13th_Dragon? Nie pisz że to dużo kodu.

1

@furious programming jeśli dobrze zrozumiałem mamy 4 aplikacje każda ma inny mutex więc jeśli zrobiłeś to porządnie to odpalenie exe (gdy już jest odpalona appka) drugi raz spowoduje przeskoczenie focusa na tą aplikację. W związku z powyższym z aplikacji secondary robisz uruchomienie main.exe. Jeśli main.exe nie ma to się uruchomi, jak jest to też dobrze bo dzięki mutexowi nie odpali się drugi raz. Po co zatem kombinować z generowaniem kolejnego mutexa w secondary?
Ja to robię tak w pliku dpr:

Var
  Handle:THandle;
begin
  MessID:=RegisterWindowMessage('"NazwaMutexa"');
  try
    Handle:=CreateMutex(nil,True,'"NazwaMutexa"');
    If GetLastError=ERROR_ALREADY_EXISTS Then    
      PostMessage(HWND_BROADCAST,MessID,0,0)
    else
    begin
      Application.Initialize;
      Application.CreateForm(TfmMain, fmMain);
      Application.Run;
    end;
  finally
    if Handle<>0 then CloseHandle(Handle);
  end;
end.

i na formie:

var
  MessID: UINT;
procedure TfmMain.AppEventMessage(var Msg: tagMSG;
  var Handled: Boolean);
begin
  if Msg.Message=MessID then 
  begin
    Application.Restore;
    SetForeGroundWindow(Application.MainForm.Handle);
    Handled:=True;
  end;
end;
0

Ok, to może odpowiem na wcześniejsze posty, a następnie pokażę testowe rozwiązanie;

@Anoxic - Twoja klasa, moja funkcja, sposób @woolfika i przykłady z Google służą do sprawdzenia, czy uruchomiona jest już instancja tego samego programu; Jak napisałem wcześniej, to już mam i można to wykonać na wiele sposobów; Jednak potrzebuję innego, kolejnego zabezpieczenia;

@Ciężki młot - ten kod jest bardzo dobry, jednak jest to komponent, a ja potrzebuję wykonać tego sprawdzenia zanim zostanie wykonane cokolwiek z klasy Application (jako pierwsze linie pliku .lpr); Oczywiście można ten kod przerobić, tak aby nie był komponentem, jednak w dalszym ciągu jest to sprawdzanie instancji tego samego programu i komunikacja z jednej instancji do kolejnej, do tego kodu za dużo jak na takie proste rozwiązanie, które potrzebuję zaimplementować; To już mam i nie w tym leży problem;

@woolfik - to nadal dotyczy instancji tej samej aplikacji; Komunikacja jednej instancji z drugą w docelowym kodzie będzie wykonana w ten sposób; Ten kod jest lepszy niż szukanie okna funkcją FindWindow, więc na pewno jego zastosuję;


Zobaczcie do załącznika; Są tam dwie testowe aplikacje - main jako główna aplikacja oraz tool1 jako pierwsze narzędzie; Tych narzędzi będzie trzy, ale do testu wystarczy jedna;

Główny program przy rozruchu sprawdza wszystko - czy jest już sam uruchomiony i jeśli tak, wyświetla komunikat z informacją; Jeżeli nie, sprawdza czy uruchomione jest narzędzie tool1; Jeżeli jest uruchomione, wyświetla komunikat błędu i zamyka się; Jeżeli nie - program startuje normalnie;

Narzędzie przy rozruchu sprawdza tylko swoje instancje; Jeżeli jakaś instancja już działa, wyświetla komunikat z informacją i zamyka się; Jeżeli nie - uruchamia się normalnie; Na formularzu jest przycisk do wykonania operacji; Po kliknięciu w niego narzędzie sprawdza, czy otwarta jest główna aplikacja; Jeżeli jest - wyświetla błąd; Jeżeli nie - wyświetla komunikat z informacją (tu będzie wykonane przetwarzanie plików);

I teraz tak, uruchamiamy główny program; Próbujemy drugi raz - informacja że już działa; Włączamy narzędzie - uruchamia się; Próbujemy drugi raz - komunikat, że jedna instancja już działa; Wciskamy guzik i błąd - komunikat, że główny program działa; Zamykamy główny program i wciskamy przycisk - informacja, że narzędzie działa samo i można przetwarzać pliki; Wszystko działa jak należy;

Te komunikaty symbolizują rozróżnienie działań, które wykonywane będą w różnych przypadkach; Zamiast informacji że już jest instancja odpalona, będzie po prostu rozgłoszenie komunikatu i podbicie formularza na wierzch; O takie coś mi chodziło, jednak używałem nie tej funkcji co potrzeba; Teraz wszystko działa bardzo dobrze, kodu jest kilka linii, więc mogę już przystąpić do implementacji w kodzie produkcyjnym;

Prezentowane kody zrzucam na dysk, bo na pewno się przydadzą - dziękuję za nie; Jako rozwiązanie zaznaczam post @abrakadaber, bo użycie funkcji OpenMutex jest w moim przypadku kluczowe; Dziękuję za pomoc, problem zażegnany :]

0

@furious programming to nie jest instancja tego samego programu, tylko programu o takim samym podanym unikalnym napisie.
Więc to już na dzień dobry robi to co chcesz.

0

@_13th_Dragon - no tak, jednak nadal wolę pozostać przy swoim rozwiązaniu, czyli przy muteksach; Może i nie jest to jakieś bardzo popularne rozwiązanie (choć tego nie umiem określić), jednak jest proste w implementacji i działa bez zarzutów, a to najważniejsze;

Całość rozwiązałem nieco inaczej niż podałem wcześniej, jednak doszedłem do wniosku, że nie będzie to zbyt dobre rozwiązanie i lepiej jest wszystko walidować od razu, a nie w trakcie działania któregoś programu.

0

Nie zupełnie "bez zarzutów", zastanawiałeś co się stanie jeżeli program zostanie ubity?

0

Nic się nie stanie - muteks i tak zostanie zwolniony, sprawdzałem to; Chyba że coś innego masz na myśli?

1

Ja do tego używałem plików mapowanych (CreateFileMapping, MapViewOfFile, OpenFileMapping).

Możesz też użyć tego:
https://github.com/blikblum/luipack/tree/master/uniqueinstance
lub tego:
http://www.codeproject.com/Articles/1089948/SingleInstance-FreePascal

0

po co jakies mutext srutexy. Masz 1.exe 2.exe 3.exe 4.exe i sprawdzczas czy procesy instnieja. Dodajesz zabezpieczenie przed zmiana nazwy pliku i gitara. W ogole po co ci 4 aplikacje? Cos jest nie tak.

0

@vpiotr - można użyć wielu rzeczy i zrobić to dobrze i bezpiecznie; Od zawsze korzystałem z muteksów, dlatego że są do tego bardzo dobre i nie znalazłem sposobu na ich oszukanie; Głównie dlatego, że system w tym pomaga; Do tego całe zabezpieczenie przed odpaleniem drugiej instancji to dwie linie kodu, opakowane w ramkę funkcji; Wykrywanie kolejnej instancji tej samej aplikacji mam już dawno temu zrobione; Jedyne co potrzebowałem to sprawdzenie istnienia mutekstu obcej aplikacji - to już też jest zrobione;

mca64 napisał(a)

W ogole po co ci 4 aplikacje? Cos jest nie tak.

Weź chłopie pomyśl, zanim coś skomentujesz; Może połącz wszystkie aplikacje z pakietu Office w jedną, bo po co kilka... Nie wiesz po co cztery aplikacje? Zaspokoję Twoją ciekawość, choć w tym wątku nie ma to żadnego znaczenia;

Na pakiet składa się główny program, z którego korzysta użytkownik - jego kod może być kompilowany jako wersja pełna (instalacyjna), jako wersja przenośna (nieco okrojona) oraz jako demówka; Taką kompilację umożliwiają symbole kompilatora - bardzo przydatna rzecz; Jest instalator, który potrafi instalować świeżą kopię, naprawiać istniejącą instalację oraz wyodrębniać wersję przenośną (portable); Do kompletu jest deinstalator - wiadome co robi; Czwartą aplikacją jest program aktualizujący; Wykonany jest jako zewnętrzne narzędzie, dlatego że aktualizacja obejmować będzie przede wszystkim program główny i jego pliki wypakowane na dysk podczas instalacji, a także deinstalator;

Jest to bezpieczniejsze i wygodniejsze rozwiązanie, nie nękające użytkownika powiadomieniami przy każdym rozruchu; Aktualizator będzie jak trojan - może wykonywać różne czynności, które spisane będą jako plik konfiguracyjny, zawarty w pliku z aktualizacją; A że będzie mógł wykonywać różne czynności - sam nie będzie musiał być aktualizowany;

Tak więc nie jest dziwnym fakt, iż są cztery aplikacje, a nie jedna; Tak jest dobrze, całość zaczyna działać wyśmienicie, więc jestem bardzo zadowolony i dalej będę kontynuować to co mam zaplanowane;

PS: Wątek jest już zakończony, problem rozwiązany kilka postów wcześniej, więc nie ma co dalej ciągnąć dyskusji; W razie czego piszcie w komentarzach pod tym postem czy innymi, jeśli ktoś chce coś dopowiedzieć, a nie jest to związane z funkcją OpenMutex :]

0

Teraz wiem co chcesz osiagnac, ale dalej to nie jest takie oczywiste ze tak trzeba czy nalezy. Ja bym to zrobil inaczej. Wiec moze ty tez pomysl za nim cos napiszesz. W ogole co ma word czy excel do twoich potrzeb? Jak tamte aplikacje mozna uruchomic jednoczesnie;p

1 użytkowników online, w tym zalogowanych: 0, gości: 1