Another instance oraz Startup arguments
Tweet
środa, 2 czerwca 2010
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:
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.
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:
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ąć.
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.
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ąć.


ATOM / RSS
Twitter