Как я могу узнать, запущен ли другой экземпляр моей программы? - PullRequest
28 голосов
/ 20 января 2009

Как узнать, запущен ли один экземпляр моей программы? Я думал, что мог бы сделать это с файлом данных, но это было бы просто грязно: (

Я хочу сделать это, так как хочу, чтобы только один экземпляр был открыт в одной точке.

Ответы [ 12 ]

39 голосов
/ 20 января 2009

Как впервые предположил Джон, вы можете попробовать создать мьютекс. Звоните CreateMutex. Если вы получите ненулевой дескриптор, позвоните GetLastError. Он сообщит вам, был ли вы тем, кто создал мьютекс, или мьютекс был открыт ранее (Error_Already_Exists). Обратите внимание, что не необходимо для приобретения права собственности на мьютекс. Мьютекс не используется для взаимного исключения. Он используется, потому что это именованный объект ядра. Событие или семафор тоже могут сработать.

Техника мьютекса дает логический ответ: да, есть другой экземпляр или нет, его нет.

Вы часто хотите знать больше, чем просто это. Например, вы можете захотеть узнать дескриптор главного окна другого экземпляра, чтобы вы могли указать, чтобы он вышел на передний план вместо вашего другого экземпляра. Вот где файл с отображением в памяти может пригодиться; он может содержать информацию о первом экземпляре, чтобы более поздние экземпляры могли ссылаться на него.

Будьте внимательны при выборе названия мьютекса. Внимательно прочитайте документацию и помните, что некоторые символы (например, обратная косая черта) недопустимы в некоторых версиях ОС, но необходимы для определенных функций в других версиях ОС.

Также помните о проблеме других пользователей. Если ваша программа может быть запущена через удаленный рабочий стол или с помощью быстрого переключения пользователей, то могут быть другие пользователи, уже запускающие вашу программу, и вы, возможно, не захотите ограничивать текущий запуск программы вашим пользователем. В этом случае не используйте глобальное имя. Если вы do хотите ограничить доступ для всех пользователей, убедитесь, что атрибуты безопасности объекта мьютекса таковы, что каждый сможет открыть для него дескриптор. Использование нулевого указателя для параметра lpSecurityAttributes недостаточно для этого; «дескриптор безопасности по умолчанию», который упоминается в MSDN, предоставляет полный доступ текущему пользователю и не дает доступа другим.

Вам разрешено редактировать файл DPR вашей программы. Обычно это хорошее место для подобных вещей. Если вы подождете до события OnCreate одной из ваших форм, то ваша программа уже имеет некоторый импульс для нормального запуска, поэтому неуместно пытаться завершить программу в этот момент. Лучше прекратить работу, пока не было выполнено слишком много работы с пользовательским интерфейсом. Например:

var
  mutex: THandle;
  mutexName: string;
begin
  mutexName := ConstructMutexName();

  mutex := CreateMutex(nil, False, PChar(mutexName));

  if mutex = 0 then
    RaiseLastOSError; // Couldn't open handle at all.

  if GetLastError = Error_Already_Exists then begin
    // We are not the first instance.
    SendDataToPreviousInstance(...);
    exit;
  end;
  // We are the first instance.

  // Do NOT close the mutex handle here. It must
  // remain open for the duration of your program,
  // or else later instances won't be able to
  // detect this instance.

  Application.Initialize;
  Application.CreateForm(...);
  Application.Run;
end.

Есть вопрос о том, когда закрывать дескриптор мьютекса. Вам не нужно закрывать это. Когда ваш процесс окончательно завершается (даже если происходит сбой), ОС автоматически закрывает все оставшиеся дескрипторы, а когда больше нет открытых дескрипторов, объект мьютекса будет уничтожен (что позволит запустить другой экземпляр вашей программы и считать себя быть первым экземпляром).

Но вы все равно можете закрыть ручку. Предположим, вы решили реализовать функцию SendDataToPreviousInstance, о которой я упоминал в коде. Если вы хотите стать модным, то вы можете учесть, что предыдущий экземпляр уже закрывается и не может принять новые данные. Тогда вам не захочется закрывать второй экземпляр. Первый экземпляр может закрыть дескриптор мьютекса, как только узнает, что завершает работу, и фактически становится экземпляром «хромой утки». Второй экземпляр попытается создать дескриптор мьютекса, преуспеть и считать себя настоящим первым экземпляром. Предыдущий экземпляр будет закрыт непрерывно. Используйте CloseHandle, чтобы закрыть мьютекс; вызывайте его из обработчика событий OnClose главной формы или из любого другого места, например, Application.Terminate.

18 голосов
/ 20 января 2009

Вы можете создать семафор и остановить выполнение (поместить код в файл * .dpr) и вывести на экран запущенное приложение.

var
  Semafor: THandle;

begin
  { Don't start twice ... if already running bring this instance to front }
  Semafor := CreateSemaphore(nil, 0, 1, 'MY_APPLICATION_IS_RUNNING');
  if ((Semafor <> 0) and { application is already running }
     (GetLastError = ERROR_ALREADY_EXISTS)) then 
  begin
    RestoreWindow('TMyApplication');
    CloseHandle(Semafor);
    Halt;
  end;

  Application.CreateForm(....);    
  Application.Initialize;
  Application.Run;
  CloseHandle(Semafor);
end;

РЕДАКТИРОВАТЬ (добавлен метод RestoreWindow):

aFormName - это имя вашего основного класса формы в вашем приложении.

procedure RestoreWindow(aFormName: string);
var
  Wnd,
  App: HWND;    
begin
  Wnd := FindWindow(PChar(aFormName), nil);
  if (Wnd <> 0) then 
  begin { Set Window to foreground }
    App := GetWindowLong(Wnd, GWL_HWNDPARENT);
    if IsIconic(App) then 
      ShowWindow(App, SW_RESTORE);

    SetForegroundwindow(App);
  end;
end;
17 голосов
/ 20 января 2009

Всемогущий JVCL имеет компонент для этой цели. Смотрите "TJvAppInstances".

6 голосов
/ 20 января 2009

Вы создаете систему мьютекс .

У меня нет кода Delphi, но вот код C ++:

HANDLE Mutex;

const char MutexName[] = "MyUniqueProgramName";

Mutex = OpenMutex(MUTEX_ALL_ACCESS, false, MutexName);

if (Mutex)
     throw Exception("Program is already running.");
else
     Mutex = CreateMutex(NULL, true, MutexName);
5 голосов
/ 20 января 2009

Обычным решением является создание именованного для всей системы mutex .

  • Если вам удастся создать его, вы - одно из запущенных приложений.
  • Если нет, вы знаете, что есть другой.

EDIT:

Я не предоставил код, так как не знаю Delphi. Я могу предоставить код C #, если это будет полезно.

2 голосов
/ 20 января 2009

Я хотел бы добавить один балл к превосходному ответу Роба Кеннеди (кроме того факта, что было бы лучше сделать функцию из его кода, а не копировать все в файл DPR . Вам нужны только два параметра: имя мьютекса и логическое значение (должен ли мьютекст быть индивидуальным или общесистемным).

Ответ не имеет большого значения для именования мьютекса. Если вы ожидаете, что ваша программа будет установлена ​​с помощью Inno Setup (и, возможно, других инструментов настройки), вы должны тщательно выбрать имя, так как мьютекс можно использовать, чтобы программа установки проверила, запущено ли приложение в данный момент, и предупредила они должны закрыть все экземпляры приложения. Если вы решите разрешить один экземпляр программы для каждого пользователя, вам может потребоваться также создать второй общесистемный мьютекс, поскольку для установки может не потребоваться никаких запущенных экземпляров приложения на всех , чтобы в состоянии заменить файлы. Имя, которое будет использоваться для синхронизации с установщиком InnoSetup, должно быть жестко запрограммировано.

1 голос
/ 23 февраля 2012

Управление количеством экземпляров приложения:

http://delphi.about.com/od/windowsshellapi/l/aa100703a.htm

1 голос
/ 20 января 2009

Вы можете просто использовать функцию API FindWindow Windows. В delphi имя класса окна совпадает с именем класса, вы можете переопределить имя класса, переопределив функцию CreateParams. Чтобы проверить, существует ли окно, добавьте код перед созданием главного окна, перед Application.Initialize;

Program test
var 
  handle :HWND;
begin
  handle := FindWindow('TMySuperApp', nil);

  if IsWindow(handle) then
  begin 
       //app is running
       exit;
  end.

  Application.Initialize;
  Application.CreateForm(TMySuperApp, SuperApp);
  Application.Run;
end;
1 голос
/ 20 января 2009

Я бы сказал, что есть несколько разных стратегий, которые вы можете использовать. Но самый простой (и не зависящий от платформы) - это тот, который вы сами предложили, а именно, при запуске программы проверить, есть ли файл блокировки, созданный в заданном, определенном месте. Если этот файл блокировки существует, значит, другой экземпляр уже запущен, если он не существует, значит, другой экземпляр не запущен. Когда ваша программа завершает работу, вы удаляете файл блокировки.

Однако, при использовании этой стратегии у вас возникает другая проблема, что произойдет, если ваша программа потерпит крах? Файл блокировки по-прежнему сохраняется, и этот конкретный случай необходимо обработать.

Другой стратегией является общесистемное решение мьютекса, где вы регистрируете свое присутствие в операционной системе (или также возможно, что это делается автоматически). Когда второй экземпляр затем пытается запуститься, он проверяет, существует ли уже активный процесс с определенным идентификатором. Если он уже существует, второй процесс выбирает не запускаться и, по выбору, фокусирует окно первого процесса (если рассматриваемый процесс имеет окно, которое есть).

Однако эта стратегия зависит от платформы, и реализация будет отличаться от платформы к платформе.

0 голосов
/ 10 октября 2018

Если вы хотите остановить выполнение ваше приложение более одного раза в в то же время (поместить код в *. Dpr файл проекта). отобразит сообщение после запуска второго приложения и мгновенно остановит его.

Forms,
  Unit1 in 'Unit1.pas' {Form1},
// add this units ....
TlHelp32,SysUtils,Windows,Dialogs;

{$R *.res}


function ProcessCount(const ExeName: String): Integer;
var
  ContinueLoop: BOOL;
  FSnapshotHandle: THandle;
  FProcessEntry32: TProcessEntry32;
begin
  FSnapshotHandle:= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  FProcessEntry32.dwSize:= SizeOf(FProcessEntry32);
  ContinueLoop:= Process32First(FSnapshotHandle, FProcessEntry32);
  Result:= 0;
  while Integer(ContinueLoop) <> 0 do begin
    if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) =
      UpperCase(ExeName)) or (UpperCase(FProcessEntry32.szExeFile) =
      UpperCase(ExeName))) then Inc(Result);
    ContinueLoop:= Process32Next(FSnapshotHandle, FProcessEntry32);
  end;
  CloseHandle(FSnapshotHandle);
end;


begin
  if ProcessCount(ExtractFileName(Application.ExeName)) > 1 then begin
    MessageDlg('Application is already running!', mtError, [mbOK], 0);
    Application.Terminate;
  end else begin

  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
  end;

end.
...