W czasach kiedy rozpoczynałem swoją zabawę z C++ (a w gruncie rzeczy żadnym wielkim wygą nie jestem ;]) takie rzeczy starano się robić w WinAPI z wykorzystaniem ShellExecute, czy SendMessage. Bo jak sam temat mówi wpis ten będzie dotyczył systemu Windows. Wraz z rozwojem języków i środowisk sporą część tychże funkcji można wykorzystać poprzez łatwiejsze w użyciu zamienniki.
Tematem dzisiejszego wykładu jest poruszony na pewnym forum problem uruchamiania zewnętrznego programu z aplikacji napisanej w C++ (bonusowo na końcu wpisu zamieszczę także klasę tego typu pisaną w C#, a ponieważ tłumaczenia rozwiązania w obydwu językach by się pokrywały to opis będę opierał na C++). Ponieważ idziemy z duchem czasu, a bodajże chodziło tam o system Windows to postaram się przybliżyć Wam proste i kompleksowe rozwiązanie działające w Visual C++ .NET.

Nie będziemy sobie w tym wypadku zaprzątać głowy natywnym Win32. Projekt stworzymy w trybie okienkowym jako Windows Forms. Do dzieła więc! Kolejno po wejściu do środowiska należy w menu wybrać File (Plik) -> New (Nowy) -> Project… (Projekt…). W nowym okienku należy wybrać Visual C++ -> CLR -> Windows Forms Application (Aplikacja Windows Forms). Wystarczy jeszcze powyżej ustawić z listy .NET Framework 2.0 (wyższe wersje też są dozwolone, ale w gruncie rzeczy niepotrzebne dla tego co będziemy starali się osiągnąć), a także na samym dole wybrać nazwę nowego programu (Name/Nazwa:, w przykładzie będzie to process_starter) i ewentualnie zmienić miejsce zapisu projektu (Location/Lokacja:). Niektórych może zdziwić nazwa przykładowego projektu, ale fakt jest taki, że otwieranie i zamykanie programów w naszym przypadku będzie odbywać się w głównej mierze na procesach systemu.

Automatycznie został wygenerowany domyślny kod wraz z klasą formularza. Ponieważ projekt całego rozwiązania jest prosty, pozwolicie że zacznę od krótkiego wytłumaczenia części okienkowej. Dla przykładu będziemy potrzebować dwóch textbox’ów (nazwanych tutaj filenameValue i argsVal) i jednego button’a (makeButton). Nie wymieniam tu użytych label’ów, czy progressbar’a, bo to tylko części estetyczna.

Teraz możecie kliknąć dwa razy na wspomniany już przycisk i przechodzimy do partii kodu.
Pora więc na trochę teorii, czyli krótki opis jak będziemy wywoływać i niszczyć procesy. Skorzystamy tu z przestrzeni nazw, wraz z klasą przeznaczoną do tego typu rzeczy.
using namespace System::Diagnostics::Process;
Poprzez odpowiednie użycie jej elementów i metod możemy bez problemu uruchomić zewnętrzny program, a później go zabić bez wprowadzania dodatkowych danych jak w przypadku użycia WinAPI. Tyle z teoretyzowania.
Kod który stworzymy proponuję podzielić na trzy oddzielne metody, plus jedna obsługująca zdarzenie przyciśnięcia przycisku. Zajmijmy się najpierw dwoma głównymi:
- runProcess(plik_do_otwarcia, opcjonalne_argumenty_pliku) pozwalająca na otwarcie programu,
- killProcess() zamykająca proces otwartego programu.
W pierwszym z nich przede wszystkim należy wywołać metodę statyczną odpowiadającą za otwarcie pliku. Ponieważ jednak będziemy chcieli go później zamknąć to musimy jej wynik przypisać do zmiennej przechowującej stan procesu. Prócz tego przydałoby się sprawdzać wcześniej czy podana ścieżka do pliku istnieje (z tym wiąże się dodanie przestrzeni nazw System::IO). A co za tym idzie typ naszej metody musimy zmienić na logiczny, gdyż będzie zwracała wartość zależną od tego czy w.w. argument jest prawidłowy.
#using <system.dll> // dodajemy korzystanie z danego rozszerzenia (dla klasy Process i RegEx)
namespace process_starter {
[...]
using namespace System::Data;
using namespace System::IO; // przestrzeń nazw dla File::Exists
using namespace System::Diagnostics; // dodajemy przestrzeń nazw
[...]
private: System::Windows::Forms::Button^ makeButton;
private:
Process^ process; // dodajemy deklarację zmiennej obsługującej stan procesu
[...]
#pragma endregion
/**
* Rozpoczęcie procesu podanego w parametrze proc w wypadku,
* gdy plik podany w parametrze istnieje.
*
* @access: private
* @param: System::String^ proc
* @param: System::String^ args
* @return: bool
*/
bool runProcess(System::String^ proc, System::String^ args)
{
if ( File::Exists(proc) ) // sprawdzenie, czy plik istnieje
{
process = Process::Start(proc, args); // wywolanie zadanego procesu
return true;
}
return false;
}
[...]
Druga metoda opiera się na sprawdzeniu, czy program który wywołaliśmy nie został już czasami zamknięty (np. ręcznie przez użytkownika) i jeśli tak się nie stało na jego zamknięciu. Wszystko zostało opatrzone dodatkowo w wyłapywanie ewentualnych wyjątków. Poniższą funkcję klasy możemy dodać pod tą zajmującą się uruchamianiem.
/**
* Łagodne zabicie procesu programu wywołanego wcześniej w przypadku
* kiedy nie został on zamknięty w międzyczasie przez użytkownika.
*
* @access: private
**/
void killProcess()
{
try {
if ( !process->HasExited ) // sprawdzenie czy proces istnieje
{
process->Refresh(); // pozbycie się scacheowanych informacji o procesie
process->CloseMainWindow(); // zamknięcie procesu
process->Close(); // zwolnienie zasobów związanych z procesem
}
}
catch ( Exception^ e ) // wyłapanie ewentualnego wyjątku
{
MessageBox::Show("Niespodziewany błąd:\n\n" + e->Message, "Błąd!", MessageBoxButtons::OK, MessageBoxIcon::Error);
}
}
Jakby nie patrzeć wszystko co miał poruszać ten wpis zostało już pokazane. Teraz dodatkowo należałoby złożyć to jakoś w całość (czyli nadać ciut życia i funkcjonalności przyciskowi odpowiedzialnemu za interakcję), a także napisać choć podstawowe zabezpieczenia czyniące program „idiotoodpornym”. Tutaj pojawia się trzecia metoda która za pomocą wyrażeń regularnych pozwala na sprawdzenie wprowadzonego adresu pliku z wzorcem. Ten kod także wstawiamy w sekcji private, tak jak powyższe. Dodatkowo należy także dodać przestrzeń nazwa dla klasy RegEx.
[...]
using namespace System::Drawing;
[...]
using namespace System::Text::RegularExpressions; // przestrzeń nazwa wyrażeń regularnych
[...]
/**
* Porównanie ciągu znaków z wprowadzonym wzorcem dzięki wyrażeniom
* regularnym.
*
* @access: private
* @param: System::String^ string
* @param: System::String^ pattern
* @return: bool
*/
bool checkExpression(System::String^ string, System::String^ pattern)
{
Regex^ regex = gcnew Regex(pattern); // tworzymy definicję klasy RegEx
Match^ match = regex->Match(string); // tworzymy definicję klasy Match
if ( match->Value->Length > 0 ) // sprawdzamy, czy ciąg odpowiada wzorcowi
{
return true;
}
return false;
}
No i ostatecznie kwestia nawigacji. Osobiście proponuję zdefiniowanie zmiennej klasy przetrzymującej stan przycisku (czy ma nastąpić uruchomienie, czy zamknięcie). Wg. tego po sprawdzeniu, czy wpisane dane dostępu do pliku nie są puste lub nie są nieprawidłowe wywołujemy odpowiednio, albo metodę wywołującą plik, albo zamykającą go.
[...]
public:
Form1(void)
{
this->button_mode = true; // true, jesli uruchamia program, false jesli zabija
[...]
private:
bool button_mode; // dodajemy deklarację stanu przycisku
[...]
private: System::Void makeButton_Click(System::Object^ sender, System::EventArgs^ e)
{
/**
* Sprawdzenie, czy pole tekstowe odpowiadające za ścieżkę dostępu
* do pliku nie jest puste lub nie jest nieprawidłowo wypełnione.
* Pola argumentów nie jest sprawdzane, gdyż nie są one wymagane.
*/
if ( this->filenameValue->Text != "" && this->checkExpression(this->filenameValue->Text, "(([a-zA-Z]{1}):|)(.*)\\.([a-z0-9A-Z]{1,3})") == true )
{
switch ( this->button_mode )
{
case true:
// wywołanie metody otwierającej plik
if ( this->runProcess(this->filenameValue->Text, ( this->argsVal->Text != "" ? this->argsVal->Text : "" )) == true )
{
this->button_mode = false;
this->makeButton->Text = "Zamknij";
}
else {
MessageBox::Show("Plik nie istnieje!", "Błąd!", MessageBoxButtons::OK, MessageBoxIcon::Error);
}
break;
default:
this->killProcess(); // wywołanie metody zamykającej program
this->button_mode = true;
this->makeButton->Text = "Uruchom";
break;
}
}
else if ( this->filenameValue->Text == "" ) // wyświetlenie ostrzeżenia o niewypełnieniu pola
{
MessageBox::Show("Nie wprowadzono nazwy lub ścieżki pliku!", "Błąd!", MessageBoxButtons::OK, MessageBoxIcon::Warning);
}
else { // wyświetlenie ostrzeżenia o błędnym wypełnieniu pola
MessageBox::Show("Wprowadzono niepoprawną ścieżkę lub nazwę pliku!", "Błąd!", MessageBoxButtons::OK, MessageBoxIcon::Warning);
}
}
Teraz wystarczy tylko skompilować kod i uruchomić program. Wykorzystanie jest chyba proste. W jednym polu tekstowym wpisujemy nazwę pliku jeżeli znajduje się on w katalogu aplikacji lub pełną ścieżkę z nazwą pliku w wypadku, gdy znajduje się on w innym folderze. Drugie pole tekstowe (argumentów) jest niewymagane do uzupełnienia. Można tam umieścić „old schoolowe” parametry elementu docelowego skrótów, bądź spróbować np. podać adres internetowy do otwarcia w przypadku wywoływania przeglądarki. Należy wspomnieć, że w wypadku podania błędnego parametru klasa Process zlekceważy go.
Na koniec kilka przykładowych danych:
- ścieżka: c:\windows\explorer.exe; argumenty: brak,
- ścieżka: c:\program files\mozilla firefox\firefox.exe; argumenty: http://utnij.eu/
PS: zamieszczam gotowy plik exe (kompilowany z C++) i klasa C++ składająca się z powyższego kodu. Na życzenie dodaję także plik wykonywalny (kompilowany z C#) oraz klasę pisaną w c# (po ściągnięciu tego pliku jego rozszerzenie należy zmienić z .h na .cs).
Zapraszam do zapoznania się także z powiązanymi artykułami:
Wpis ten został opublikowany dnia:
sobota, 6 Wrzesień 2008 o godzinie 2:11
w działach C++, Visual C#, Webhosting.
Możesz śledzić rozwój tematu, w tym odpowiedzi dla tego artykułu poprzez kanał informacyjny RSS 2.0.
Możesz także zostawić swój komentarz lub trackbackować ze swojej własnej strony.
Fajny arcik, ale ze względu, że nie piszę w C++, a przynajmniej tym okienkowym, fajnie jak byś to przełożył na C# :D A z pewnych, źródeł słyszałem, że jest taka opcja :P
Aha, jeszcze pytanie: kiedy pojawi się dalsza część artu 10 punktów podstaw teoretycznego tworzenia stron? dotycząca bezpieczeństwa? :>
Pozdrawiam,
gruch4
Przepiszę to na C#. Postaram się jutro. Ale jak już to zamieszczę jedynie klasę. Nie ma sensu opisywać drugi raz praktycznie tego samego.
Co do kolejnej części wpisu o tworzeniu stron. Był tam właśnie komentarz żebym coś skrobnął o zabezpieczeniach. I nawet zacząłem pisać. Leży to w nieopublikowanych artykułach bo nie skończyłem. Nawet jeśli chciałbym poruszyć to w formie kompleksowej to spodziewaj się, że będzie to długi art. Dlatego musisz mi dać trochę czasu.
Co do samego PHP i językach współużytkowanych. Jak to widzisz/widzicie? Mam starać się pisać cały kurs od podstaw (zagadnienie książki znam i nie przeczę, że jest to kuszące)? Czy jakieś konkretne zagadnienia? Może macie jakieś o których chcielibyście się czegoś dowiedzieć?
Pozdrawiam.
Co do artu o zabezpieczeniach, to czekam z niecierpliwością :D
Moim zdaniem nie ma sensu pisać kolejnego kursu/książki od podstaw. I nie mówię, że znam podstawy, czy tam może trochę więcej, ale takich kursów jest pełno.
Od podstaw można zacząć bardziej zaawansowane zagadnienia. Ja np. bym sobie z chęcią poczytał o obiektowym PHP, o wyrażeniach regularnych w PHP, o wspomnianych zabezpieczeniach, jakiś fajny art o PHP i MySQL też by się przydał :)
Więcej nie wymieniam, bo wiem, jak stoisz z czasem :)
Pozdrawiam,
gruch4
Właśnie mi chodzi, żebyście konkretnie wymienili jak najwięcej. Będę miał większy wybór co napisać. I z biegiem czasu większość powinna się pojawić (a za jego braku – czasu – możesz podziękować PP ;]).
Z lekkim poślizgiem, ale umieściłem dodatkową klasę pisaną w Visual C# .NET oraz skompilowany do exeka cały przykładowy program.
lol shellexecute i sendmessage – do tego używa się CreateProcess :D
ale za to ShellExecute jest dobrą metodą na odpalenie strony w dom. przeglądarce lub dom. klienta poczt.
kod w c# jest niemal identyczny
pozdr.
CreateProcess ma ciut więcej ograniczeń i plus w postaci ExitProcess() ;] Ale także ShellExecute można się bez problemu posługiwać, nie ma nigdzie wymogu, że trzeba używać funkcji Windowsowskiego Shella, czy usług. Jedynie WinExec() jest przestarzałe. Więc ja kto woli, ja wypisałem dwie przypadkowe z tego zestawu.
Co do C#, wspomniałem o tym ;] Czemu miałby nie być? :>
Proste uruchamianie i zamykanie zewnętrznych programów w Visual C++/C# .NET?…
Dziękujemy za publikację – Trackback z dotnetomaniak.pl…