0195d - Pascal potrafi ?

Przeglądając numer 10/94 PCkuriera natknąłem się na ciekawy artykuł pana Marka Wierzbickiego pod tytułem "Pascal potrafi". Przedstawiono w nim "pozornie nierozwiązywalny w Pascalu" problem zabezpieczenia przed wielokrotnym uruchomieniem tej samej aplikacji w środowisku Windows.

Do rozwiązania autor wykorzystał bibliotekę Object Windows. Za jej pomocą zaimplementował obsługę próby wielokrotnego uruchomienia tej samej aplikacji, polegającą na przełączeniu Windows na uruchomioną wcześniej jej kopię. Propozycja ta jest bardzo interesująca i zasługuje na uwagę, jednakże z lektury artykułu pana Wierzbickiego można wyciągnąć złe wnioski. Po pierwsze, przedstawione i podobne rozwiązania są zupełnie odmienne w implementacjach za pomocą języka Pascal i języka C (co wynika ze wstępu artukułu). Po drugie, nie istnieje łatwy sposób napisania podobnego rozwiązania bez wykorzystania biblioteki OWL i metody InitMainWindow jej obiektu TApplication. Chęć szerszego przybliżenia wymienionych problemów skłoniła mnie do napisania niniejszego artykułu.

Architekci MS Windows przewidzieli uruchamianie wielu kopii tej samej aplikacji, a każdą z nich nazwali jej instancją. Z całą pewnością jest to zaleta systemu: można bowiem na przykład jednocześnie edytować dwie mapy bitowe w dwóch instancjach Paintbrusha, który nie pozwala na równoległe edytowanie wielu obrazków. Z uwagi na oszczędność pamięci tylko w przypadku ładowania pierwszej instancji rezerwowane są obszary pamięci na segment kodu i segment danych oraz rejestrowana jest w systemie klasa okna aplikacji. Przy próbie uruchomienia kolejnej instancji system rezerwuje tylko pamięć na segment danych, zapewniając wspólne użytkowanie obszaru pamięci, w którym znajduje się segment kodu aplikacji. Wynika to z faktu, że zawartość segmentu kodu nie ulega zmianom w czasie wykonywania programu, a co za tym idzie, wszystkie kopie mogą korzystać ze wspólnego segmentu kodu. Podobnie tworzone jest okno kolejnej instancji - powstaje ono na podstawie klasy zarejestrowanej już w systemie przez pierwszą instancję. W wyniku zamknięcia wszystkich instancji aplikacji następuje zwolnienie obszaru zajmowanego przez segment kodu oraz automatyczne wyrejestrowanie klasy okna aplikacji z systemu.

Podczas gdy zarządzanie segmentami spoczywa wyłącznie na systemie, to za rejestrację klas okien aplikacji odpowiada sam programista. Od systemu MS Windows otrzymuje on dwie informacje: oznacznik instancji uruchamianej aplikacji oraz oznacznik poprzedniej instancji aplikacji, który przyjmuje wartość zerową, jeżeli uruchamiana jest pierwsza instancja. Programista musi zapewnić przeprowadzenie rejestracji klasy okna aplikacji tylko dla pierwszej jej instancji, czyli wówczas, gdy oznacznik poprzedniej instancji ma właśnie zerową wartość. Rejestracja klasy okna jest więc stałym elementem programu pracującego w środowisku Windows. Różna jest tylko jej implementacja, wynikająca z budowy języka programowania, jaki wykorzystano do tworzenia programu. Przekazywanie wartości obu omówionych oznaczników instancji jest mechanizmem systemowym i niezależnym od języka programowania czy biblioteki, z pomocą której będzie pisana aplikacja.

W języku C oznacznik uruchamianej instancji i oznacznik poprzedniej instancji są odpowiednio pierwszym i drugim parametrem funkcji WinMain. Funkcja ta jest bowiem pierwszą wywołaną funkcją programu. Oba omówione parametry są typu HINSTANCE. W języku Pasacal podobna funkcja nie istnieje, a oba oznaczniki są zmiennymi znajdującymi się w module SYSTEM, o identyfikatorach odpowiednio: HInstance, oraz HPrevInst. Obie zmienne są typu Word, a ich wartości ustala MS Windows. Programista zmuszony jest do kontroli wartości oznacznika poprzedniej instancji i wykonania lub nie, w zależności od jego wartości, rejestracji klasy okna. Równolegle można zrealizować zabezpieczenie przed wielokrotnym uruchomieniem aplikacji. W przypadku, gdy wartość oznacznika poprzedniej instancji nie jest zerem - nie następuje tworzenie i wyświetlenie nowego okna aplikacji, lecz przełączenie na uruchomioną wcześniej instancję.

Wykorzystując bibliotekę OWL (jest ona identyczna w C i Pascalu), programista zostaje uwolniony od konieczności kontroli oznacznika poprzedniej instancji w celu rejestrowania klasy okna aplikacji dla pierwszej jej instancji. Operację tę wykonują za niego obiekty biblioteki. Przykrywając metodę InitMainWindow obiektu dziedziczącego z TApplication i kontrolując oznacznik poprzedniej instancji, programista może również zrealizować omawiane zabezpieczenie, tak jak przedstawił to pan Marek Wierzbicki w swoim artykule.

Dla porównania sposobu obsługi oznaczników instancji w celu rejestracji klas okien oraz implementacji zabezpieczenia przed wielokrotnym uruchomieniem aplikacji, na wydrukach zostały przedstawione identyczne rozwiązania programowe w języku Microsoft/Borland C i Borland Pascal. Programy nie wykorzystują modułów biblioteki OWL, co pozwoliło na dokładne przedstawienie zasad obsługi oznaczników instancji. Oba programy nie wykonują żadnych działań oprócz uruchomienia okna aplikacji. W przypadku próby uruchomienia aplikacji po raz drugi nastąpi przełączenie na aplikację uruchomioną wcześniej.

W funkcji/procedurze WinMain następuje sprawdzenie, czy oznacznik poprzedniej instancji jest równy zero. W programie napisanym w C jest to wspomniany wcześniej drugi parametr funkcji WinMain, w programie napisanym w Pascalu - zmienna HPrevInst. Jeżeli tak, to następuje przypisanie wartości komponentom klasy okna, próba jej rejestracji w systemie, utworzenie i wyświetlenie okna na podstawie zarejestrowanej klasy oraz obsługa pętli komunikatów. W przeciwnym wypadku - jeżeli oznacznik poprzedniej instancji nie jest równy zero - istnieje już uruchomiona aplikacja w systemie, nie dojdzie zatem do utworzenia i wyświetlenia na podstawie zarejestrowanej klasy nowego okna kolejnej instancji, lecz zostanie uaktywnione okno uruchomionej już aplikacji. Uaktywnienie to realizowane jest przez funkcję SetActiveWindow. Deskryptor okna przeznaczonego do uruchomienia zwraca funkcja FindWindow [1].

Jak widać na przedstawionych przykładach rozwiązanie problemu jest identyczne w obu językach, z wyjątkiem różnic wynikających z samej ich budowy. Obsługa oznaczników instancji jest niezależna od jakiegokolwiek języka, wynika z mechanizmów systemu MS Windows. Sposób obsługi oznaczników instancji przez programistę jest taki sam jak w programach wykorzystujących bibliotekę OWL, gdzie rejestracja klasy okna może odbywać się bez udziału programisty.

Literatura :

[1] Marek Wierzbicki, "Pascal potrafi.", PCkurier 10/94, str.

 Listing 1 
        
 #pragma argsused
 long FAR PASCAL _export ProceduraOkna(HWND hOkno,
      WORD wKomunikat, WORD wParametr, LONG lParametr)
 {
   switch(wKomunikat) 
   {
     case WM_DESTROY: 
       PostQuitMessage(0);
       break; 
       
     default: 
       return DefWindowProc(hOkno, wKomunikat,
                            wParametr, lParametr); 
   } 
   return 0L; 
 } 
 
 #pragma argsused 
 int PASCAL _export WinMain(HINSTANCE hOznacznikInstancji,
                            HINSTANCE hPoprzedniOznacznik,
                            LPSTR lpszLiniaPolecenia, int nStanOkna) 
 { 
   WNDCLASS wndKlasaOkna; 
   MSG msgKomunikat; 
   HWND hOkno; 
   
   if(!hPoprzedniOznacznik) 
   { 
     wndKlasaOkna.style = CS_HREDRAW | CS_VREDRAW; 
     wndKlasaOkna.lpfnWndProc = ProceduraOkna; 
     wndKlasaOkna.cbClsExtra  = 0; 
     wndKlasaOkna.cbWndExtra  = 0; 
     wndKlasaOkna.hInstance   = hOznacznikInstancji; 
     wndKlasaOkna.hIcon   = LoadIcon(NULL, IDI_APPLICATION); 
     wndKlasaOkna.hCursor = LoadCursor(NULL, IDC_ARROW); 
     wndKlasaOkna.hbrBackground = GetStockObject(WHITE_BRUSH); 
     wndKlasaOkna.lpszMenuName  = NULL; 
     wndKlasaOkna.lpszClassName = "Klasa"; 
     
     if(!RegisterClass(&wndKlasaOkna)) return FALSE; 
     
     hOkno = CreateWindow("Klasa", "Aplikacja", WS_OVERLAPPEDWINDOW, 
          CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
          NULL, NULL, hOznacznikInstancji, NULL); 
          
     ShowWindow(hOkno, nStanOkna); 
     UpdateWindow(hOkno); 
     
     while(GetMessage(&msgKomunikat, NULL, 0, 0)) 
     { 
       TranslateMessage(&msgKomunikat); 
       DispatchMessage(&msgKomunikat); 
     } 
   }
   else SetActiveWindow(FindWindow("Klasa", "Aplikacja")); 
   return 0; 
 }

--- * --- * ---

 Listing 2

 uses WinTypes, WinProcs;

 function ProceduraOkna(hOkno: HWnd; wKomunikat, wParametr: Word;
                        lParametr: Longint): Longint; export;
 begin 
   ProceduraOkna := 0; 
   
   case wKomunikat of 
     wm_Destroy: begin 
       PostQuitMessage(0); 
       Exit; 
     end; 
   end; 
   
   ProceduraOkna := DefWindowProc(hOkno, wKomunikat,
                               wParametr, lParametr); 
 end; 
 
 procedure WinMain; 
 var wndKlasaOkna : TWndClass; 
     msgKomunikat : TMsg; 
     hOkno : HWnd; 
 begin 
   if HPrevInst = 0 then 
   begin 
     wndKlasaOkna.style := cs_HRedraw or cs_VRedraw; 
     wndKlasaOkna.lpfnWndProc := @ProceduraOkna; 
     wndKlasaOkna.cbClsExtra  := 0; 
     wndKlasaOkna.cbWndExtra  := 0; 
     wndKlasaOkna.hInstance   := HInstance; 
     wndKlasaOkna.hIcon   := LoadIcon(0, idi_Application); 
     wndKlasaOkna.hCursor := LoadCursor(0, idc_Arrow); 
     wndKlasaOkna.hbrBackground := GetStockObject(white_Brush); 
     wndKlasaOkna.lpszMenuName  := nil; 
     wndKlasaOkna.lpszClassName := 'Klasa'; 
     
     if not RegisterClass(wndKlasaOkna) 
     then Halt(255); 
     
     hOkno := CreateWindow('Klasa', 'Aplikacja', ws_OverlappedWindow,
       cw_UseDefault, cw_UseDefault, cw_UseDefault, cw_UseDefault,
       0, 0, HInstance, nil); 
          
     ShowWindow(hOkno, CmdShow); 
     UpdateWindow(hOkno); 
     
     while GetMessage(msgKomunikat, 0, 0, 0) 
     do begin 
       TranslateMessage(msgKomunikat); 
       DispatchMessage(msgKomunikat); 
     end; 
   end else 
     SetActiveWindow(FindWindow('Klasa', 'Aplikacja')); 
 end; 
 
 begin 
   WinMain; 
 end.

Dariusz Bojanowicz 
PC Kurier, Styczeń 1995r.