Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: 'Złośliwiec'
Biblioteki C++

Jedna instancja

[lekcja] Artykuł opisuje w jaki sposób utworzyć mechanizm zapobiegający uruchomieniu więcej niż jedną instancję aplikacji.
Jak wiadomo, naszą aplikację można uruchamiać wiele razy pod rząd – tak długo, aż wyczerpią się zasoby systemowe. Wszystko w porządku, dopóki jest ona stosunkowo mała, ale kiedyś może urosnąć do niebotycznych rozmiarów, tak że już próba uruchomienia jej drugiej instancji zakończy się czymś niemiłym. Zresztą nie tylko rozmiary aplikacji decydują o tym, czy będzie się dało stworzyć drugą instancję. Może ona na przykład wykonywać częste zapisy i odczyty z pliku w trybie wyłączności – tak, że druga instancja nie będzie mogła się do tego pliku dostać (albo zrobi to i uszkodzi jakieś dane). Możemy też stworzyć aplikację typu klient–serwer, do której trzeba będzie się logować. Ponieważ na ogół nie pozwala się zalogować jednemu użytkownikowi więcej niż raz, więc w celu uniknięcia nieporozumień dobrze byłoby ograniczyć możliwość uruchamiania więcej niż jednej instancji klienta.

We wszystkich tych przypadkach można sprawdzić, czy jakaś instancja naszej aplikacji jest już uruchomiona i jeśli okaże się, że jest, podjąć jakieś dalsze kroki. Do wyboru mamy kilka opcji. Możemy wyświetlić wiadomość, że jedna instancja już istnieje i zakończyć bieżącą instancję lub tamtą "starą" instancję (to pierwsze będzie zapewne łatwiejsze i zwykle właśnie takie rozwiązanie jest wybierane). Możemy też nic nie wyświetlać, tylko odnaleźć główne okno tamtej działającej instancji, wyrzucić je na pierwszy plan (bo jeśli użytkownik próbował odpalić aplikację po raz kolejny, to prawdopodobnie nie wiedział, że już jest odpalona, bo jej okno było ukryte), przekazać temu oknu fokusa i wreszcie zakończyć bieżącą instancję.

Ani wyświetlanie wiadomości, ani pokazywanie / aktywowanie / fokusowanie okien nie jest zbyt wielkim wyzwaniem, więc skupmy się na samym wykryciu, czy nasza aplikacja jest już odpalona. Najpopularniejsze są tu dwa podejścia.

Wyszukiwanie okna

Nasze główne okno zwykle ma w pasku tytułowym jakiś charakterystyczny tekst, najczęściej jest to nazwa programu. Jeśli jest w miarę unikalna i nie zmienia się w trakcie działania aplikacji, to możemy wyszukać ją za pomocą FindWindow. Jeśli znajdziemy okno o takim tytule zanim jeszcze utworzymy je w bieżącej instancji, to będzie prawdopodobnie znaczyło, że jedna instancja jest już uruchomiona.

Funkcja FindWindow przyjmuje dwa parametry: nazwa klasy okna i tytuł okna. Możemy podać dowolny jeden z nich bądź obydwa naraz. Nazwa klasy jest na pewno unikalna, więc najlepiej jest korzystać właśnie z niej. Wyszukiwanie po tytule może być kłopotliwe, jeśli ten zmienia się u nas w czasie działania programu:
C/C++
WNDCLASSEX wcex;

wcex.cbSize = sizeof( WNDCLASSEX );
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_WINTEST3 ) );
wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
wcex.hbrBackground =( HBRUSH )( COLOR_WINDOW + 1 );
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "SomeFancyWindowClass";
wcex.hIconSm = LoadIcon( wcex.hInstance, MAKEINTRESOURCE( IDI_SMALL ) );

ATOM temp = RegisterClassEx( & wcex );

HWND hOther = FindWindow( wcex.lpszClassName, NULL );
if( hOther )
{
    MessageBox( NULL, "Ta aplikacja jest już uruchomiona.",
    "WindowsTest", MB_ICONEXCLAMATION );
    PostQuitMessage( 0 );
    return 0;
}

hwnd = CreateWindow( wcex.lpszClassName, "Windows Test",
WS_OVERLAPPEDWINDOW | WS_HSCROLL, CW_USEDEFAULT, CW_USEDEFAULT,
800, 600, NULL, NULL, hInstance, NULL );
W przykładzie powyżej rejestrujemy klasę okna jak zwykle, po czym sprawdzamy, czy ktoś już przypadkiem nie stworzył okna tej klasy. Jeśli tak jest, to FindWindow zwróci nam uchwyt do tego okna, co będzie dla nas oznaczało, że inna instancja już istnieje, wobec czego powinniśmy wyświetlić wiadomość i zakończyć bieżącą instancję przez PostQuitMessage. W przeciwnym wypadku po prostu tworzymy okno i kontynuujemy normalny tok działania aplikacji.

Mutex

Jeśli z jakichś powodów nie możemy użyć FindWindow, jest jeszcze jedna popularna sztuczka pozwalająca stwierdzić, czy instancja naszej aplikacji została już odpalona. Opiera się ona na wykorzystaniu muteksów. Są to generalnie obiekty służące do synchronizacji wątków i podobne w działaniu do sekcji krytycznych, ale przydatne bywają też w innych celach. Mutex ma tę właściwość, że jeśli nadamy mu nazwę (bo mogą być też muteksy bezimienne), to żaden inny proces (w tym również inna instancja tej samej aplikacji) nie będzie mógł stworzyć drugiego muteksa o tej nazwie. Co doskonale pasuje do naszego celu:
C/C++
HANDLE hMutex = CreateMutex( NULL, FALSE, "MyCoolMutex" );
if( hMutex && GetLastError() != 0 )
{
    MessageBox( NULL, "Ta aplikacja jest już uruchomiona.",
    "WindowsTest", MB_ICONEXCLAMATION );
    PostQuitMessage( 0 );
    return 0;
}
Jeśli uruchomimy aplikację po raz pierwszy, mutex po prostu się stworzy. Drugie uruchomienie spowoduje wprawdzie, że zmienna hMutex będzie zawierała prawidłowy uchwyt, ale będzie to uchwyt muteksa utworzonego przez tę pierwszą instancję, zaś GetLastError zwróci nam kod błędu ERROR_ALREADY_EXISTS. Wówczas przyjmujemy, że inna instancja już działa, wyświetlamy nasz komunikat i kończymy, tak samo jak w poprzednim przykładzie.
Poprzedni dokument Następny dokument
Różności Kolory w konsoli