<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel><title>Hery's devlog</title><link>http://pdziepak.quarnos.org/</link><description>Wpisy z dziennika internetowego Jogger, wspomaganego przez Jabbera</description><lastBuildDate>Fri, 30 Jul 2010 15:10:15 +0200</lastBuildDate><generator>JoggerPL</generator><item><title>The ancient Blob</title><link>http://pdziepak.quarnos.org/2009/11/10/the-ancient-blob/</link><description>FreeSpace is a 10-year-old game and despite the fact SCP developers have been doing great work since 2002 there are still many parts of code that were left unchanged. That means there is a great variety of antipatterns. Some of them are being solved while the others are so complex that their refactorization will require large amount of work. One of these antipatterns, easily found in FSO, is The Blob.
The Blob Class is a class that has a huge number of members and is given many, loosely related responsibilities. It is a procedural design, therefore, it is considered as antipattern and an existence of such thing in any object-oritented code is a problem. More elaborated description of The Blob is available here.
FreeSpace used to support both DirectX and OpenGL, after 2002 the majority of new features was implemented only in OpenGL thus DirectX code became outdated soon and eventually was removed from the code. Unfortunately, the original mechanism that allowed the engine to support two APIs is still present in the code.
In OOP the best way to design the code that implements the one interface in two different ways is to take advantage from virtual member functions. The original FreeSpace developers did the same, but without the &quot;object-oriented&quot; part. They created structure screen that consist of over 100 function pointers (the whole structure has 277 lines of code). Actually, they implemented some kind of virtual functions table that was filled at start-up with the addresses of appropriate functions.
What's more, this structure also stores data required by rendering functions, such as screen resolution, fog settings, alpha blending configuration, etc. In addition to that, it still grows, new graphics functions are being added to the list. The situation, probably won't improve until somebody rewrites a large amount of graphics code in OOP manner.
{geshi lang=&quot;c&quot;}
typedef struct screen {
	uint	signature;
	int	max_w, max_h;

	/* lots of things here */

	bool recording_state_block;
	int current_state_block;

	void (*gf_start_state_block)();
	int (*gf_end_state_block)();
	void (*gf_set_state_block)(int);

	//switch onscreen, offscreen
	void (*gf_flip)();

	/* lots of things here */

	void (*gf_gradient)(int x1, int y1, int x2, int y2, bool resize);
 
	void (*gf_circle)(int x, int y, int r, bool resize);
	void (*gf_curve)(int x, int y, int r, int direction);

	/* lots of things here */

	void (*gf_render_buffer)(int, int, ushort*, uint*, int);
	int	 (*gf_make_flat_buffer)(poly_list*);
	int	 (*gf_make_line_buffer)(line_list*);
	
	/* lots of things here */
} screen;
{/geshi}It is not surprising that ten years ago programmers didn't thought about OOP and used C to create a computer game. In procedural design such structure is not a &quot;pure evil&quot; as much as in object-oriented design. Unfortunately, nobody bothered to improve that part of FSO code and structure screen still scares unsuspecting people.
Nevertheless, such nasty parts of code may be very interesting (especially for the coders that don't remember the &quot;good old days&quot;). They show how much changed the techniques programmers use and give an general idea how much it will change. Procedural programming ten years ago, object-oriented now and something completely new in the next 10 years (maybe something that will evolve from data-driven component design that is more and more popular among game developers).
</description><pubDate>Tue, 10 Nov 2009 16:59:00 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/11/10/the-ancient-blob/</guid><category>Ancient code</category><category>FreeSpace Source Code Project</category><category>Programowanie</category><category>Projekty</category><category>Techblog</category><category>blob</category><category>antipattern</category><category>oop</category><category>procedural</category><category>design</category><category>object</category><category>scp</category><category>fso</category></item><item><title>Post-processing</title><link>http://pdziepak.quarnos.org/2009/11/09/post-processing/</link><description>When I finished adding support of subtractive shaders to FSO engine (described in my previous post) I decided to implement post-processing. It usually doesn't require many changes in existing code. That was good since I wasn't familiar with the majority of FSO source code at that time. The other reason was that it is easy to achieve satisfactory results adding a few very simple post-processing effects. However, most of them will be used only in cutscenes rendered by the engine.

How does it work?
The main idea of post-processing is really simple. The scene has to be rendered to a texture first and then drawn to the back buffer. During the second step various additional effects may be applied, hence the name - these computations are performed when the scene is already rendered. It allows to implement a wide range of effects from the completely useless ones like ASCII-art rendering to things like HDR, depth of field, motion blur, light shafts, etc.


Simple effects
Initially my patch introduced only a few simple effects, that were implemented entirely in shaders code. To work properly they needed nothing but the scene texture and intensity values. Currently four simple effects are supported:

contrast
saturation
film grain
distortion noise

These effects are similar to the filters that can be found in many graphics editors. In fact, they work in exactly the same way - apply appropriate computations to the static 2D image. Exposure, gamma, contrast, hue, saturation and many other properties can be applied without any problems as well as different types of noises, blurs and many other things. However, blur is usually implemented in a bit more complicated way in order to minimize the performance loss.


Effects management
All these effects enlisted above have their entries in post_processing.tbl file in order to give the engine the information it needs to apply and control them. The following is an example entry:

$Name:          saturation
$Uniform:       saturation
$Define:        FLAG_SATURATION
$AlwaysOn:      false
$Default:       1.0
$Div:           100
$Add:           0


Name - general name of the effect used for identification purposes. It is mostly used in FRED. If there are more than one effect with the same name the behaviour is undefined.
Uniform - name of the uniform variable in the shader, that specifies the intensity of the effect.
Define - name of the preprocessor definition that allows disabling and enabling the effect when needed.
AlwaysOn - determines if the effect is always active or applied only when current intensity differs from the default intensity.
Default - the default effect intensity.
Div - divisor
Add - adder

The value of current intensity that is passed to the shader is counted using the equation:
intensity = given_intensity / divisor + adder
When given_intensity is the value set by user in mission code or additional scripts.

In the following posts I'm going to describe other, more advanced effects that are achieved using post-processing. There also will be (already promised) description of the new shaders manager.
</description><pubDate>Mon, 09 Nov 2009 15:34:36 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/11/09/post-processing/</guid><category>FreeSpace Source Code Project</category><category>Game Development</category><category>Projekty</category><category>Techblog</category><category>postprocessing</category><category>gamedev</category><category>freespace</category><category>scp</category><category>fso</category></item><item><title>FreeSpace Source Code Project</title><link>http://pdziepak.quarnos.org/2009/11/06/freespace-source-code-project/</link><description>It has been a long time since my last post here. Many things have changed, most notably the language in which this devlog is written. I considered switching to English a few months ago, when my blog was still active, but I didn't found enough reasons for that.
Now, the situation is different. In September I joined FreeSpace Source Code Project. The main goal of this project is to keep FreeSpace, almost 10-year-old game, up to date, by improving every aspect of the game. It wouldn't be possible if Volition, Inc., the studio that created FreeSpace, hadn't given fans the whole source code in 2002. Since then, it is an open source project, not completely free (it can't be sold or used commercially in any other way), though. The website of SCP is a bit outdated, but you may find many interesting things on one of the biggest FreeSpace forum: HardLight.net or on the Polish FreeSpace website: FreeSpace.pl.
I've started contributing to SCP by improving the way in which shaders source code was stored and managed. There were dozens of *.sdr files containing shaders with different features implemented. The ones with normal mapping, the ones with environment mapping, the ones with glow maps, etc. Each possible combination of feature had to be in a separate file, that's why SCP developers ended up with over 60 (if I recognize correctly) shaders, which shared very much with each other.
I decided to introduce subtractive shader. This technique consists in creating one shader (one vertex shader and one fragment shader to be exact) with all the features implemented. Then according to the current needs parts of the code can be enabled or disabled using GLSL preprocessor instructions #define and #ifdef.
. It was easy to implement in the rendering engine and only a bit harder to create one unified shader. I don't want to describe here in details how shaders are managed in FSO, because it is also improved by my next patch which I will describe soon in my next post.

</description><pubDate>Fri, 06 Nov 2009 18:37:09 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/11/06/freespace-source-code-project/</guid><category>FreeSpace Source Code Project</category><category>Projekty</category><category>freespace</category><category>gamedev</category><category>game development</category><category>shader</category></item><item><title>Very Concurrent Garbage Collection</title><link>http://pdziepak.quarnos.org/2009/03/31/very-concurrent-garbage-collection/</link><description>Jednym z głównych problemów związanych z wykorzystaniem garbage collectora jest możliwość dość drastycznego spadku wydajności w nieoczekiwanych momentach. Z tego powodu odśmiecanie pamięci zwykle nie może zostać zastosowane w systemach czasu rzeczywistego, a także w innych sytuacjach gdzie wydajność programu jest istotna. Twórcy systemu Inferno stworzyli algorytm Very Concurrent Garbage Collection (VCGC), którego zadaniem jest zminimalizowanie tych problemów.
Równoległe przetwarzanie
System Inferno jest systemem rozproszonym. Powoduje to, że programowanie równoległe jest zalecanie i potrafi przynieść dość duży wzrost wydajności. Dlatego też istotą VGCG jest możliwość wykonywania procesu odśmiecania na wielu wątkach. Każdy z trzech elementów garbage collectora typu mark &amp; sweep (jakim jest VGCG) jest całkowicie niezależny od pozostałych i może działać w oddzielnym wątku bez potrzeby używania dodatkowych mechanizmów synchronizacji.
Niezależność poszczególnych operacji związanych z odśmiecaniem pozwala przenieść je na inny procesor dzięki czemu właściwe wykonywanie programu nie jest w żaden sposób dodatkowo spowalniane i działa tak samo wydajnie jak w sytuacji gdy garbage collector nie jest używany.
Mark &amp; sweep
Najpopularniejszym rodzajem algorytmów odśmiecania pamięci jest mark &amp; sweep. Polega on na zlokalizowaniu i oznaczeniu wszystkich używanych obszarów pamięci (czyli takich do których istnieją odwołania) a następnie zwolnieniu pozostałego miejsca. Zwykle garbage collector tego typu składa się z trzech elementów:

mutator - właściwy kod programu, w nim znajduje się kod zajmujący się przydzielaniem pamięci na żądanie aplikacji
marker - wyszukuje i oznacza nieużywane obiekty w pamięci
sweeper - zwalnia obiekty które marker uznał za nieużywane

Epoki
Czas działania programu jest podzielony na epoki. Podczas każdej z nich w osobnych wątkach uruchomione zostają poszczególne elementy garbage collectora (muatator, marker i sweeper). Dodatkowo każdej epoce jest przyporządkowany jeden z trzech kolorów: COLOR(epoch) = epoch mod 3.
Przydzielanie pamięci
Pamięć jest przydzielana przy użyciu free lists. Jest to lista wskazujących na siebie wolnych bloków pamięci. Dzięki temu, że każdy wolny blok zawiera wskaźnik na następny, nie ma potrzeby tworzenia dodatkowych struktur danych do przechowywania tego typu informacji. Każdy przydzielany w ten sposób obiekt jest oznaczony kolorem aktualnie trwającej epoki.
Marker
W każdej epoce marker porusza się po wskazujących na siebie obiektach. Jeżeli kolor obiektu jest inny niż kolor aktualnej epoki marker uaktualnia go a następnie rekursywnie przegląda wszystkie bloki na które wskazuje dany obiekt. Gdy marker zakończy działanie wszystkie używane obiekty mają kolor danej epoki.
Warto zauważyć że mutator i marker w żaden sposób nie wpływają na swoje działanie. Mutator oznacza wszystkie nowe obiekty kolorem aktualnej epoki, a ten jest przez markera ignorowany. Istotny jest także fakt, że marker nigdy nie spotka bloku oznaczonego kolorem innym niż kolor aktualnej, bądź poprzedniej epoki.
Sweeper
Sweeper przegląda listę przydzielonych bloków w poszukiwaniu obiektów oznaczonych kolorem epoki COLOR(epoch-2). Jeśli natrafi na taki obszar pamięci to jest on zwalniany. Jest to operacja całkowicie bezpieczna ponieważ ani marker ani mutator nie mają dostępu do obiektów oznaczonych takim kolorem.
W przypadku natrafienia przez sweeper bloków o innym kolorze są one ignorowane, ponieważ wciąż mogą być w użyciu przez pozostałe elementy programu.
Zakończenie epoki
Gdy sweeper i marker zakończą pracę program przechodzi do kolejnej epoki o nowym kolorze i cały proces zaczyna się od nowa. Dzięki całkowitej niezależności poszczególnych elementów od siebie mutator, marker i sweeper mogą działać równolegle bez potrzeby wprowadzania blokad, co korzystnie wpływa na wydajność systemu.
Podsumowanie
Obecnie bardzo wiele języków programowania oferuje wbudowany garbage collector dlatego rozwój algorytmów tego typu nie jest niczym dziwnym. Dlatego taż warto dowiedzieć się na jakiego rodzaju rozwiązania można w tej dziedzinie natrafić.
</description><pubDate>Tue, 31 Mar 2009 21:38:33 +0200</pubDate><guid>http://pdziepak.quarnos.org/2009/03/31/very-concurrent-garbage-collection/</guid><category>Programowanie</category><category>Systemy operacyjne</category><category>Systemy rozproszone</category><category>Techblog</category><category>grabage collector</category><category>odśmiecanie pamięci</category><category>inferno</category><category>mark and sweep</category><category>mutator</category><category>marker</category><category>sweeper</category></item><item><title>Inferno, Plan 9 i maszyny wirtualne</title><link>http://pdziepak.quarnos.org/2009/03/24/inferno-plan-9-i-maszyny-wirtualne/</link><description>Historia Uniksa sięga końca lat 60 XX wieku. Tymczasem wiele systemów operacyjnych wciąż opiera się na przyjętych w nim, często już nieaktualnych, założeniach. Oczywiście takie systemy jak Solaris czy rodzina *BSD wprowadzają dużo dodatkowych technologii, ale wciąż są w pewien sposób ograniczane przez swoich poprzedników i oparte na nich standardy.
Jest to główną przyczyną powstawania systemów eksperymentalnych. Brak zgodności ze standardami czy brak nacisku na niektóre aspekty (najczęściej jest to wydajność) powodują, że z reguły nie nadają się one do użytku codziennego, czy to jako serwer czy jako desktop. W żaden sposób nie zmniejsza to jednak wartości takich projektów. Są nimi, między innymi rozwijane przez Bell Labs systemy Plan 9 oraz Inferno, które w założeniu mają być następcami Uniksa.
Zasoby i pliki
W systemach Inferno i Plan 9 każdy zasób jest reprezentowany przez plik. Przypomina to założenia projektowe Uniksa ale jest na to kładziony jeszcze większy nacisk. Każde urządzenie, proces, sieć, połączenie sieciowe czy nawet otwarte okno jest osobnym plikiem. Dodatkowo zawartość tych plików może być generowana dynamicznie w zależności od stanu zasobu lub konkretnego klienta.
Wyspecjalizowane sterowniki najczęściej są reprezentowane przez katalog zawierający pliki data i ctl. Pierwszy z nich służy do operacji wejścia-wyjścia, podczas gdy drugi jest używany do konfiguracji i kontroli urządzenia. Innym ciekawym przykładem jest nawiązywanie połączeń TCP, które odbywa się poprzez serię zapisów do odpowiednich plików reprezentujących konkretny sterownik.
Także, wszelkie usługi i serwery są reprezentowane jako pliki. Jeżeli program w celu nawiązania połączenia internetowego potrzebuje adresu IP serwera obsługującego daną domenę otwiera plik reprezentujący usługę DNS i za jego pomocą uzyskuje odpowiednie informacje. Najczęściej dzieje się to poprzez wpisanie do pliku nazwy domeny, a następnie odczytanie adresu IP. To rozwiązanie jest szczególnie wygodne, zwracając uwagę na fakt, że zarówno lokalne i jak i zdalne zasoby są w ten sposób reprezentowane. Wspomniany serwer DNS wcale nie musi działać na tym samym komputerze.
Prawa dostępu do plików pozwalają jednocześnie dokładnie określić prawa dostępu do poszczególnych usług, zasobów i urządzeń. Natomiast przechowywanie wszystkich obiektów (także np. połączeń sieciowych czy procesów) w postaci plików upraszcza wewnętrzną budowę systemu. Jedyny sztywno zapisany interfejs dotyczy samej obsługi systemu plików podczas gdy cała reszta jest oparta na plikach.
Styx
Traktowanie każdego zasobu czy usługi jako osobnego pliku wymaga odpowiedniego protokołu usprawniającego komunikację w systemach rozproszonych. Jego zadanie jest zbliżone do NFS, lecz można wskazać pewne wyraźne różnice. Przede wszystkim NFS jest przystosowany głównie do obsługi zwykłych plików, a nie np. generowanych w procfs. Styx jest przystosowany do obsługi każdego rodzaju plików.
Każde odwołanie się klienta do systemu plików jest tłumaczone na szereg komunikatów protokołu Styx. Dla zapewnienia bezpieczeństwa wspierana jest autoryzacja klientów przy pomocy certyfikatów, a także szyfrowanie transmisji. Jest to mechanizm całkowicie niewidoczny dla aplikacji korzystających z protokołu Styx.
Limbo
Programy dla systemów Plan 9 oraz Inferno są pisane w specjalnie do tego zaprojektowanym języku programowania Limbo. Jest on kompilowany do kodu pośredniego wykonywanego następnie przez maszynę wirtualną Dis. Limbo jest także językiem bezpiecznym oferującym silną kontrolę zgodności typów.
Limbo oferuje także kanały jako mechanizm komunikacji między procesami. Każdy kanał ma ściśle określony typ. Wymiana danych jest synchronizowana: proces, który chce wysłać lub odebrać dane musi czekać na gotowość drugiego procesu. Możliwe jest także utworzenie buforowanych kanałów które nie wymagają blokowania procesów z nich korzystających.
Programy są zbudowane z modułów. Każdy moduł oferuje określony publiczny interfejs. Możliwe jest załadowanie wielokrotnie tego samego modułu z różnymi implementacjami. Kod jest wykonywany przez procesy, które przypominają swoim wyglądem i zachowaniem wątki w innych systemach operacyjnych.
Dis
Do uruchamiania programów napisanych w języku Limbo Inferno używa maszyny wirtualnej Dis. Podczas gdy większość języków programowania tego typu, domyślnie używa stosowych maszyn wirtualnych, Dis jest maszyną rejestrową. Twórcy tłumaczą to nieznacznie wyższą wydajnością oraz łatwością przystosowania maszyny do działania na innej platformie sprzętowej. Uważają, także że maszyna wirtualna powinna jak najbardziej przypominać architektury na których pracuje.
Kod może być przez Dis kompilowany just-in-time lub interpretowany przez odpowiednią bibliotekę. Niezależnie od tego jest on umieszczony w osobnym obszarze pamięci do którego nie można uzyskać dostępu żadną z instrukcji oferowanych przez Dis.
Dane są podzielone na globalne, wspólne dla całego kodu zawartego w danym module oraz lokalne dla danej funkcji, umieszczone na stosie. Dostęp do nich uzyskuje się korzystając z adresów bazowych zawartych odpowiednio w rejestrach mp (module pointer) i fp (frame pointer). Można także podawać bezpośrednie adresy w przestrzeni danych programu, nie korzystając z tych rejestrów.
Dis oferuje zestaw instrukcji podobny do procesorów CISC. Różni się on głównie o wiele wyższym poziomem abstrakcji. Oprócz podstawowych instrukcji takich jak jmp czy mov oferowany jest także zestaw instrukcji wspomagający operacje na tablicach, alokację pamięci, czy ładowanie modułów.
Format plików
Dis obsługuje własny format plików wykonywalnych, stworzony specjalnie po to aby dostosować się do jego szczególnych właściwości. Każdy taki plik wykonywany przez maszynę wirtualną składa się z sześciu części:

nagłówek - zawiera sygnaturę pozwalającą rozpoznać format pliku, a także sprawdzić czy jest podpisany (i ewentualnie zawiera także podpis). Z tej sekcji Dis odczytuje wszystkie wymagania programu odnośnie środowiska w jakim będzie uruchomiony. Możliwe opcje to np. wymuszenie kompilacji JIT lub interpretacji kodu. Inne informacje przechowywane w nagłówku to rozmiary pozostałych sekcji oraz punkt wejścia programu.
kod - jest to sekcja zawierająca ciąg instrukcji maszyny wirtualnej. Każda instrukcja składa się z opcode, sposobu adresowania i trzech argumentów.
sekcja typów - umieszczone są w niej deskryptory umożliwiające lokalizację wszelkich wskaźników i referencji w każdym obiekcie. Każdy deskryptor zawiera mapę bitową w której każdy bit reprezentuje jedno słowo. Jeżeli bit jest ustawiony oznacza to, że dane słowo jest wskaźnikiem na inny obiekt.
dane - ta sekcja zawiera dane dostępne później przy użyciu rejestru mp. Każdy wpis w tej sekcji opisuje rodzaj danych, ich położenie, typ i ewentualnie ilość obiektów.
nazwa modułu
sekcja linków - jest to sekcja używana do dynamicznej konsolidacji modułów. Zawiera informacje o wszystkich eksportowanych funkcjach: nazwę, wskaźnik oraz informację o typie argumentów.

Odśmiecanie
Przechowywane informacje o typach pozwalają na realizację systemu odśmiecania pamięci. W celu polepszenia wydajności składa się on z dwóch mechanizmów. Pierwszy z nich to zwykłe zliczanie referencji - proste i stosunkowo szybkie. Nie nadaje się jednak do usuwania obiektów wzajemnie do siebie odwołujących się. Z tego powodu jest także stosowany algorytm Very Concurrent Garbage Collection (VCGC). Pozwala on na zrównoleglenie procesu odśmiecania i tym samym wykorzystania faktu, że Inferno jest systemem rozproszonym. Dzięki temu, że odśmiecanie nie przerywa działania głównego programu możliwe jest zbudowanie systemu czasu rzeczywistego. Sam algorytm VCGC jest tematem na osobny artykuł. Dis umożliwia wykorzystywanie zliczania referencji, VCGC lub hybrydy obu tych rozwiązań.
Podsumowanie
Inferno i Plan 9 mają za zadanie rozwinąć technologie mogące zastąpić Uniksa. Pod pewnymi względami kontynuują przyjęte w nim założenia (&quot;wszystko jest plikiem&quot;), lecz z drugiej strony maszyna wirtualna jest odmienną technologią wykorzystywaną w wielu nowoczesnych językach programowania. Są to systemy niewątpliwie bardzo ciekawe i warto im się bliżej przyjrzeć.
</description><pubDate>Tue, 24 Mar 2009 21:52:32 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/03/24/inferno-plan-9-i-maszyny-wirtualne/</guid><category>Programowanie</category><category>Systemy operacyjne</category><category>Systemy rozproszone</category><category>Techblog</category><category>inferno</category><category>plan 9</category><category>styx</category><category>dis</category><category>maszyna wirtualna</category><category>bell labs</category><category>limbo</category><category>garbage collector</category><category>odśmiecanie</category><category>pliki</category><category>unix</category></item><item><title>Programowanie aspektowe</title><link>http://pdziepak.quarnos.org/2009/03/17/programowanie-aspektowe/</link><description>Jednym z niepożądanych zjawisk dość często pojawiających się przy tworzeniu aplikacji w oparciu o programowanie zorientowane obiektowo jest nadmierny rozrost metod. Najczęściej muszą one wykonać szereg dodatkowych operacji (sprawdzenie uprawnień, poprawności danych, logowanie czynności, itp.) zanim przystąpią do wykonywania głównego zadania. Często można sobie z tym poradzić uwzględniając takie ewentualności w projekcie aplikacji, zwykle nie jest to jednak idealne rozwiązanie. W tym celu powstała idea programowania zorientowanego aspektowo, które jest przystosowane właśnie do radzenia sobie z tego typu problemami.
Podstawowe założenia
Programowanie zorientowane aspektowo w żadnej swojej formie nie wyklucza programowania obiektowego. Właściwie to jest jego uzupełnieniem dostarczającym dodatkowe elementy pozwalające inaczej spojrzeć na projekt aplikacji. Aspektowe języki programowania pozwalają na wskazanie miejsc w kodzie obiektowym w których mają zostać wykonane dodatkowe operacje. Istnieje wiele implementacji tego, lecz zdecydowanie najbardziej popularny jest AspectJ stworzony dla Javy. Wprowadza on następujące elementy:

pointcut - ogólny opis miejsca przecięcia kodu w którym mają zostać wykonane operacje definiowane przez aspekt. W większości sytuacji jest to wywołanie lub wykonanie kodu funkcji składowej, ale możliwe jest także zdefiniowanie pointcuta przy każdej operacji wykonanej przez metody w danej klasie. Pointcuty mogą także dotyczyć każdego dostępu do pola lub tworzeniu obiektu.
joinpoint - konkretne miejsce przecięcia kodu w którym zostaną wstawione dodatkowe operacje definiowane przez dany aspekt.
advice - definiuję operację jaka ma zostać wykonana w joinpointach. Kod opisany w advices jest najczęściej wstawiany przed lub po wywołaniu metody w klasie, ale także w przypadku rzucenia wyjątku lub oryginalna funkcja składowa zostaje całkowicie przez niego zastąpiona.

W przypadku AspectJ a także wielu innych języków aspekt jest traktowany jak klasa. Oprócz wyżej opisanych elementów może zawierać funkcje składowe lub pola. Mogą być tworzone ich instancje, ale najczęściej aspekt jest singletonem. Dodatkowo można utworzyć hierarchię aspektów korzystając z dziedziczenia. Wiele implementacji pozwala także na istnienie abstrakcyjnych aspektów oraz wirtualnych pointcutów.
Realizacja
W przypadku Javy i AspectJ aspekty są realizowane w czasie kompilacji z niewielkim wsparciem refleksyjności dzięki czemu nie wpływają w dużym stopniu na wydajność. W przypadku AspectC++ w związku brakiem wsparcia dynamicznego metaprogramowania ze strony C++ kod aspektowy (napisany w AspectC++) jest najpierw tłumaczony do kodu obiektowego (C++) i dopiero wtedy kompilowany. Bibliotek wprowadzająca aspekty w C# LOOM.NET wykonuje wszystkie operacje z tym związane podczas działania programu, podobnie rzecz się ma w przypadku modułu Perla Aspect. Generalnie sposób realizacji jest inny dla różnych bibliotek i także powinien być świadomym wyborem ponieważ, może w dość dużym stopniu wpłynąć na wydajność programu.
Zastosowanie
Najbardziej oczywistymi zastosowaniami programowania aspektowego to wszelkiej maści loggery i narzędzia diagnostyczne. Można także użyć ich do kontrolowania zgodności otrzymanych danych z kontraktami, ale należy pamiętać, że wiele zalet aspektów jest zauważalna dopiero wtedy gdy odnoszą się do tych samych operacji wykonywanych w wielu metodach. Przykładem może być realizacja wzorca Obserwator, gdzie każda zmiana stanu obiektu obserwowanego wymaga poinformowania o niej obiekty obserwujące. W tym przypadku najlepszym rozwiązaniem jest wstawienie po wywołaniu każdej metody (nie oznaczonej jako const) klasy obiektu obserwowanego wywołania metody uaktualniającej obiekty obserwujące.
Przykład
Warto przedstawić prosty przykład użycia aspektów w realizacji wzorca Obserwator. Zwykłe podejście wymagałoby czegoś podobnego do kodu poniżej:
{geshi lang=&quot;java&quot;}
class Obserwowany {
	// ...
	public void metoda1(int wartość) {
		// ... operacje ...

		uaktualnijObserwatorów();
	}

	public void metoda2(string tekst) {
		// ... operacje ...

		uaktualnijObserwatorów();
	}
}
{/geshi}W przypadku AspectJ kod realizujący te same funkcje mógłby wyglądać następująco:
{geshi lang=&quot;java&quot;}
class Obserwowany {
	// ...
	public void metoda1(int wartość) {
		// ... operacje ...
	}

	public void metoda2(string tekst) {
		// ... operacje ...
	}
}

aspect WzorzecObserwator {
	pointcut operacja() : execution(public * Obserwowany.*(..));
	after() : operacja() {
		uaktualnijObserwatorów();
	}
}
{/geshi}Ten przykład bardzo dobrze prezentuje główne założenie aspektów. Jest nim pozbycie się często powtarzanego kodu niezwiązanego z głównym przeznaczeniem metody z jej kodu. Taka izolacja ma na celu sprawienie kodu bardziej przejrzystym i ułatwienie wprowadzenia ewentualnych zmian.
Problemy
Głównym problemem związanym z programowaniem aspektowym jest fakt, że sam pomysł jest stosunkowo młody i w związku z tym wsparcie jest wciąż niewielkie. O ile ze znalezieniem odpowiednich narzędzi nie powinno być problemów to kwestia przyzwyczajenia programistów i to, że języki modelowania takie jak UML nie zostały stworzone z myślą o aspektach sprawiają, że wciąż nie zyskało dużej popularności.
Podsumowanie
Programowanie aspektowe, mimo że z niskopoziomowego punktu widzenia nie zmienia praktycznie niczego, to bez wątpienia jest w stanie wprowadzić pewne uporządkowanie w miejscu gdzie typowe programowanie obiektowe pozostawiło dużą swobodę. Rozwiązanie ciekawe, ale jest tylko jednym z wielu sposobów na ominięcie pewnych problemów co sprawia, że nie odnosi, jak na razie, spektakularnych sukcesów.
</description><pubDate>Tue, 17 Mar 2009 20:53:43 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/03/17/programowanie-aspektowe/</guid><category>Programowanie</category><category>Techblog</category><category>programowanie aspektowe</category><category>aspekt</category><category>pointcut</category><category>joinpoint</category><category>advice</category><category>weaver</category><category>aspectj</category><category>aspectc++</category><category>programowanie obiektowe</category><category>obiekt</category><category>klasa</category><category>obserwator</category></item><item><title>Choices</title><link>http://pdziepak.quarnos.org/2009/03/10/choices/</link><description>Jedną z cech systemów operacyjnych na którą zwykle kładzie się duży nacisk jest ich niezawodność i stabilność. W tym celu starano się rozwijać mikrojądra, które dzięki większej izolacji poszczególnych elementów systemu zmniejszają podatność na błędy. Także wykorzystanie bezpiecznych języków (jak np. w Singularity) jest pewnym krokiem w kierunku zwiększenia niezawodności systemów. Innym projektem wykorzystującym pewne ciekawe mechanizmy jest eksperymentalny system operacyjny Choices rozwijany na Uniwersytecie Illinois w Urbana-Champaign.
Ogólna budowa
Choices jest napisanym w C++ systemem operacyjnym zorientowanym obiektowo. Jako całość jest tworzony z wielu odrębnych obiektów współpracujących ze sobą. Składa się z frameworków definiujących poszczególne aspekty jego działalności. Dziedziczenie jest wykorzystywane do łatwego przenoszenia systemu na inne platformy i do stosowania zależnych od sprzętu optymalizacji. Choices wspiera SPARC, x86 oraz ARM, a także istnieje możliwość jego uruchomienia w trybie użytkownika na Solarisie i Linuksie.
Wyjątki procesora
Chocies korzysta z dobrodziejstw języków programowania wysokiego poziomu także w przypadku wyjątków procesora. Każde przerwanie jest obsługiwane przez InterruptManagera. Przekazuje on stosowne informacje do odpowiednich elementów systemu operacyjnego, lub jeżeli przerwanie jest wyjątkiem procesora zgłasza błąd. InterruptManager rzuca wtedy wyjątek C++ (model jego obsługi nie ma znaczenia), w taki sposób, aby stos wskazywał, że został on rzucony z miejsca w którym wystąpił wyjątek procesora. Dzięki temu kod obsługi wyjątków może zlokalizować błędną operację i wykonać odpowiednie czynności.
Odzyskiwanie po awarii
Choices wspiera mechanizmy odzyskiwania stanu programu (bądź jądra) w przypadku ewentualnych błędów spowodowanych wadliwym kodem lub nieprawidłowym funkcjonowaniem sprzętu.

przeładowanie kodu - jest to mechanizm uaktywniający się, gdy procesor zasygnalizuje próbę wykonania nieprawidłowej operacji, które są najczęściej oznaką błędu sprzętowego związanego z pamięcią operacyjną. W tej sytuacji wadliwa instrukcja jest ponownie ładowana z innego źródła danych (np. obrazu jądra trzymanego w pamięci flash). Dodatkowo, możliwe są okresowe kontrole poprawności kodu opierające się na zgodności sum kontrolnych. Ten mechanizm może być zastosowany jedynie do kodu, który nie jest generowany run-time.
mikrorestart jest wykonywany najczęściej gdy błąd wystąpi podczas wykonywania funkcji dostarczanej przez komponent. Polega na ponownym załadowaniu, przygotowaniu do działania i kolejnej próbie wywołania funkcji. W odróżnieniu od poprzedniego mechanizmu, mikrorestart skupia się głównie na próbie odzyskania uszkodzonych struktur danych, gdzie pomocne są także opisane w dalszej części artykułu Server State Regions.
automatyczny restart usługi - dotyczy głównie serwerów działających jako procesy w trybie użytkownika. Polega właściwie na tym samym co mikrorestart, z tym, że także próbuje radzić sobie z ewentualnymi problemami wynikającymi z utrzymywanych przez proces blokad oraz dostępu do pamięci współdzielonej. W celu umożliwienia skutecznego restartu usługi, Choices monitoruje wszystkie blokady utrzymywane przez procesy. W sytuacji gdy proces ulegnie awarii, blokady są zwalniane, tak aby nie powodowały problemów po wznowieniu działania programu.


transakcje - jest to sposób radzenia sobie z ewentualnymi błędami szeroko wykorzystywany w bazach danych. Transakcje polegają na zachowaniu kopii danych, tak aby można było całkowicie anulować wykonywaną operację, jeżeli którykolwiek z jej elementów się nie powiedzie. Innymi słowy, w przypadku napotkania błędu, dane pozostają niezmienione. Dzięki temu ewentualne błędy, nie mogą naruszyć integralności danych.

Kontrolowane restarty
Innym istotnym mechanizmem, co prawda jeszcze niezaimplementowanym, są policy-driven restarts. Dają programistom możliwość dokładnego opisania sposobu ponownego uruchamiania programu, tak aby dostosować go do konkretnych potrzeb. Możliwe jest także zdefiniowanie pewnych operacji jako reakcję na określony błąd zastępującą zwykły restart programu.
Server State Regions
Część mikrojąder jest zaprojektowana w taki sposób, że każdy serwer przetrzymuje wszystkie potrzebne mu dane na temat każdego z klientów. Jest to rozwiązanie proste i wygodne zarówno dla jądra jak i poszczególnych serwerów działających w przestrzeni użytkownika. Sprawia to jednak, że działanie programu jest uzależnione od przypisanych mu danych obecnych w usługach z których korzysta. W rezultacie, mimo izolacji poszczególnych komponentów, błąd w kodzie serwera wciąż może doprowadzić do błędu aplikacji z niego korzystających.
Rozwiązanie tego problemu, jakie zastosowano w Choices polega na przeniesieniu wszystkich danych aplikacji poza przestrzeń adresową serwera. Tworzy się w ten sposób tak zwane Server State Regions, obszary pamięci przechowujące wymagane przez konkretną usługę dane na temat aplikacji. SSR pomimo że są tworzone z pamięci dostępnej klientom, są dla nich ze względów bezpieczeństwa niedostępne. Dzięki temu, serwer może bezpiecznie umieścić w nich wszystkie informacje jakie dotyczą konkretnych aplikacji.
Gdy aplikacja po raz pierwszy skorzysta z usług oferowanych przez dany serwer, zostanie utworzony dla niej osobny SSR. Następnie przy każdym wywołaniu funkcji serwera odpowiedni SSR jest tymczasowo mapowany do jego przestrzeni adresowej1. Po wykonaniu wszystkich potrzebnych operacji i powrocie do kodu aplikacji serwer ponownie traci dostęp do SSR.
W przypadku awarii serwera zostaje on automatycznie uruchomiony ponownie. Dzięki SSR błąd serwera może naruszyć poprawność danych co najwyżej jednej aplikacji, podczas gdy wszystkie pozostałe nie odczują żadnych skutków awarii. Dodatkowo po restarcie serwer może uzyskać informacje na temat swojego poprzedniego stanu i spróbować naprawić ewentualne błędy w przetwarzanym SSR.
Zastosowanie SSR ma także szereg dodatkowych zaleta. Wykorzystywanie pamięci aplikacji do tworzenia SSR znacząco ogranicza możliwe sposoby przeprowadzenia ataku DoS na serwer. SSR są także niezależne od klienta, dzięki czemu mogą zostać wykorzystane do odzyskania dawnego stanu aplikacji przed jej ewentualną awarią. Oczywiście wymaga to dodatkowego wsparcia ze strony klienta i nie jest wspierane przez jądro w takim stopniu jak odzyskiwanie serwera, ale pozwala np. na niezerwanie połączeń TCP w przypadku błędu klienta.
1 Warto zaznaczyć, że nie wpływa to w znaczący sposób na wydajność, ponieważ każde wywołanie funkcji udostępnianych przez serwer i tak powoduje unieważnienie TLB.
Podsumowanie
Choices nie jest projektem przeznaczonym do użytku. Należy na niego patrzeć jak na system edukacyjny i eksperymentalny, który łączy w sobie wiele różnych mechanizmów zapewniających większą niezawodność. Wiele z tych rozwiązań zostało już zastosowanych w innych mikrojądrach, ale w Choices stara się stworzyć z nich spójny system współpracujących ze sobą elementów. Projekt niestety zdaje się być obecnie martwy, ostatnie aktualizacje jego strony miały miejsce latem 2007 r. Innym problemem jest fakt, że wiele mechanizmów zostało szczegółowo opisanych, ale nie są jeszcze zaimplementowane.
</description><pubDate>Tue, 10 Mar 2009 17:34:35 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/03/10/choices/</guid><category>Systemy operacyjne</category><category>Techblog</category><category>choices</category><category>mikrojądro</category><category>microreboot</category><category>wyjątek</category><category>serwer</category><category>server state region</category><category>ssr</category><category>transakcje</category><category>mikrorestart</category></item><item><title>Singularity</title><link>http://pdziepak.quarnos.org/2009/03/03/singularity/</link><description>Większość obecnych systemów operacyjnych w mniejszym lub większym stopniu bazuje na dość podobnych założeniach. Nawet jeżeli architektura jądra znacząco się różni (jądra monolityczne, mikrojądra) to i tak wiele pozostałych elementów pozostaje w niewiele zmienionej formie. Istnieją jednak pewne eksperymentalne projekty, mające na celu sprawdzić, jak nowe koncepcje sprawują się w praktyce. Jednym z nich jest rozwijany przez Microsoft Research system operacyjny Singularity.
C# i Sing#
Projekt jest w przeważającej większości napisany w C#. Według danych Microsoftu 95% kodu mikrojądra jest napisana w tym języku. Pozostałe to assembler x86 oraz C++ wykorzystane przy niskopoziomowej obsłudze pewnych podstawowych elementów systemu, o czym później. W tym miejscu nie można nie wspomnieć o Sing#. Jest to zestaw rozszerzeń do C# wspierający wiele dodatkowych mechanizmów często wykorzystywanych w kodzie Singularity. Dodatkowe elementy zawarte w Sing# to między innymi:

kontrakty - pozwalają na zachowanie bezpieczeństwa typologicznego podczas komunikacji międzyprocesowej realizowanej przez kanały (channels). Kontrakty definiują jakie wiadomości mogą być przesyłane, typy oraz wymagania co do wartości ich argumentów oraz kierunek w jakim są wysyłane. Kontrakty pozwalają także na ustalenie oczekiwanych komunikatów na każdym etapie transmisji.
endpoints - każdy kontrakt definiuje endpointy przeznaczone dla obu stron komunikacji. Zawierają one wszystkie metody opisane w kontrakcie jako możliwe wiadomości i służa do wygodnego ich odbierania i wysyłania.
exchange heap - Sing# pozwala na alokację obiektów w specjalnym miejscu przestrzeni adresowej zwanym stertą wymiany (exchange heap), która jest wykorzystywana przez wszelkie rodzaje komunikacji międzyprocesowej. Dodatkowo wykonywane są wszystkie informacje związane z zachowaniem informacji o właścicielu danego obiektu.
konstrukcja switch-receive - razem z endpointami pozwala na wygodne odbieranie wiadomości opierając się na standardowej konstrukcji switch.

{geshi lang=csharp}switch receive {
	case endpoint.Message1() :
		...
		break;
	case endpoint.Message2() :
		...
		break;
}{/geshi}Bezpieczeństwo
C# (a także wszystkie rozszerzenia związane z Sing#) jest językiem bezpiecznym pod wieloma względami, na czym bazują główne założenia systemu Singularity. Można rozróżnić dwa obszary w których ograniczona została możliwość występowania błędów:

bezpieczeństwo typów zapewnia, że wszystkie operacje wykonywane na obiekcie są zgodne z jego typem
bezpieczeństwo pamięci zapewnia, że wszystkie odwołania do pamięci są poprawne. Wiąże się to z zerowymi wskaźnikami, odwołaniom poza rozmiar tablicy lub do nieistniejących obiektów.

Bartok
Programy pisane w C# lub innych językach związanych z platformą .NET zwykle są tłumaczone do pośredniego (także bezpiecznego) języka MSIL. Zadaniem kompilatora Bartok, będącego jedną z najważniejszych części Singularity jest tłumaczenie MSIL do kodu natywnego, zachowując przy tym bezpieczeństwo kodu. Odpowiednie optymalizacje pozwalają na stworzenie jak najbardziej wydajnego kodu wykorzystującego wszystkie możliwości danego komputera, co chociaż w pewnym stopniu jest w stanie zrekompensować straty wydajności spowodowane procedurami wsparcia runtime.
Jednolite procesy
Zapewnienie pełnego bezpieczeństwa kodu natywnego wymaga od Singularity wprowadzenie jeszcze dodatkowych ograniczeń w stosunku do procesów (a także i jądra):

Kod programu nie może zostać zmodyfikowany w trakcie jego działania.
Wyklucza to kompilację just-in-time (obecną np. na platformie .NET), a wszystkie dynamicznie konsolidowane biblioteki muszą zostać załadowane przed rozpoczęciem pracy programu.
Żaden obiekt nie może należeć do więcej niż jednego procesu w tym samym czasie.
To ograniczenie ułatwia synchronizację między procesami upraszczając sposób komunikacji między nimi.

Wymaga to między innymi udziału systemu operacyjnego w większości operacji mogących stanowić zagrożenie i tym samym wymusza użycie mechanizmów typu wspomniane wcześniej kontrakty. Istotne jest, że jądro także podlega tym ograniczeniom.
Dzięki tym ograniczeniom możliwe jest także zastosowanie refleksyjności czasu kompilacji (Compile Time Reflection). Żaden element kodu nie może zostać zmieniony w trakcie działania programu, dlatego też refleksyjność realizowana podczas kompilacji (czyli de facto w ostatnim momencie w którym kod może się zmienić) jest w zupełności wystarczająca.
Ochrona pamięci
Najczęstszym obecnie stosowanym mechanizmem mającym za zadanie ochronę przestrzeni adresowej poszczególnych procesów jest stronicowanie. Jako element realizowany sprzętowo HIP wiąże się z pewnymi spadkami wydajności, szczególnie w przypadku komunikacji międzyprocesowej, która wymaga operacjach na katalogu/tablicy stron skutkujących unieważnieniem TLB. Także wywłaszczenie procesu w większości sytuacji unieważnia przynajmniej część wpisów w TLB. Przez bardzo długi czas była to jedna z ważniejszych przyczyn małej popularności mikrojąder.
Każdy program (a także i kernel) w Singularity jest napisany w bezpiecznym języku programowania. Nie może on samodzielnie utworzyć lub unieważnić referencji do obiektów w pamięci a także wykonać żadnych innych operacji, które mogłyby zagrozić bezpieczeństwu systemu. Jedynie alokacja pamięci oraz garbage collector nie spełniają tej zasady, ale są to mechanizmy dostarczane przez system operacyjny i tym samym w pełni zaufane.
Skoro sam język programowania ogranicza programistę do podobnego stopnia jak HIP, sprzętowa ochrona pamięci staje się niepotrzebna. Jej rolę pełnią wszystkie funkcje kontrolne wsparcia runtime, które tym samym tworzą SIP. Wiąże się z tym także założenie, że każdy obiekt może należeć tylko do jednej przestrzeni adresowej. Z tego powodu pamięć współdzielona, a także wszelkie inne sposoby komunikacji międzyprocesowej inne niż kanały (channels) są niedozwolone.
Komunikacja międzyprocesowa
Komunikacja międzyprocesowa w Singularity opiera się na kanałach (channels). Wykorzystują one dodatkowe elementy Sing# opisane wcześniej: kontrakty i endpointy. Dzięki nim utrzymane jest bezpieczeństwo operacji związanych w komunikacją. Cała komunikacja odbywa się przy pomocy endpointów które dostarczają odpowiedni interfejs dla obu uczestników transmisji.
Dane przekazywane są przy użyciu sterty wymiany (exchange heap). W tym miejscu nie funkcjonuje garbage collector, zamiast niego wykorzystywane są liczniki referencji. Jest to obszar pamięci do którego ma dostęp każdy proces, ale zgodnie z zasadami dotyczącymi bezpieczeństwa każdy obiekt przez cały czas ma przyporządkowanego mu właściciela. Przekazanie obiektu odbywa się przez dostarczenie endpointowi odbiorcy wskaźnika na przesyłany obiekt (wewnątrz sterty wymiany).
Sterta wymiany ma też ograniczenia co do obiektów znajdujących się w niej. Przede wszystkim muszą być one wymiennego typu czyli nie mogą zawierać żadnych referencji do innych, nieprzesyłanych obiektów.
Podsumowanie
Singularity jest bez wątpienia bardzo ciekawym, ale też i bardzo rozbudowanym projektem. Ten artykuł opisuje jedynie najbardziej istotne cechy tego systemu, sprawiające, że wyróżnia się on na tle obecnie często stosowanych systemów. Jest to projekt eksperymentalny w którym nie wszystkie rzeczy są jeszcze w pełni ukończone, ale mimo to już zawiera wiele interesujących rozwiązań.
Najprawdopodobniej, w najbliższym czasie pojawi się kolejny artykuł opisujący mniej popularne ale wciąż bardzo istotne elementy systemu Singularity. Być może także pokuszę się o dokładniejsze opisanie któregoś z mechanizmów, bo projekty eksperymentalne zawsze warte są szczególnej uwagi.
</description><pubDate>Tue, 03 Mar 2009 21:57:57 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/03/03/singularity/</guid><category>Systemy operacyjne</category><category>Techblog</category><category>singularity</category><category>sing</category><category>c</category><category>mikrojądro</category><category>sip</category><category>microsoft</category><category>contract</category><category>endpoint</category><category>safe</category><category>msil</category><category>bartok</category></item><item><title>Obiekty, klasy i metody w Objective-C</title><link>http://pdziepak.quarnos.org/2009/02/24/obiekty-klasy-i-metody-w-objective-c/</link><description>Programowanie zorientowane obiektowo w czystym C, mimo że możliwe, rzadko kiedy jest proste i przyjemne, a powstały kod jest zwykle bardzo zagmatwany. Dlatego też na bazie C powstały kompatybilne z nim języki dodające wygodne w użyciu wsparcie dla kodu zorientowanego obiektowo. Pierwszy z nich to niezwykle popularny C++, w którym większość tych rozszerzeń to różnego rodzaju dodatkowe operacje wykonywane podczas kompilacji. Drugi to niszowy Objective-C, który (oprócz składni) od C++ różni się tym, że dodatkowe elementy jakie zostały do niego wprowadzone są odpowiednimi operacjami wykonywanymi podczas działania programu. Dzięki temu udaje się uzyskać w języku kompilowanym do kodu natywnego część udogodnień znanych z technologii takich jak .NET czy Java.
Klasy
W języku Objective-C każda klasa jest także obiektem zawierające podstawowe informacje wymagane przez pozostałe funkcje wsparcia runtime. Najważniejsze z danych przechowywanych przez obiekty opisujące klasy to:

nazwa klasy
rozmiar instancji klasy (z uwzględnieniem klas bazowych)
lista zmiennych składowych klasy
lista funkcji składowych klasy
wskaźnik na klasę bazową
lista klas pochodnych
realizowane protokoły

Traktowanie klasy jako obiektu (inspirowane Smalltalkiem) pozwala znacząco poszerzyć możliwości języka szczególnie jeżeli chodzi o refleksyjność. Struktury opisujące klasy najczęściej są przechowywane w wewnętrznych danych runtime. Odpowiednie informacje są wydobywane przy okazji tworzenia instancji danej klasy, a następnie wiązane nowym obiektem. Dlatego też poniższy kod:
{geshi lang=&quot;objc&quot;}array = [NSArray new]{/geshi}zostanie w większości implementacji, już przez kompilator zamieniony na konstrukcję podobną do tej:
{geshi lang=&quot;objc&quot;}array = [(objc_get_class(&quot;NSArray&quot;)) new]{/geshi}Funkcja objc_get_class przegląda listę wszystkich klas w poszukiwaniu żądanej i w przypadku jej odnalezienia zwraca wskaźnik do niej. Cała operacja ma miejsce już podczas działania programu, dlatego też istotna jest szybkość działania takich podstawowych operacji. Jednym ze sposobów na przyśpieszenie kodu jest cachowanie wskaźników na obiekty opisujące klasy. W rezultacie funkcja objc_get_class jest wykonywana jeden raz dla każdej klasy, a w każdym kolejnym przypadku używany jest zwrócony przez nią wskaźnik.
Obiekty
Wszystkie klasy w Objective-C dziedziczą po NSObject. Jest to klasa która realizuje większość operacji pozwalających na korzystanie przez programistę z informacji jakie są przechowywane o każdej klasie. Oferowana funkcjonalność to między innymi:

sprawdzenie czy klasa odpowiada na selektor
sprawdzenie czy klasa realizuje protokół
podanie funkcji odpowiadającej na selektor
wykonanie podanego selektora
podanie nazwy klasy

Selektory
Selektory identyfikują wysyłaną do obiektu wiadomość, czyli innymi słowy, nazywają funkcję składową która powinna zostać wywołana. W większości jest to po prostu ciąg znaków odpowiadających nazwie funkcji, chociaż w niektórych implementacjach zawiera także unikalny numer identyfikacyjny. W celu usprawnienia operacji tłumaczenia ciągu znaków na selektor i odwrotnie istnieją odpowiednie tablice przechowujące listy wszystkich zarejestrowanych danych. Jako że mimo tego, może to być kosztowna operacja, umożliwiane jest (wręcz zalecane) tłumaczenie nazw do selektorów podczas kompilacji.
Wywołanie funkcji składowych
Wywołanie funkcji składowych, a właściwie zgodnie z nomenklaturą Objective C: wysyłanie wiadomości obiektom jest przeprowadzany w całkowicie dynamiczny sposób. Warto także zaznaczyć, że ponieważ klasy także są traktowane jako obiekty, statyczne funkcje składowe wywoływane są w taki sam sposób. Oto przykładowy kod wysyłania komunikatu do obiektu:
{geshi lang=&quot;objc&quot;}[obiekt metoda]{/geshi}zostaje zamieniony na:
{geshi lang=&quot;objc&quot;}objc_msgSend(obiekt, @selector(metoda)){/geshi}Funkcja objc_msgSend, na podstawie informacji zawartych w opisie klasy której instancją jest object, wywołuje odpowiednią metodę. Każda klasa zawiera listę selektorów oraz przypisanych im adresów funkcji. Procedura przekazująca wiadomość przeszukuje tę listę (z uwzględnieniem klas bazowych) w poszukiwaniu adresu odpowiedniej metody, która następnie zostaje wywołana.
Często, w celu przyśpieszenia tej bardzo częstej operacji, wykorzystywane są tak zwane dispatch tables. Właściwie pod każdym względem przypominają one tablice funkcji wirtualnych wykorzystywane np. w C++. Każda taka tablica zawiera jedynie adresy kolejnych funkcji składowych klasy. W tej implementacji unikalny numer identyfikacyjny selektora służy także jako indeks wskazujący na odpowiedni adres w tej tablicy. Najważniejszą różnicą między dispatch table a tablicami funkcji wirtualnych jest fakt, że dispatch tables najczęściej są tworzone już podczas działania programu na podstawie listy funkcji składowych zawartej w obiekcie opisującym klasę.
Pozostałe dane dotyczące klasy
Często zdarza się, że wpisy z adresami funkcji składowych i odpowiadającymi im selektorami zawierają także informację o przyjmowanych przez metodę argumentach. Jest to przydatne przy debuggingu. Podobnie rzecz się ma z wpisami dotyczącymi zmiennych składowych. Obiekt opisujący klasę przechowuję listę struktur zawierających nazwę każdej takiej zmiennej, jej typ (dla ułatwienie debuggingu) i przesunięcie w stosunku do bazowego adresu instancji.
Podsumowanie
Objective-C prezentuje inne podejście co do sposobu implementacji rozszerzeń związanych z OOP w C niż C++. Opiera się bardziej na wykonywaniu operacji podczas działania programu, co zmniejsza jego wydajność, ale z drugiej strony oferuje szereg dodatkowych możliwości. Niech za przykład posłużą tutaj choćby systemy rozproszone. W Objective-C przy odrobinie wysiłku ze strony osoby implementującej taki system, można tworzyć obiekty rozproszone w banalnie prosty sposób. Możliwe jest to właśnie dzięki dużej ilości informacji które zostają w kodzie po kompilacji, oraz wielu możliwościach wstawienia dodatkowego kodu podczas działania programu. Oczywiście takie rozwiązania są też możliwe w C++ czy innych językach, ale nie zawsze jest to aż takie proste.
Główną wadą Objective-C jest jego niewielka popularność. Przed śmiercią chroni go właściwie jedynie OpenStep. Warto jednak zwrócić uwagę na jego możliwości, ponieważ jest on gdzieś pomiędzy innymi językami kompilowanymi do kodu natywnego a kompilowanymi do kodu pośredniego. Z jednej strony wsparcie runtime jest bardzo rozbudowane, ale wciąż istnieje możliwość uzyskania maksymalnej możliwej wydajności w krytycznych miejscach, przez ograniczenie używania niektórych z jego funkcji.
</description><pubDate>Tue, 24 Feb 2009 22:18:12 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/02/24/obiekty-klasy-i-metody-w-objective-c/</guid><category>Objective-C</category><category>Programowanie</category><category>Techblog</category><category>objective-c</category><category>dispatch table</category><category>virtual</category><category>vtable</category><category>dtable</category><category>selector</category><category>selektor</category><category>SEL</category><category>klasa</category><category>obiekt</category><category>instancja</category><category>runtime</category><category>cocoa</category><category>openstep</category></item><item><title>Quarn OS 0.0.90</title><link>http://pdziepak.quarnos.org/2009/02/22/quarn-os-0-0-90/</link><description>Po długim okresie prac nad Quarnem (niestety nie obyło się bez przerw), w końcu zdecydowałem się na wydanie wersji 0.0.90. Warto zaznaczyć, że nie jest to wersja nadająca się do użytkowania. Jej głównym zadaniem jest wyznaczenie osiągniętego milestone.
Zainteresowani mogą ściągnąć Quarna tutaj lub bezpośrednio tutaj. Możliwe jest także ściągnięcie gotowego obrazu dyskietki w tym miejscu. Uprzedzam, że w wielu częściach kod jest jeszcze niedopracowany i czasami wręcz razi błędami, jednak głównym celem tego wydania było ustabilizowanie pewnej podstawy na bazie której będą prowadzone dalsze prace. W najbliższej przyszłości planuję po kolei zająć się poszczególnymi elementami systemu znacząco je dopracowując. Obecnie, są one jedynie zalążkami, których głównym zadaniem jest wykorzystanie możliwości podsystemu Manes.
Quarn w wersji 0.0.90 posiada zaimplementowane między innymi:

Managed Execution System
Execution Flow Controller
podstawowe sterowniki (klawiatura, dma, fdc, pic, pit, rs232, pci)
wielozadaniowość z planistą round-robin (wywłaszczanie)
alokator pamięci O(1)
podstawowe elementy interfejsu Hydra
port Hydry do POSIX
podstawowe wsparcie dla FAT12
wsparcie dla RTTI w C++
wsparcie dla plików ELF, dynamiczny konsolidator
ładowanie modułów kernela
wykonywanie zewnętrznych plików
systemu zasobów, urządzeń i usług

W tym momencie należą się pewne wyjaśnienia co do niektórych elementów Quarna. Co prawda w przyszłości zamierzam napisać o nich dużo więcej, jednak teraz przynajmniej podam ogólny zarys ich działania.
Manes
Managed Execution System (Manes) jest systemem rozproszonej dystrybucji obiektów i komponentów (przypomina systemy typu CORBA czy COM). Jest sercem Quarna, rozwiązującym wszelkie kwestie związane z modułowością i komunikacją pomiędzy poszczególnymi elementami systemu. W przyszłośći Manes wspierać będzie także szereg dodatkowych technologii jak na przykład programowanie aspektowe.
Kolejnym elementem bardzo mocno związanym z Manes jest Execution Flow Controller. Wykorzystuje on tablice metod wirtualnych do wstrzykiwania kodu, który zostanie wykonany przed wywołaniem takiej funkcji składowej. Pozwala to na implementację większości funkcji dostarczanych przez Manes.
Hydra
Innym istotnym elementem Quarna jest Hydra czyli główne API jakie jest dostarczane aplikacjom. Obejmuje między innymi interfejs użytkownika (obecnie tylko w trybie tekstowym, w przyszłości także i w graficznym), operacje wejścia-wyjścia, wielowątkowość itp. Możliwe jest uruchomienie programów napisanych przy wykorzystaniu Hydry na każdym systemie zgodnym z POSIX dzięki specjalnej warstwie pośredniczącej. W Quarnie 0.0.90 jest to zaprezentowane za pomocą domyślnego shella.
Podsumowanie
Quarn jest projektem amatorskim w związku z czym prace nad nim czasowo ulegają większemu spowolnieniu. Nie skupiam się w nim na uzyskiwaniu jak największej funkcjonalności lecz na zastosowaniu ciekawych, nowych rozwiązań. Dlatego też obecna wersja nie powala swoimi możliwościami i służy głównie ułatwieniu w utrzymaniu porządku w kodzie.
</description><pubDate>Sun, 22 Feb 2009 00:12:49 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/02/22/quarn-os-0-0-90/</guid><category>Projekty</category><category>Quarn OS</category><category>Techblog</category><category>quarn os</category><category>system operacyjny</category></item><item><title>Programowanie oparte na komponentach</title><link>http://pdziepak.quarnos.org/2009/02/17/programowanie-oparte-na-komponentach/</link><description>Programowanie zorientowane obiektowo jest niewątpliwie jednym z najpopularniejszych obecnie paradygmatów. Powstało bardzo wiele opracowań na jego temat wprowadzających chociażby techniki znane jako wzorce projektowe. Nie jest to jednak rozwiązanie idealne i w pewnych zastosowaniach wiążą się z nim pewne trudności. Chodzi tutaj o tworzenie gier komputerowych gdzie coraz częściej można się spotkać z programowaniem opartym na komponentach.
Uzasadnienie
Praktycznie w każdej grze występuje główna baza obiektów w niej występujących. Zwykle są to instancje klas ściśle ze sobą powiązanych odpowiednią hierarchią. Wspólna funkcjonalność, zgodnie z duchem OOP, jest w miarę możliwości przenoszona do klas znajdujących się wyżej w hierarchii. Wszelkie odstępstwa od domyślnego zachowania są implementowane przy użyciu polimorfizmu.
Wraz ze wzrostem stopnia skomplikowania projektu coraz trudniej jest unikać dziedziczenia wielobazowego i wszystkich związanych z nim niedogodności, w trakcie pozbywania się duplikowanego kodu. Innym problemem który się pojawia jest powstanie bardzo dużych klas bazowych, które są zwykle wysoce niezalecane. Dochodzi do sytuacji gdzie programowanie zorientowane obiektowo najwyraźniej nie jest najlepszy sposobem na rozwiązanie danego problemu.
Podstawowe założenia
W powyższych sytuacjach bardziej istotnym od zarządzania obiektami jest wygodne zarządzanie logiką kodu. Realizowane jest to za pomocą dwóch podstawowych elementów programowania opartego na komponentach:

jednostka (entity) - identyfikuje to co w OOP zostałoby nazwane obiektem. Nie posiada pól ani metod, jest jedynie czymś w rodzaju nazwy. Swoim przeznaczeniem przypomina klasę, ponieważ definiuje konkretne zachowanie, lecz ilościowo odpowiada obiektom.
komponent (component) - definiują logikę operacji. Każdy komponent opisuje inne fragmenty zachowania jednostki. Najważniejszą różnicą między klasami a komponentami jest to, że klasy opisują pełną funkcjonalność, podczas gdy pojedynczy komponent tylko jej część. W większości implementacji komponenty są obiektami globalnymi realizowanymi przy pomocy wzorca singleton, jednakże nie jest to sztywną regułą.

Niezwykle istotnym elementem całego systemu jednostek jest przyporządkowywanie im konkretnych komponentów. Może to odbywać się na dwa sposoby, zależnie od implementacji. Także umieszczenie kodu i danych jest sprawą zależną od samej implementacji. Warto jednak pamiętać, że podstawowym założeniem programowania opartego na komponentach jest utrzymanie minimalnego rozmiaru jednostki. W rezultacie jest to najczęściej liczba całkowita.
Dane
W systemach opartych na tym paradygmacie dane są umieszczane w komponentach. W zależności od możliwych zastosowań jednostki, komponenty przechowują dane potrzebne do realizowania przez nie odpowiednich funkcji. Wymaga to dodatkowego mechanizmu komunikowania się pomiędzy komponentami różnych typów w sytuacji gdy potrzebna jest modyfikacja danych innego komponentu. Powinno to być jednak unikane. Główną zaletą jest uproszczenie mechanizmów związanych z synchronizacją w przypadku systemów wielowątkowych. Każdy komponent zajmuje się tylko swoimi danymi dzięki czemu można ograniczyć ilość blokad.
Dodatkowo najczęściej to komponenty przechowują informację o przyporządkowanym im jednostką. Dzięki temu zlokalizowanie wszystkich jednostek jest proste i nie wymaga istnienia dodatkowych struktur danych. W tej kwestii wiele jednak zależy od implementacji, bo nie da się ukryć, że zdarza się także sytuacja w której każda jednostka przechowuje listę przypisanych komponentów.
Kod
W przypadku kodu najpopularniejszym rozwiązaniem jest umieszczenie go także w komponentach. Wtedy co prawda jest on powiązany z danymi, co może przypominać OOP, jednak uzyskuje się duże rozdrobnienie funkcjonalności co ułatwia wiele kwestii. Oprócz wspomnianego ominięcia problemu synchronizacji, możliwa jest dynamiczna zmiana właściwości i metod obiektu (reprezentowanego przez jednostkę) co pozwala na ograniczenie ilości zajmowanej pamięci. Istotny jest także inny sposób zapisu kodu co jest główną cechą odróżniającą od siebie paradygmaty programowania. Znacząco ułatwia to dodanie dodatkowej funkcjonalności poprzez wprowadzenie kolejnego komponentu.
Komponenty ułatwiają także masowe wykonywanie operacji. Jeżeli dana funkcja powinna być wykonana na wszystkich obiektach danego typu, komponent potrafi to zrobić korzystając z posiadanej listy przyporządkowanych mu jednostek. Kluczowe jest tutaj ustalenie odpowiedniej kolejności wykonywania komponentów w odpowiedzi na zdarzenia.
Przykład realizacji
Przykładowe zastosowanie komponentów w grze polega na stworzeniu wystarczającej ich ilości do pokrycia każdego możliwego zachowania obiektów. Oczywiście, z reguły nie stosuje się tutaj żadnej hierarchii komponentów, ponieważ zwykle nie przynosi to żadnych korzyści. Każdy obiekt obecny w grze jest zarejestrowany w komponencie Render który udostępnia procedury związane z jego wyświetlaniem. Oprócz tego łódki mogą być zarejestrowane w komponencie Boat. Po ich zniszczeniu zostają usunięte z komponentu Boat a zarejestrowane w Wreck.
Główna pętla programu w odpowiedniej kolejności, często w odpowiedzi na konkretne zdarzenia wykonuje żądane komponenty. Dla przykładu, jeżeli samochód jest sterowany przez gracza, to należy do komponentu PlayerControl, którego procedury są wywoływane w odpowiedzi na zdarzenia z urządzeń zewnętrznych.
Podsumowanie
Programowanie oparte na komponentach zwane także bardziej szczegółowo Entity System jest bez wątpienia bardzo ciekawym spojrzeniem na sposób rozwiązania problemów związanych z dużą ilością podobnych obiektów, których implementacja zawiera jednak pewne różnice. Pozwalają one w takiej sytuacji na uporządkowanie kodu i co za tym idzie ułatwienia jego dalszej rozbudowy.
Jest to technika stosowana najczęściej w przypadku produkcji gier komputerowych co nie oznacza, że w innych dziedzinach nie może znaleźć ona zastosowania. Istnieje wiele różnych podejść do takich kwestii jak umieszczenie kodu czy sposób kojarzenia komponentów z jednostkami. Dzieje się tak, ponieważ nie ma tutaj uniwersalnego rozwiązania i każdy dostosowuje to do swoich potrzeb. Podobnie rzecz się ma gdy porównać komponenty do OOP. Trudno powiedzieć żeby jeden paradygmat był lepszy od drugiego. Ich zastosowanie jest odmienne i należy tak je używać, aby wykorzystać jak najwięcej ich zalet.
</description><pubDate>Tue, 17 Feb 2009 22:07:02 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/02/17/programowanie-oparte-na-komponentach/</guid><category>Programowanie</category><category>Techblog</category><category>komponenty</category><category>entity system</category><category>component</category><category>data-driven</category><category>oop</category><category>object oriented</category></item><item><title>Protokół SOAP</title><link>http://pdziepak.quarnos.org/2009/02/10/protokol-soap/</link><description>W każdym systemie rozproszonym, niezależnie od tego czy jest to CORBA, DCOM, .NET Remoting czy cokolwiek innego, niezbędny jest pewny protokół zdalnego wywoływania kodu. Java posiada swój RMI, CORBA - GIOP/IIOP, DCOM - własny protokół, ponadto w Uniksach często stosuje się RPC firmy Sun Microsystems. Ten ostatni próbowano pogodzić z językiem XML i w rezultacie stworzono XML-RPC, zwany protokołem XML pierwszej generacji. Jego następcą jest niewątpliwie jeden z ciekawszych protokołów zdalnego wywoływania procedur (i nie tylko) - SOAP.
Podstawowe założenia
SOAP zakłada, że każda wiadomość jest wysyłana od konkretnego nadawcy do przynajmniej jednego odbiorcy przy opcjonalnym udziale jednego lub więcej pośredników. Każdy z elementów uczestniczących w przekazaniu wiadomości (tj. nadawca, pośrednik i odbiorca) jest nazywany węzłem i posiada swój własny adres URI. Dodatkowo, każda przesyłana wiadomość jest całkowicie niezależna od pozostałych, a protokół nie wspiera żadnych mechanizmów synchronizacji.
Budowa wiadomości
Każda wiadomość składa się z nagłówka (header) i ciała (body). Pierwszy z tych elementów może być analizowany przez każdego z pośredników, a także przez ostatecznych odbiorców. Ciało wiadomości jest zarezerwowane jedynie dla odbiorców. Poniżej przedstawiono strukturę wiadomości:
{geshi lang=xml}


	
		...
	
	
		...
		
		...
		
	

{/geshi}Nagłówek jest podzielony na bloki. Każdy blok posiada informację o roli do której jest przeznaczony, informacje czy musi być analizowany przez węzeł, informacje dotyczące warunków w których ma on być przekazany dalej oraz właściwe dane. Nagłówki służą głownie do realizacji przez pośredników dodatkowych zadań takich jak autoryzacja, logowanie czy dodatkowe mechanizmy bezpieczeństwa. Nagłówki są opcjonalne.
Ciało wiadomości jest częścią wymaganą przez standard, mimo że jej zawartość jest praktycznie dowolna. To właśnie w tym miejscu umieszczana jest treść wiadomości. Jedyną istotną rzeczą jest env:Fault jest to opcjonalny element używany do przekazywania informacji o błędach.
Cechy (features)
Niezwykle istotnym elementem protokołu SOAP są cechy (features), opisują one w dokładny sposób niemalże każdy aspekt całego procesu zdalnego wywołania procedury. Implementacje mają tutaj dość dużą dowolność, dzięki czemu mogą nadać pożądane w danym przypadku dodatkowe właściwości, które pozwolą lepiej wykorzystać możliwości protokołu SOAP.
Role (roles)
Role opisują zachowanie węzła po otrzymaniu danej wiadomości. Każdy blok w nagłówku posiada informację do jakiej roli jest przeznaczony, co decyduje czy węzeł powinien analizować zawartość danego bloku. Podobnie jak węzły i cechy, role także posiadają swój adres URI, który służy do ich identyfikacji. Wersja 1.2 standardu SOAP definiuje trzy role, lecz możliwe jest wprowadzenie dodatkowych.

next - każdy pośrednik oraz odbiorca musi przeanalizować nagłówek wiadomości
none - nagłówek wiadomości nie może być analizowany
ultimateReceiver - nagłówek wiadomości jest analizowany jedynie przez ostatecznych odbiorców, jest to domyślna rola

Właściwości (properties)
Kolejnym istotnym elementem protokołu SOAP są właściwości. Przechowują one istotne informacje dotyczące wiadomości nie będące jednak jej częścią. Najczęściej dotyczą one szczegółów związanych z transmisją danych. Podobnie jak większość innych elementów SOAP właściwości także identyfikowane są adresami URI.
Istnieją dwa konteksty właściwości: kontekst węzła (environment context) i kontekst wiadomości (message exchange context). Pierwszy zawiera informacje dotyczące konkretnego węzła, takie jak jego numer IP czy lokalną datę i czas. Drugi oprócz standardowego opisu wiadomości takiego jak jej ID zawieta także informacje zależne od konkretnego MEP-a.
Wzorce wymiany wiadomości (MEP)
MEP jest wzorcem według którego węzły SOAP wymieniają miedzy sobą informację. Każdy wzorzec tego typu posiada swój własny adres URI. Ich implementacja jest ściśle zależna od rodzaju wykorzystywanego protokołu niższej warstwy. Dodatkowo w zależności od schematu wg którego przesyłane są dane transmisja jest podzielona na poszczególne etapy. Dwa schematy wymienione w standardzie (które muszą być implementowane przez obsługę HTTP) to Response Message Exchange Pattern i Request-Response Message Exchange Pattern. Zasadnicza różnica między nimi jest taka, że pierwszy schemat nie zakłada przesyłania szczegółowego żądania. Ogranicza się ono do prostego wywołania zależnego od używanego protokołu niższej warstwy (np. HTTP). W obu przypadkach odpowiedź jest pełnowartościową wiadomością SOAP.
Przetwarzanie wiadomości
Po otrzymaniu wiadomości każdy węzeł analizuje ją w poszukiwaniu informacji, które są dla niego przeznaczone. Wygląda to w następujący sposób.

Nagłówek wiadomości jest przeszukiwany pod kątem istnienia bloków przeznaczonych do roli jaką spełnia węzeł.
Odnajdywane są bloki nagłówka obowiązkowe do przetworzenia.
Jeżeli węzeł nie jest w stanie przetworzyć któregokolwiek z bloków znalezionych w powyższych punktach wysyłana jest zwrotna informacja o błędzie.
W przeciwnym wypadku węzeł przetwarza w odpowiedni dla siebie sposób znalezione bloki. Jeżeli jest jednocześnie ostatecznym odbiorcą przetwarza także ciało wiadomości.
W sytuacji gdy węzeł nie jest odbiorcą oraz nie wystąpił żaden błąd wiadomość jest przekazywana dalej z pominięciem przetworzonych bloków w nagłówku.

Transmisja wiadomości
W większości przypadków SOAP wykorzystuje do przesyłania danych protokół HTTP. Dzięki temu łatwiej jest zbudować system oparty na SOAP w sieci z firewallami i serwerami proxy. Warto także zauważyć, że HTTP idealnie odpowiada specyfice SOAP. Każda wiadomość SOAP, podobnie jak każde żądanie HTTP, jest całkowicie niezależna od pozostałych. Naturalnie, możliwe jest także wykorzystywanie HTTPS do transmisji wymagających większego bezpieczeństwa. Inną alternatywą, choć dość rzadko spotykaną i omawianą, jest wykorzystanie protokołu SMTP. Spotyka się także implementacje które dla zwiększenia wydajności operują bezpośrednio na niższej warstwie - protokole TCP.
Jak zostało to wspomniane w paragrafie o wzorcach wymiany wiadomości, transmisja jest podzielona na stany. Oto przykładowy przebieg transmisji Request-Response Message Exchange Pattern z użyciem protokołu HTTP.

init
Odpowiednia właściwość kontekstu wiadomości zawiera adres HTTP węzła do którego należy się połączyć. Wykorzystywana jest w zależności od konfiguracji i/lub implementacji metoda GET lub POST. W żądaniu HTTP umieszczone zostają wszystkie informacje dotyczące kodowania znaków. W tym czasie nasłuchujący adresat przyjmuje połączenie i jeżeli nie wystąpi żaden błąd oczekuje na zakończenie transmisji ze strony nadawcy.
requesting
Używając metody określonej w poprzednim etapie przesyłana jest treść żądania do adresata. W zależności od odpowiedzi algorytm może powrócić do stanu init aby ponowić próbę (ma to miejsce np. przy przekierowaniach), fail jeżeli wystąpił bład lub sending+receiving jeżeli wszystko przebiegło pomyślnie. W tym samym czasie adresat jest w stanie responding w którym oczekuje na dane od nadawcy i po ich odebraniu wysyła odpowiedź.
sending+receiving
W tym stanie adresat przetwarza otrzymaną wiadomość. Transmisja danych jest zakończona.
success lub fail
W zależności od wyniku przeprowadzonej operacji węzeł kończy przesyłanie danych z informacją o sukcesie lub porażce.

Serializacja danych
Niezbędnym elementem w każdym systemie zdalnych wywołań procedur jest mechanizm serializacji przekazywanych danych, takich jak argumenty funkcji. Nazwy występujące w programie wykorzystującym SOAP są modyfikowane w taki sposób aby mogły zostać użyte w każdym elemencie języka XML. Dlatego niedozwolone znaki zastępuje się ich wartością szestnastkową w UTF-16 (lub UTF-8) dla przykładu Hello world zostanie zamienione na Hello_x0020_world.
Wywołanie jest przedstawiane w ciele wiadomości jako element o nazwie takiej jak nazwa funkcji (oczywiście po zakodowaniu znaków niedozwolonych w XML-u) zawierający wszystkie wejściowe i wejściowo-wyjściowe argumenty. Każdy argument jest przedstawiany jako element o tej samej nazwie zawierający jego wartość (także po przekształceniu w formę akceptowalną dla XML-a). Odpowiedzią jest element o dowolnej nazwie zawierający wszystkie argumenty wyjściowe oraz wejściowo-wyjściowe. Sposób ich zapisu jest taki sam jak w przypadku wywołania. Do RPC wykorzystywane są wzorce Response Message Exchange Pattern i Request-Response Message Exchange Pattern, chociaż standard zezwala także na inne.
Zastosowanie
SOAP jest jednym z elementów pozwalającym na rozproszoną dystrybucję komponentów. Razem z technologiami takimi jak WSDL pozwala on na stworzenie rozbudowanego systemu podobnego do CORBA i DCOM. W rezultacie zostało to uczynione w Java 2 Enterprise Edition (gdzie SOAP występuje obok Java RMI) oraz platformie .NET, gdzie SOAP ma za zadanie całkowicie wyprzeć DCOM. Często też spotyka się tę technologię w aplikacjach webowych z racji wykorzystania typowych dla tej dziedziny technologii jak XML i HTTP. Trwają także prace nad standardem XUP, który jest czymś w rodzaju połaczenia SOAP i XUL. Zakłada on wykorzystanie SOAP do przekazywania informacji o zdarzeniach dotyczących interfejsu opartego na XML (na przykład wspomniany XUL).
Podsumowanie
SOAP jest niewątpliwie ciekawym protokołem, mimo że oparcie się na XML-u ogranicza jego wydajność. Co prawda trwają pracę nad technologiami takimi jak Binary XML, który pozwolą przyśpieszyć pracę z XML-em, lecz jak na razie w starciu wydajnościowym CORBA czy DCOM bez problemu są w stanie pokonać SOAP.
To co jest wadą jest także i zaletą, fakt, że SOAP opiera się na XML-u pozwala na wykorzystanie w nim wielu istniejących już technologii (chociażby XSLT czy algorytmy serializacji danych). Stawia on na modułowość i łatwość dostępu co niejednokrotnie jest ważniejsze od wydajności. Jednak niezależnie od tego, jest to bez wątpienia bardzo interesująca technologia.
</description><pubDate>Tue, 10 Feb 2009 22:39:05 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/02/10/protokol-soap/</guid><category>Systemy rozproszone</category><category>Techblog</category><category>soap</category><category>simple object access protocol</category><category>mep</category><category>rpc</category><category>remote procedure call</category><category>xml</category><category>xul</category><category>xup</category><category>message exchange pattern</category></item><item><title>Komunikacja międzyprocesowa w QNX</title><link>http://pdziepak.quarnos.org/2009/02/03/komunikacja-miedzyprocesowa-w-qnx/</link><description>Na desktopach oraz serwerach niewątpliwie królują jądra monolityczne. Kernele systemów takich jak *BSD, (Open)Solaris czy Linux z grubsza opierają się na tej samej architekturze. Podobnie rzecz się ma w stosunku do Windowsa, mimo że w jego budowie jest już parę ciekawych różnic. Właściwie jedynym popularnym na tego typu maszynach systemem bazującym na mikrojądrze jest Mac OS X korzystający z jądra Mach.
Zupełnie inaczej rzecz się ma w przypadku systemów wbudowanych, a także wielu systemów czasu rzeczywistego. Tam mikrojądra odgrywają znacznie większą rolę. Jednym z popularniejszych systemów opartych na mikrojądrze jest QNX korzystający z jądra QNX Neutrino. Jego wewnętrzna architektura jest zupełnie inna od tej znanej z Linuksa czy też Uniksów. Wynika to głównie z tego, że nacisk jest położony na zupełnie elementy systemu. Jedną z najistotniejszych kwestii we wszystkich mikrojądrach jest komunikacja międzyprocesowa.
Wiadomości (messages)
Podobnie jak w innych systemach opartych na mikrojądrach, także w QNX tym co łączy wszystkie elementy systemu w jedną całość jest IPC. Podstawowym mechanizmem IPC udostępnianym przez QNX Neutrino są wiadomości (messages). Są to synchroniczne komunikaty na które adresat zawsze odpowiada. Warto przyjrzeć się sposobowi w jaki Neutrino przekazuje wiadomości.

Wątek będący klientem wykonuje procedurę MsgSend(). Przechodzi on w stan SEND blocked, co jest równoważne jego wstrzymaniu. Jeżeli wątek będący adresatem czeka na wiadomość, jest on wznawiany automatycznie, bez udziału planisty.
W momencie w którym wątek będący serwerem wywoła procedurę MsgReceive() dostanie on informację o otrzymanej wiadomości, a nadawca przejdzie w stan REPLY blocked. Jeżeli procedura MsgReceive() zostanie wywołana w sytuacji, kiedy nie ma żadnych wiadomości w kolejce do przetworzenia, wątek przejdzie w stan RECEIVE blocked i zostanie wstrzymany do momentu otrzymania wiadomości.
Po wykonaniu operacji związanych z obsługą otrzymanego komunikatu, serwer wykonuję procedurę MsgReply(), która przekazuje klientowi odpowiedź lub MsgError(), która przekazuje informacje o błędzie. Obie te funkcje powodują przejście klienta w stan READY, a więc wznowienie jego działania.

Przesyłanie danych
Obok synchronizacji działania realizowanej przez przechodzenie wątków w różne stany drugą kwestią jest sam sposób przesłania danych. Jeżeli ilość przesyłanych danych jest niewielka (najczęściej poniżej 256 bajtów) są one buforowane w przestrzeni jądra i w ten sposób przenoszone między przestrzeniami adresowymi dwóch wątków. W przeciwnym razie Neutrino tymczasowo mapuje fragment przestrzeni adresowej serwera w przestrzeni klienta co pozwala na przeniesienie danych między nimi w bardzo wydajny sposób. Dzięki temu prędkość kopiowania wiadomości jest zależna jedynie od przepustowości pamięci operacyjnej urządzenia. Neutrino pozwala także na wysyłanie wiadomości złożonych z bloków o różnych rozmiarach rozmieszczonych w różnych obszarach przestrzeni adresowej.
Kanały (channels) i połączenia
W QNX Neutrino wątek, który chce otrzymywać wiadomości musi utworzyć kanał (channel). Nadawca, przed wysłaniem danych nawiązuje połączenie z tym kanałem. W przestrzeni użytkownika zarówno kanały jak i połączenia są reprezentowane przez liczby całkowite. Dodatkowo liczby identyfikujące połączenia używa się zamiennie z deskryptorami plików. Do każdego kanału przyporządkowane są trzy struktury danych przechowujące informacje o klientach.

receive - bufor LIFO wątków oczekujących na wiadomość
send - kolejka (FIFO) wątków, które wysłały jeszcze nieodebrane wiadomości
reply - nieuporządkowana lista wątków, które wysłały wiadomość na którą nie udzielono jeszcze odpowiedzi

Podsumowanie
Oprócz wyżej wymienionych messages QNX wspiera także wiele innych rodzajów komunikacji międzyprocesowej. Są to sygnały, kolejki, pipes, pamięć dzielona, itp. Ich implementacja opiera się jednak głównie na messages, które to w najbardziej dobitny sposób przedstawiają cechy charakterystyczne QNX jeżeli chodzi o przesyłanie komunikatów. Wykorzystany został w nich przemyślany system synchronizacji, pozwalający uniknąć opóźnienia związanego z wywołaniem planisty CPU oraz mechanizm kopiowania danych wybierający optymalną metodę dla określonej ich ilości. Nie dziwi nacisk jaki położono właśnie na IPC, skoro jest to jeden z najważniejszych elementów mikrojądra QNX Neutrino.
</description><pubDate>Tue, 03 Feb 2009 22:22:38 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/02/03/komunikacja-miedzyprocesowa-w-qnx/</guid><category>Systemy operacyjne</category><category>Techblog</category><category>qnx</category><category>mikrojądro</category><category>ipc</category><category>messages</category><category>przestrzeń adresowa</category><category>komunikacja międzyprocesowa</category></item><item><title>Organizacja przestrzeni adresowej</title><link>http://pdziepak.quarnos.org/2009/01/13/organizacja-przestrzeni-adresowej/</link><description>Organizacja przestrzeni adresowej procesu jest jedną z istotniejszych decyzji projektowych, która może zaważyć na wydajności całego systemu. W praktyce okazuje się, że dla każdej architektury istnieje jedno najpopularniejsze i najczęściej także najlepsze rozwiązanie. Warto jednak przyjrzeć się jakie ciekawe mechanizmy oferują platformy mniej znane (choć nie zawsze rzadziej spotykane) niż x86.
IA-32 (aka x86)
W tej architekturze procesor udostępnia dwa mechanizmy zarządzania i ochrony pamięci. Są to segmentacja oraz stronicowanie. Pierwszy z nich jest niejako pozostałością po procesorach 16 bitowych i trybie rzeczywistym. Dodatkowo w trybie IA-32e segmentacja jest wspierana jedynie w ograniczonym zakresie. Wszystko to powoduje, że współczesne systemy operacyjne praktycznie nie korzystają z tego mechanizmu stosując model pamięci zwany protected flat model. Polega on na utworzeniu jedynie czterech segmentów (segmenty kodu i danych dla jądra i przestrzeni użytkownika), a ochrona pamięci jest realizowana przez stronicowanie.
Zdecydowana większość jąder systemów operacyjnych organizuje przestrzeń adresową procesów w ten sam sposób, najlepszy dla tej architektury. Polega to na umieszczeniu stron należących do programu w dolnej części przestrzeni adresowej a stron jądra w górnej części przestrzeni adresowej. Z tym że strony jądra są ustawione globalnie, a więc są takie same dla wszystkich procesów. Głównym powodem dla którego jądro jest zwykle umieszczone w wyższych adresach pamięci jest tryb emulacji 8086 (v86). Wymusza on aby kod działający w tym trybie znajdował się w pierwszym megabajcie pamięci. Gdyby kernel umieszczony w dolnych adresach pamięci, chciał udostępnić ten tryb programom, wprowadziłoby to dodatkowe komplikacje.
Dzięki takiemu rozwiązaniu kopiowanie danych z pamięci programu do pamięci jądra jest proste i wydajne. Także, przy okazji wywołań systemowych nie zachodzi potrzeba zmiany katalogu stron. Niestosowanie rozbudowanego systemu segmentacji pozwala ominąć problemy związane z innymi adresami tego samego obiektu w różnych kontekstach wykonania.
Najistotniejszą wadą jest ograniczenie ilości pamięci dostępnej dla jednego procesu. W zależności od systemu operacyjnego i jego konfiguracji są to 2GB lub 3GB w przypadku systemów 32 bitowych (oczywiście PAE nie ma wpływu na te wartości). Problem ten obecnie nie istnieje w przypadku systemów 64 bitowych, gdzie maksymalny rozmiar przestrzeni adresowej jest nieporównywalnie większy od ilości pamięci operacyjnej zainstalowanej w większości komputerów.
sun4u (aka UltraSPARC, SPARCv9)
Zupełnie inne rozwiązanie zastosował Sun Microsystems w swojej architekturze sun4u. Wprowadzono w niej mechanizm nazwany address space identifiers (ASI). Pozwala on na umieszczenie jądra w osobnej przestrzeni adresowej. Na tej platformie sprzętowej adres komórki pamięci składa się dodatkowo z ośmiobitowego ASI, który określa do której przestrzeni adresowej odnosi się dany adres. Dzięki temu kernel może kopiować dane między różnymi przestrzeniami adresowymi nie zmieniając tej głównej w której się znajduje oraz bez potrzeby aktualizacji rekordów w TLB.
Rozwiązanie zastosowane w sun4u pozwala na udostępnienie programom maksymalnego rozmiaru przestrzeni adresowej, zachowując przy tym prostotę i wydajność operacji kopiowania danych między pamięcią użytkownika a pamięcią jądra. Dodatkowo znika problem niewielkiej przestrzeni adresowej dostępnej kernelowi.
Warto także zauważyć, że powyższe rozwiązanie jest możliwe do implementacji na platformie x86. Pojawia się wtedy jednak problem mało wydajnego kopiowania danych miedzy kernelem a programami. Próba implementacji takiego sposobu organizacji przestrzeni adresowej wymagałaby ustawienia każdego przerwania wywołującego funkcje kernela jako task gate. Dzięki temu każdy przerwanie skutkowałoby zmianą przestrzeni adresowej i uruchomieniem kodu kernela. Następnie jądro mogłoby uzyskać dostęp do przestrzeni adresowych innych procesów odpowiednio mapując strony pamięci. Rozwiązanie to jednak wiąże się z dużą dodatkowych operacji i częstymi potrzebami aktualizacji TLB. Z tego powodu jest to właściwie tylko ciekawostka.
MIPS
Pewne różnice w stosunku do x86 można także zaobserwować w architekturze MIPS. Pamięć wirtualna jest podzielona na cztery segmenty z czego tylko jeden, o rozmiarze 2 GB jest dostępny dla programów. Pozostałe trzy segmenty są do użytku jądra.

Pierwszy z nich kseg0 jest stale zmapowany na pierwsze 512 MB pamięci fizycznej (w związku z czym nie ma potrzeby wykorzystywania TLB) i procesor stosuje co do niego pamięć cache. W tym segmencie znajdują się procedury obsługi przerwań (o ile system nie jest jeszcze w fazie ładowania). Warto zauważyć, że architektura MIPS wymaga od kernela obsłużenia każdego TLB miss.
Kolejny segment kseg1 tak samo jak poprzedni jest zmapowany na pierwsze 512 MB pamięci fizycznej. Jedyną istotną różnicą jest fakt, że procesor nie wykorzystuje w nim pamięci podręcznej. Dzięki temu po uruchomieniu CPU może bezpiecznie rozpocząć wykonywanie kodu zawartego w tym segmencie, bez ryzyka wystąpienia błędu spowodowanego niezdefiniowaną wartością w cache. Tak długo jak długo jest ustawiona flaga BEV, oznaczająca że system jest w fazie bootowania wszystkie procedury wyjątków i przerwań są wykonywane z adresów w tym segmencie. Po zakończeniu bootowania ten segment jest nieużywany.
Ostatnim segmentem jądra jest kseg2. Jądro ma w nim pełną dowolność w mapowaniu stron, wykorzystywany jest TLB oraz pamięć cache. Rozmiar tego segmentu to 1 GB.

Podsumowanie
Trzy opisane w tym tekście platformy dosyć odmiennie podchodzą do kwestii organizacji pamięci adresowej. sun4u oraz x86 dają stosunkowo duża dowolność pozwalając umieścić jądro zarówno w osobnej przestrzeni adresowej jak i w tej samej co program. x86 dodatkowo wspiera (nie licząc trybu IA-32e) segmentację. Dużo mniejsza dowolność jest w przypadku MIPS gdzie podział na segmenty jest odgórny. Inna sprawa, że w praktyce i tak wszystkie systemy (nie licząc tych eksperymentalnych) korzystają z tego jednego, najlepszego rozwiązania dla danej platformy.
</description><pubDate>Tue, 13 Jan 2009 21:55:01 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/01/13/organizacja-przestrzeni-adresowej/</guid><category>Hardware</category><category>Systemy operacyjne</category><category>Techblog</category><category>sparc</category><category>sun4u</category><category>address space identifier</category><category>x86</category><category>mips</category><category>kseg</category><category>przestrzeń adresowa</category><category>proces</category><category>kernel</category><category>jądro</category></item><item><title>Statyczny polimorfizm</title><link>http://pdziepak.quarnos.org/2009/01/06/statyczny-polimorfizm/</link><description>Głównym sposobem wspierania polimorfizmu w prawie wszystkich popularnych implementacjach C++, a także innych języków programowania, jest korzystanie z tablic funkcji wirtualnych vtables. Wiążą się z tym jednak pewne nieudogodnienia. Funkcje szablonowe nie mogą być jednocześnie funkcjami wirtualnymi, jest to co prawda całkiem logiczne, ale z drugiej strony czasami staje się problemem. Tego typu trudności można łatwo pokonać korzystając z poprzednio opisanych thin templates. Zupełnie inną klasą problemów jest narzut związany z wywoływaniem funkcji wirtualnych, a także sama obecność vtables, które przy rozbudowanych klasach mogą osiągnąć pokaźne rozmiary. Sposobem na pozbycie się spadku wydajności spowodowanego dynamiczną obsługą mechanizmu, także i w tej sytuacji jest statyczny sposób rozwiązania problemu. Warto zapoznać się ze statycznym polimorfizmem znanym także jako simulated dynamic binding.
Curiously recurring template pattern
Podstawą statycznego polimorfizmu jest wzorzec opisany przez Jamesa Copliena zwany curiously recurring template pattern (CRTP). Pozwala on na przekazanie za pomocą szablonów do klasy bazowej informacji o typie klasy pochodnej. Cały mechanizm najlepiej prezentuje poniższy przykład:
{geshi lang=cpp}class derived : public base {};{/geshi}Dzięki dodatkowej informacji jaką dysponuje klasa bazowa, ma ona dostęp do klasy pochodnej. Pozwala to na wygodne zaimplementowania wzorca singleton, licznika instancji klasy i wielu innych mechanizmów. Jak się okaże ten idiom jest także głównym elementem statycznego polimorfizmu. Można się także spotkać z podobnymi konstrukcjami typu:
{geshi lang=cpp}template
class derived : public base {};{/geshi}Pozwalają one między innymi na swobodne ustalanie hierarchii klas, a także na wprowadzenie mechanizmu wytycznych. Jak łatwo zauważyć wiele idei powiązanych z tak zwanym nowoczesnym C++ jest oparta właśnie na CRTP.
Simulated dynamic binding
Statyczny polimorfizm został spopularyzowany przez ATL oraz WTL. Polega on na wykorzystaniu przez klasę bazową informacji o typie klasy pochodnej do wywołania odpowiedniej metody. Przedstawia to poniższy przykład:
{geshi lang=cpp}
template
class base {
public:
	int metoda1() {
		T *ptr = static_cast(this);
		return ptr-&gt;metoda2();
	}
};

class derived : public base {
public:
	int metoda2();
};
{/geshi}Klasa bazowa jest w stanie wywołać funkcję odpowiedniej klasy pochodnej dzięki temu, że ma informację o niej. Po rzutowaniu this na odpowiedni typ T. Zostanie wywołana funkcja składowa właśnie tego typu. Oczywiście przedstawiony przykład nie posiada żadnego wsparcia dla sytuacji gdy programista źle użyje CRTP i jako T przekaże typ w tej sytuacji niepoprawny. Na wygodny sposób wygenerowania w takiej sytuacji czytelnego komunikatu o błędzie przez kompilator, trzeba niestety czekać do pojawienia się C++0x i concepts.
Statyczne składowe klasy
Jedną z najciekawszych rzeczy na jakie pozwala ten mechanizm jest polimorfizm statycznych funkcji składowych. Oto przykład analogiczny do poprzedniego, z tym że wykorzystane są statyczne funkcje składowe:
{geshi lang=cpp}
template
class base {
public:
	static int metoda1() {
		return T::metoda2();
	}
};

class derived : public base {
public:
	static int metoda2();
};
{/geshi}Dzięki temu, statyczne funkcje składowe klasy bazowej mogą bez przeszkód wywoływać statyczne funkcje składowe klasy pochodnej. Daje to większe pole manewru przy projektowaniu między innymi fabryk obiektów. Patrząc na to bardziej ogólnie, pozwala na korzystanie w statycznych funkcjach składowych z dobrodziejstwa programowania zorientowanego obiektowo jakim jest polimorfizm.
Konsekwencje stosowania statycznego polimorfizmu
W zaprezentowanym kodzie nie są wykorzystywane, dynamiczne elementy implementacji języka, a w szczególności funkcje wirtualne. Dzięki temu problem narzutu związanego z wywoływaniem wskaźników do funkcji jest rozwiązany, tak samo jak problem zwiększania się rozmiaru pliku binarnego przez obecność wielu vtables. Także rozmiar pojedynczej instancji klasy zmniejsza się o rozmiar wskaźnika na danej platformie, co może mieć duże znaczenie, w przypadku gdy gromadzone są pokaźne ilości obiektów. Oczywiście nie można tutaj pominąć także możliwości zastosowania tego mechanizmu dla funkcji statycznych tym bardziej, że jest to rzecz niemożliwa do zrealizowania jedynie za pomocą funkcji wirtualnych.
Nie sposób jednak nie powiedzieć o wadach tego rozwiązania. Gdy stosujemy funkcje wirtualne, kompilator wykonuje całą pracę związaną z implementacją polimorfizmu za programistę. Tutaj jest inaczej, więcej kodu należy napisać ręcznie, co może go czasami niepotrzebnie skomplikować. Nie można w bezpieczny sposób gromadzić instancji różnych klas pochodnych dziedziczących po tych samych klasach bazowych, w tej sytuacji klasy bazowe base, base są zupełnie różnymi klasami. Dlatego też jakiekolwiek rzutowania na klasę bazową mijają się z celem, ponieważ i tak nie ma możliwości na pozbycie się informacji o klasie pochodnej. Sprawia to, że główny zakres wykorzystania statycznego polimorfizmu to wywoływanie funkcji składowych klasy pochodnej z wnętrza klasy bazowej. To wyjaśnia dlaczego technika ta upowszechniła się głównie w API takich jak ATL i WTL.
Podsumowanie
Statyczny polimorfizm na który można natknąć się także pod nazwami simulated dynamic binding, ATL style inheritance lub upside down inheritance, jest niewątpliwie bardzo ciekawą techniką. Z drugiej strony należy pamiętać, że zdecydowanie nie nadaje się on do używania przy każdej możliwej okazji. Jest to mechanizm, który należy stosować wtedy kiedy potrafi przynieść duże korzyści a jego wady nie są istotne. W każdej innej sytuacji niepotrzebnie zaśmieca kod i utrudnia życie programistom.
</description><pubDate>Tue, 06 Jan 2009 18:17:42 +0100</pubDate><guid>http://pdziepak.quarnos.org/2009/01/06/statyczny-polimorfizm/</guid><category>C/C++</category><category>Programowanie</category><category>Techblog</category><category>statyczny polimorfizm</category><category>curiously recurring template pattern</category><category>crtp</category><category>ATL style inheritance</category><category>simulated dynamic binding</category><category>upside down</category><category>WTL</category></item><item><title>Thin templates</title><link>http://pdziepak.quarnos.org/2008/12/29/thin-templates/</link><description>Jednym z najbardziej oczywistych zastosowań szablonów jest implementacja różnego rodzaju struktur danych. Zostało to wykorzystane na szeroką skalę w bibliotekach takich, jak chociażby STL. Z szablonami wiążą się jednak pewne problemy, które nie zawsze ujawniają się na architekturze x86, ale są dużą przeszkodą w przypadku systemów wbudowanych. Chodzi tutaj głównie o nadmierny wzrost objętości plików binarnych. Jednym ze sposobów na poradzenie sobie z tym jest zastosowanie idiomu thin template.
Uzasadnienie
Niżej przedstawiona klasa stack implementuje prostą kolejkę LIFO. Interfejs tej klasy wykorzystuje szablony w celu zapewnienia bezpieczeństwa typologicznego. Ewentualne prywatne składowe klasy zostały pominięte, ponieważ nie są istotne.
{geshi lang=cpp}template
class stack {
public:
	void push(T 
	T 
};{/geshi}Obiekty tej klasy można wykorzystywać na wiele różnych sposobów w innych fragmentach kodu. Istotny jest jednak fakt, że dla każdego typu dla którego szablon jest konkretyzowany, kompilator wygeneruje osobny kod metod tej klasy. Może to doprowadzić do znacznego wzrostu rozmiaru kodu i w rezultacie zmniejszenia wydajności, a w przypadku systemów wbudowanych, może to stanowić poważną przeszkodę we wdrażaniu takiej aplikacji.
Szablony są kłopotliwe także w sytuacji, gdy biblioteka, która wykorzystuje ten mechanizm nie ma mieć ogólnodostępnego kodu źródłowego. Co prawda tutaj pewnym udogodnieniem jest słowo kluczowe export, ale z tych bardziej znanych kompilatorów tylko jeden je obsługuje. Nie jest to więc wystarczająco dobry sposób na zamknięcie kodu biblioteki.
Przy dużych projektach szablony mogą także skutecznie wydłużyć czas kompilacji programu. Innym problemem są także bardzo nieczytelne komunikaty o błędach generowane przez większość kompilatorów w sytuacji gdy pojawi się jakiś problem w samej implementacji klasy (w tym przypadku stack) lub kodu klienckiego.
Rozwiązanie
Rozwiązaniem powyższych problemów jest właśnie idiom języka C++ zwany thin template. Został on spopularyzowany w systemie Symbian. Mechanizm polega na przeniesieniu implementacji do innej, nieszablonowej klasy. Przykładowa klasa stack z poprzedniego listingu, po zastosowaniu thin template może wyglądać tak:
{geshi lang=cpp}class stack_base {
public:
	void push(void *);
	void *pop();
}

template
class stack : private stack_base {
public:
	inline void push(T 
	}
	inline T 
	}
};{/geshi}Klasa stack_base zawiera całą implementację struktury danych i jako nieszablonowa może być umieszczona w osobnym pliku binarnym, co rozwiązuje problem zamkniętego kodu oraz długiego czasu kompilacji. Niezależnie od ilości konkretyzacji klasy stack dla różnych typów, wykorzystywana jest zawsze kod metod klasy stack_base, więc rozmiar pliku wynikowego powinien być znacznie mniejszy. Jedynymi zadaniami klasy szablonowej jest wykonanie odpowiednich rzutowań oraz przechowywanie informacji o typie.
W powyższym przykładzie metody stack::push i stack::pop są rozwijane w miejscu wykonania. Pozwala to w pewnych przypadkach na ograniczenie rozmiaru kodu, o ile same rzutowania się stosunkowo proste. Jest to spowodowane faktem, że w kodzie klienckim bezpośrednio wstawiane są wywołania metod klasy stack_base, a nieskomplikowane rzutowania nie wymagają generacji dodatkowego kodu.
Oczywiście korzystanie z metod rozwijanych w miejscu wywołania jest opcjonalne i w przypadku gdy metody klasy stack będą zmuszone wykonywać dodatkowe operacje, lub skomplikowane rzutowania przed wywołaniem metod klasy bazowej, może się okazać, że lepszym rozwiązaniem będzie pominięcie tego słowa kluczowego.
Podsumowanie
Thin templates jest niewątpliwie bardzo praktycznym mechanizmem, który pozwala znacząco zmniejszyć objętość kodu. W znakomitej większości sytuacji nie posiada, żadnych znaczących wad, poza tym, że zmusza do napisania implementacji danej klasy bez korzystania z dobrodziejstw statycznej kontroli typów. Thin templates mimo że tak często prezentowane na przykładzie struktur danych, mogą być wykorzystane w wielu innych sytuacjach, gdzie używa się szablonów, głównie do zapewnienia odpowiedniego bezpieczeństwa. W każdym razie, warto wiedzieć o takim mechanizmie, który pomaga rozwiązać część problemów związanych z szablonami w C++.
</description><pubDate>Mon, 29 Dec 2008 16:58:58 +0100</pubDate><guid>http://pdziepak.quarnos.org/2008/12/29/thin-templates/</guid><category>C/C++</category><category>Programowanie</category><category>Techblog</category><category>thin templates</category><category>szablony</category><category>c++</category><category>idiom</category><category>symbian</category></item></channel></rss>