Another instance oraz Startup arguments

Jak sprawdzić czy uruchomiona jest inna instancja naszej aplikacji? Otóż – jak się okazuje – bardzo prosto, za pomocą Mutex‘u. W dodatku wiele osób rozpisywało się na ten temat, toteż ja nie będę tego robił – zamieszczę tylko odpowiedni kawałek kodu:

static void Main(string[] args)
{
    bool isNewInstance = false;
    var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();
    string mutexName = string.Format("{0}:{1}", 
    	currentAssembly.GetName().Name, currentAssembly.GetName().Version);
    System.Threading.Mutex mutex = null;
    try
    {
        mutex = new System.Threading.Mutex(false, mutexName, out isNewInstance);
    }
    catch (Exception)
    {
        return;
    }

    if (isNewInstance)
        Console.WriteLine("No active instance.");
    else
        Console.WriteLine("Another instance is active.");

    // (...)
}

A co jeśli wynikłaby potrzeba zezwolenia na uruchomienie innej instancji aplikacji, jednakże przy rozróżnieniu ich argumentami startowymi? Intuicyjnie można by napisać:

static void Main(string[] args)
{
    bool isAnotherInstance = false;
    var currentProcess = System.Diagnostics.Process.GetCurrentProcess();
    var arrProcess = System.Diagnostics.Process.GetProcessesByName(currentProcess.ProcessName);
    if (arrProcess.Length > 1)
    {
        foreach (var item in arrProcess)
        {
            if (item.StartInfo.Arguments == currentProcess.StartInfo.Arguments && item.Id != currentProcess.Id)
            {
                isAnotherInstance = true;
                break;
            }
        }
    }

    if(isAnotherInstance)
        Console.WriteLine("Another instance is active.");
    else
        Console.WriteLine("No active instance.");

    // (...)
}

Niemniej jednak powyższy kawałek kodu nie zapewni nam oczekiwanych rezultatów. Dlaczego? Otóż, gdy zdebugujemy ten krótki kawałek kodu ówcześnie dodając parametry startowe (co zaprezentowano na poniższym zrzucie ekranu), okaże się, iż property StartInfo.Arguments bieżącego procesu jest pustym string’iem. Natomiast tablica argumentów args wykazuje coś innego – że są parametry. Co więcej, każdy inny proces o tej samej nazwie pobrany z systemu, uruchomiony z argumentacją, również ma puste StartInfo.Arguments.

Properties projektu

Podgląd debugera

Oczywiste staje się to, iż w ten sposób nie rozróżnimy instancji naszej aplikacji argumentami startowymi. Aby otrzymać listę aktualnie uruchomionych procesów wraz z ich parametrami startowymi, trzeba skorzystać z zapytania WMI. Poniżej znajduje się odpowiedni kod metody, która nam to umożliwi. Należy pamiętać o dodaniu referencji do System.Management.

public static System.Data.DataTable GetRunningProcesses()
{
    string wmiClass = "Win32_Process";
    string condition = "";
    string[] queryProperties = new string[] { "Name", "ProcessId", "ExecutablePath", "CommandLine" };
    System.Management.SelectQuery query = new System.Management.SelectQuery(wmiClass, condition, queryProperties);
    System.Management.ManagementScope scope = new System.Management.ManagementScope("root\\CIMV2");

    System.Management.ManagementObjectCollection runningProcesses =
        new System.Management.ManagementObjectSearcher(scope, query).Get();

    System.Data.DataTable dtResults = new System.Data.DataTable();
    dtResults.Columns.Add("Name", typeof(string));
    dtResults.Columns.Add("ProcessId", typeof(int));
    dtResults.Columns.Add("Path", typeof(string));
    dtResults.Columns.Add("CommandLine", typeof(string));

    foreach (System.Management.ManagementObject item in runningProcesses)
    {
        dtResults.Rows.Add(item["Name"], item["ProcessId"], item["ExecutablePath"], item["CommandLine"]);
    }

    return dtResults;
}

W powyższym kodzie na początku definiujemy jakie elementy WMI chcemy otrzymać. Później jakie własności nas interesują. Następnie tworzymy zapytanie (SelectQuery) oraz jego zasięg (ManagementScope). Na koniec pobieramy procesy (ManagementObjectSearcher(…). Get()) i wrzucamy je do obiektu DataTable. W ten sposób otrzymamy wystarczające informacje do uzyskania oczekiwanego efektu.
Najważniejszą częścią kodu jest stworzenie “przeszukiwacza” z zapytaniem, a następnie pobranie procesów. Całość w skrócie może wyglądać następująco:

System.Management.ManagementObjectCollection runningProcesses =
    new System.Management.ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_Process").Get();

Na tym etapie mamy wszystkie elementy układanki. Pozostaje nam tylko odpowiednio poprawić program. Ja zrobiłem to w następujący sposób:

static void Main(string[] args)
{
    bool isAnotherInstance = false;
    var currentProcess = System.Diagnostics.Process.GetCurrentProcess();
    var arrProcess = GetRunningProcesses();
    arrProcess.DefaultView.RowFilter = string.Format("Name = '{0}.exe' AND ProcessId <> {1}",
        currentProcess.ProcessName, currentProcess.Id);
    if (arrProcess.DefaultView.Count > 0)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < args.Length; i++)
            sb.AppendFormat("{0} ", args[i]);
        string currentProcessArgs = sb.ToString().TrimEnd();

        for (int i = 0; i < arrProcess.DefaultView.Count; i++)
        {
            string singleProcessArgs = arrProcess.DefaultView[i]["CommandLine"].ToString().Replace("\"", "");
            singleProcessArgs = singleProcessArgs.Replace(arrProcess.DefaultView[i]["Path"].ToString(), "").Trim();
            if (singleProcessArgs == currentProcessArgs)
            {
                isAnotherInstance = true;
                break;
            }
        }
    }

    if (isAnotherInstance)
        Console.WriteLine("Another instance is active.");
    else
        Console.WriteLine("No active instance.");

    // (...)
}

Czyli: pobrałem procesy z WMI i przefiltrowałem wyniki zapytania po nazwie oraz id mojego procesu. Gdy okaże się, że uruchomiona jest inna instancja aplikacji, sklejam argumenty startowe mojego procesu w jeden ciąg znaków. To samo w pętli robię z pobranymi procesami, dokonując przy tym małych modyfikacji. Na koniec oczywiście porównuje argumenty procesów.

Moja modyfikacja nie jest idealna, ponieważ nie bierze pod uwagę kolejności argumentów startowych, co nie oznacza jednak, iż tego efektu nie da się osiągnąć.

Promuj

2 thoughts on “Another instance oraz Startup arguments

Leave a Reply

Your email address will not be published. Required fields are marked *