Pull to refresh

Comments 24

Под linux есть замечательная консольная утилита flock. С её помощью скрипт, запрещающий повторный запуск пишется в пару строк. Возможно в виндах есть аналог?
Я тоже задаюсь этим вопросом — решения пока не нашел
Для пользователей Qt4/5 есть очень простое кроссплатформенное решение, используем в своих приложений — QtSingleApplication
Хочу все-таки придраться, и сообщить, что Mutex — это примитив синхронизации потоков ядра, и к блокированию двойных запусков он имеет крайне опосредованное отношение.

Решение данной задачи основано на совершенно другой идее, которая называется «именованные объекты ядра». Фактически, вместо семафора (который почему-то назван мьютексом) можно было использовать абсолютно любой другой объект ядра. Популярными вариантами также являются использование файлов и сокетов.

Кстати, вместо имени «Mutex» я бы рекомендовал использовать что-то более уникальное. Например, название вашей программы, GUID, или же полный путь к исполнимому файлу.
Замечание по поводу названия семафора принято — правки в статье сделал
Кстати, вот решение на пакетных файлах. Уже ночь, так что пишу по памяти и без тестирования, надеюсь, что сильно не накосячу.

@echo off
rem runonce.bat - запуск указанного исполнимого файла с блокированием повторных запусков

copy /b /y %1 %1~.exe || goto error
start %1~.exe
exit /b

:error
cls
echo Повторный запуск программы запрещен
pause


Смысл в том, что файл с запущенной программой защищен от записи — а потому попытка перезаписать его неизбежно провалится.
После закрытия приложения повторный запуск невозможен у вас, кажется.
UFO landed and left these words here
А, то есть мы расчитываем, что exe-шник открыт на чтение во время работы приложения. Тогда понял. Но тут может зависеть от ПО, думаю — не каждое ПО будет так оставлять файл. Может там преЛончер какой или типа того.
exe-шник будет открыт на чтение до тех пор, пока выполняется процесс. Если процесс завершится раньше закрытия программы (например, там будет пре-ланчер), то поломается не только мое решение, но и авторское, а также ваше.
Ну, по сути, запрета таки нет, есть защита от дурака.

Пример на PowerShell. Зачем семафоры ядра, можно флаг-файлами делать

Скрин того, что получается

+ добавить скрипт очистки %APPDATA%\*.flg при логоне, мало ли что.

Делать ярлыки на
C:\Windows\SYstem32\WindowsPOwerSHell\v1.0\powershell.exe -STA -NoLogo -NoProfile -WindowStyle Hidden -File "C:\.....\UniqueAppRunner.ps1" -AppID UNIQUENOTEPAD

(Я -WindowStyle Hidden опустил)

Param(
    [string]$AppID=$null
)

$appList = @{
'UNIQUENOTEPAD' = 'C:\WINDOWS\notepad.exe'
'UNIQUECALC' = 'C:\WINDOWS\System32\CALC.exe'
}

if ($AppID -eq $null) {
    Write-Host 'Приложение не указано!' #Тут должен быть MessageBox, но лень
    Exit
}

if ($appList[$appID.ToUpper()] -eq $null) {
    Write-Host 'Запуск этого приложения через скрипт не предусмотрен!' #Тут должен быть MessageBox, но лень
    Exit
}

if ($(Test-Path -Path $appList[$appId.ToUpper()] -ErrorAction SilentlyContinue ) -ne $true) {
    Write-Host 'Исполняемый файл не обнаружен!' #Тут должен быть MessageBox, но лень
    Exit
}

$appFlagFile = Join-Path -Path $env:APPDATA -ChildPath $($AppID + '.flg' ) 

If ( $(Test-Path -Path $appFlagFile ) -ne $false) {
    Write-Host "Приложение $($AppID) уже запущено - присутствует флаг-файл $($appFlagFile)!" #Тут должен быть MessageBox, но лень
    Exit
}

$(Get-Date ) | Out-File -FilePath $appFlagFile
Write-Host -ForegroundColor Green 'Запуск приложения'
Start-Process -FilePath $appList[$appID.ToUpper()] -Wait
Remove-Item -Path $appFlagFile -Force -Confirm:$false
Exit
Или можно с Get-Process поизвращаться…
Ну и свято дело в моем скрипте есть что доработать — ночь.
Отлично! Пропадает питание — и приложение больше не запустится, пока админ не удалит файл-флаг вручную.

Если делать файл-флагами, то надо хотя бы pid в файл записывать…
Прочитайте мое сообщение еще раз и найдите, где вы облажались. Подсказка — строка #4.
Cообщения не читай@Комменты отправляй
После закрытия приложения семафор сам скинется?
Как я понимаю, да. По крайне мере данное решение работает без нареканий почти месяц.
Еще глупый вопрос как от человека не шарящего — семафор ставится в области видимости машины или сеанса? Как отработает на терминальнике, например?
По умолчанию для приложений — в области видимости сеанса.
Чтобы он оказался в глобальной области, надо его имя начать с префикса Global\
Не «скинется» — таким свойством обладают только мьютексы — а удалится.
Я так понимаю, если программа уже запущена, то двойной клик по ассоциированному файлу закончится фейлом.
Может, если открытый документ не изменялся / сохранился, закрыть программу и открыть с новым файлом?
Можно даже попытаться заставить сохранить ранее открытый и изменённый файл.
В принципе можно, но в моей ситуации такая логика (закрыть старый файл — открыть новый) не нужна.
Когда-то очень давно, курсе эдак на первом или втором, делал подобную штуку, но чуть более специфичную: нужно было запретить запуск более одного инстанса программы в пределах одной локальной сети. Сделал обёртку над программой, которая сначала опрашивала у сервера возможность запуска, а потом отправляла уведомление о завершении.
Мне кажется, в случае, когда программа уже запущена, было бы лучше не молча завершать работу прослойки, а предварительно разворачивать окно уже запущенной копии.
Решение, приведённое здесь, как и сам его код, судя хотя бы по добавлению в foreach, совсем не идеальны.
При запуске или существовании копии имени какого-то процесса, естественно, и сообщение будет выбрасывать на экран, (плюс ко всему, я пока точно не могу сказать, точно ли сохраняется равенство при конвертации, это надо работу метода преобразования изучать), короче говоря, это очень приблизительный и очень ленивый набросок, по которому можно только понять идею:

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace forHabr
{
    class Program
    {
        static void Main(string[] args)
        {
            Process[] allProcesses = Process.GetProcesses(); // берём все процессы.
            List<string> processName = new List<string>();
            foreach (Process p in allProcesses)
            {
                string name = p.ProcessName;
                processName.Add(name);
            }

            string[] allProcessName = processName.ToArray<string>(); // имена всех процессов
            string[] distinctProcessName = processName.Distinct<string>().ToArray<string>(); // "уникальные" процессы

            for (; ; ) {
                if (allProcessName.Count<string>() == distinctProcessName.Count<string>()) {  }
                else
                {
                 Task task1 = Task.Run(() => { MessageBox.Show("Вы запустили копию процесса"); });
                }
            }
                
        }
    }
}



Sign up to leave a comment.

Articles