wrz
14
2012
Czasami zdarza się, że potrzebujemy wymienić jakieś proste informacje pomiędzy procesami a nie chcemy budować całej infrastruktury komunikacyjnej w stylu WCF, socketów itp. Na taką prostą sytuację wymagającą komunikacji interprocesowej ostatnio natrafiłem.Otóż mam sobie aplikację WPF-ową która działa w Tray-u. Okno główne odpala tylko po kliknięciu na Tray-a. Oczywiście chcemy, żeby w danej chwili uruchomiona była tylko jedna instancja takiej aplikacji, więc zrobiłem zabezpieczenie na Mutexie (działa to w ten sposób, że aplikacja próbuje utworzyć nowy obiekt typu Mutex, a w przypadku kiedy już taki istnieje to znaczy że mamy odpaloną inna instancję aplikacji i tę należy zamknąć). I działa to OK, tylko problem w tym że jeśli aplikacja wisi tylko w Tray-u i ktoś klika na ikonkę aplikacji to nic się nie dzieje (nie otwiera nowego okna ani nie pokazuje starego). A chcemy, żeby ten "drugi" proces nim zostanie zamknięty wymusił w jakiś sposób otwarcie głównego okienka należącego do "pierwszego" procesu.
Prostym i niezwykle wydajnym rozwiązaniem jest użycie w tym przypadku MemoryMappedFile. Należy on do przestrzeni System.IO i pełni dwie funkcje:
- Zapewnia "obróbkę" w pamięci bardzo dużych plików
- Umożliwia dostęp do danego obszaru pamięci wielu procesom
Nam chodzi oczywiście o to drugie zastosowanie. W moim przypadku rozwiązanie problemu polega na implementacji dwóch kroków:
- Ustawienie przez proces "zamykany" wartości w pamięci informującej o konieczności uruchomienia okna aplikacji
- Sprawdzanie przez główny proces wartości tej pamięci i w przypadku zmiany odpalenia okna
Implementację pierwszego kroku prezentuje fragment kodu:
private bool SingleInstanceCheck()
{
bool created;
_singleInstanceMutex = new Mutex(true, "VU.ClientGUI.Mutex", out created);
if (!created)
{
var mappedFile = System.IO.MemoryMappedFiles.MemoryMappedFile.CreateOrOpen("ShowWindow", 1);
using (var access = mappedFile.CreateViewAccessor(0,1))
{
access.Write(0, true);
}
Shutdown();
return false;
}
return true;
}
W tej funkcji mamy najpierw sprawdzanie istnienia mutexu a przypadku kiedy on istnieje następuje ustawienie wartości pamięci. Obiekty typu MemoryMappedFile są identyfikowane prosto po nazwie, przy ich inicjalizacji zawsze musimy podać ich oczekiwany rozmiar. W naszym przypadku używamy najprostszego możliwego typu Boolean, który oczywiście zajmuje tylko 1 bajt.
Jeśli chodzi o implementację drugiego kroku, to musimy zapewnić mechanizm okresowego sprawdzania zawartości pamięci. Ja zastosowałem najprostsze rozwiązanie: osobny wątek który to robi:
var thread = new Thread(new ThreadStart(() =>
{
var file = System.IO.MemoryMappedFiles.MemoryMappedFile.CreateOrOpen("ShowWindow", 1);
var accessor = file.CreateViewAccessor(0, 1);
while (true)
{
if (accessor.ReadBoolean(0))
{
if (ShowMainWindowRequest != null)
{
ShowMainWindowRequest(this, null);
accessor.Write(0, false);
}
}
Thread.Sleep(200);
}
}));
thread.Start();
Jak widać, pobieramy zawartość pamięci korzystając z tej samej nazwy co w kroku 1. Po pobraniu wartości i sprawdzeniu, że jest true delegujemy do wątku GUI zadanie pokazania istniejącego okna. W przypadku WPF taki zadanie wygląda tak:
application.ShowMainWindowRequest += (o, e) => Dispatcher.Invoke((Action)Show);
Metoda Show() to standardowa metoda Window która oczywiście wyświetla okno.
Jak widać, użycie MemoryMappedFile jest niezwykle proste a może rozwiązać całkiem sporą gamę problemów :)