czwartek, 17 lutego 2011

This is Sparta!

Na wstępie chciałem podziękować za zainteresowanie projektem kilku osobom, które mnie zmotywowały do ujawnienia poniższych informacji :). Wpis proszę traktować jako ciekawostkę, w najbliższym czasie (co najmniej do wakacji) nie planuję żadnych aktualizacji, wpisów ani akcji informacyjnych o Sparcie. Muszę się w pełni skoncentrować na prowadzonym projekcie (mort-game.com) i pisaniem pod niego komponentów GCL i usprawnianiem narzędzi, a takie artykuły, prezentacje i tworzenie filmów pochłania spore ilości czasu. Poza prowadzonym projektem, jeśli, nie uda się nawiązać współpracy z Embarcadero i tak nie ma sensu publikowanie jakichkolwiek technicznych informacji, albowiem sukces/ewentualne wypuszczenie na rynek Sparty zależy od likwidacji kilku kluczowych błędów w kompilatorze i ograniczeń IDE (sytuacją niedopuszczalną byłoby dla mnie wypuszczenie do użytku samego prototypu, czy niekompletnego i wadliwego środowiska).

Kiedyś miałem sen. Śniłem o środowisku programistycznym Delphi dedykowanym dla programowania gier. Silniki to za mało. Chciałem czegoś więcej, przez ostatnią dekadę liczyłem na to, że Borland/Codegear/Embarcadero zrobi krok w tym kierunku. Niestety, w życiu często bywa tak, że mamy tylko to co zrobimy sobie sami :)

Zrzut z IDE powinien wyjaśnić mniej więcej, o czym będę pisał w dalszej części wpisu:

http://img513.imageshack.us/img513/238/newlife.jpg

Na początek krótkie zestawienie najważniejszych elementów oferowanych przez Spartę:

*Brak VCL (! jednak można go podłączyć jako hosta).
*3D (OpenGL) w RAD Studio.
*GCL (Graphics/Game Component Library) w miejsce VCL (nie jest to nakładka na VCL czy zbiór komponentów jak GLScene, tylko nowy, niezależny system).
*Własne designery i modyfikatory
*Fizyka 2D/3D, GUI (i inne mogą) być edytowane jak formy w VCL.
*Własny system komponentów niezależny od istniejącego (nowe komponenty, moduły danych - tu moduły danych są rozumiane jako moduły na poziomie logicznym Form w VCL - mogą być tworzone w sposób znacznie prostszy i szybciej niż w tradycyjnie).
*Kompilator GLSL zintegrowany z RAD Studio
*GCL jest kompatybilny z GeForce i Radeon
*Zaawansowana i prosta binarna serializacja dla wszystkich typów danych (dosłownie wszystkich, serializator bazuje na nowym RTTI)
*Rekordy w "Object Inspector" i w plikach *.dfm (bazuje na nowym RTTI)
*Pełna integracja skryptów LUA z Delphi (bazuje na nowym RTTI)
*GCL jest gotowe na MacOSX i Linux (jako hosta używa obecnie SDL)
*Wszystkie zdarzenia komponentów mogą być kojarzone ze skryptami (dodatek ten nie ma wpływu na wydajność)
*Dodatkowe moduły pomocnicze do OTA
*Biblioteka matematyczna wprowadza szereg struktur i funkcji dla wektorów, macierzy, kwaternionów, kolizji, interpolacji etc.
*Zintegrowane narzędzia w RAD Studio do zarządzania projektem i budowania nowych elementów Sparty w Sparcie

Sparta IDE

Nad Spartą pracują dwie osoby ja (aka. HNB) i Krystian Komisarek (aka. Spider). Krótka charakterystyka:
• HNB - OTA integracja z Delphi, pomysł na całe IDE i wykonanie, projekt i implementacja GCL
• Spider - Specjalista od OpenGL, tworzenie nowych komponentów dla GCL, wiele kluczowych pomysłów

Dlaczego powstała Sparta?

1. Sparta powstała by w prosty, przejrzysty i uporządkowany sposób realizować projekty gier/wizualizacji

2. Mieszanie VCL i OpenGL jest niewygodne. Kontrolki windowsowe często nie pasują wizualnie do grafiki wyświetlanej w widoku 3D OpenGL. Często koniecznym jest tworzenie nowych komponentów w samym VCL, integracja między światem OpenGL a VCL jest uciążliwa (bardzo odczuwalne przy tworzeniu bardziej skomplikowanych edytorów i aplikacji).

3. Sprawdzony i działający kod, najaktualniejsze moduły do grafiki. Nie trzeba szukać frameworków po internecie i zastanawiać się czy użyty framework OpenGL jest nowoczesny i właściwy.

4. Wygoda kształtowania GUI. VCL został stworzony dla kontrolek Windowsowych co niesie z sobą wiele problemów i ograniczeń przy łączeniu z OpenGL. GUI z GCL zostało zaprojektowane od zera specjalnie do integracji z działającym w tle 3D. Ma trochę wspólnego z VCL (łatwość w użyciu), eliminuje częć rzeczy uciążliwych i jednoczenie wprowadza różne udogodnienia, co więcej działa bezpośrednio w OpenGL.

Czym jest Sparta?

Po pierwsze Sparta to nie tylko GUI dla OpenGL. Po drugie jedyne wspólnego co ma GCL (Sparta) z VCL (Delphi) to IDE, wygoda w użyciu i kompilator. Poniżej w podpunktach zawrę to co istotniejsze

1. Moduł na poziomie formy z VCL w GCL zyskał zupełnie nowe znaczenie. Dla ułatwienia nazwijmy te moduły na poziomie formy, Kontrolkami GCL.

2. Kontrolki GCL to między innymi : pulpit, forma, main game, fizyka 2D, menager tekstur, timer, scena, teren, particle...

3. Kontrolki GCL są zebrane w grupy : GUI (pulpit, forma) Default (main game) itd.

4. Każda z kontrolek GCL zawiera swój unikalny system komponentów (!) widoczny na palecie komponentów tylko w chwili gdy jest aktywna zakładka designera z daną kontrolką GCL. Przykładowo dla formy GUI z GCL mamy komponenty jak w VCL (buttony, listy, różne niewizualne komponenty itd.)

5. Każda z kontrolek GCL może definiować własne selektory (coś co decyduje czy dany komponent został zaznaczony) i modyfikatory (coś co wizualnie modyfikuje komponent, przesuwa go/skaluje) dla różnych komponentów i ich grup (zaznaczonych kilku komponentów).

Co daje Sparta?

1. Kontrolki GCL w przeciwieństwie do form z VCL mają liczniki referencji. Z poziomu IDE można tworzyć powiązania między kontrolkami GCL. W dużym skrócie programista może decydować które kontrolki GCL dla których mają być automatycznie stworzone/aktywne. Dzięki temu nie są ładowane wszystkie kontrolki już na starcie programu a jedynie wtedy gdy są potrzebne innej kontrolce GCL.

2. Tworzenie w usystematyzowany sposób wiele programów wirtualnych w jednym programie. Przykładem mogą być formy poprzypisywane do pulpitów. Można w prosty sposób aktywować aktualnie potrzebny pulpit. Również z poziomu projektu programistycznego można ładnie uporządkować formy GUI po katalogach danego pulpitu.

3. Koncepcja nazewnicza. Projekt tej skali wymagał stworzenia odpowiedniego podziału modułów, warstw i przedrostków i przyrostków. Istnieje wiele modułów zbiorczych i nazewniczo podobnych do tych z VCL/RTL np. gclClasses, guiClasses, dtxUtils. Doświadczony programista Delphi powinien szybko się odnaleźć w środowisku Sparty.

4. Granicą jest ludzka wyobraźnia, mając w miejscu designera form okno OpenGL można tworzyć nawet inne biblioteki aniżeli GCL, VCL... Np. do tworzenia grafów etc.

Warstwy Sparty

Projekt tego rozmiaru wymagał usystematyzowania i uporządkowania.

• Warstwa 1 (aka. SpartaRTL)
Własne moduły pomocnicze wypełniające braki w RTL. W ich skład wchodzą moduły realizujące/dostarczające mi.: serializację, matematykę, skrypty

• Warstwa 2 (aka. SpartaEngine)
Silnik gry. Istnieją różne silniki do gier. Używając samej warstwy 2 i 1 można tworzyć pełnowartościowe gry komputerowe i wizualizacje w tradycyjny sposób jak ma to miejsce w przypadku zwykłych silników, używając różnorodnych klas do kształtowania wirtualnej rzeczywistości.

• Warstwa 3 (aka. otaUtils)
Warstwa nabudowana na ToolsAPI. Znacząco upraszcza i automatyzuje użycie interfejsów z ToolsAPI. W planach jest stworzenie komponentów do VCL korzystających z warstwy 3, przeznaczonych tylko i wyłącznie do tworzenia bibliotek *.bpl dla IDE.

• Warstwa 4 (aka. Sparta IDE)
Korzystając z warstwy 3, zostało podłączone OpenGL do RAD Studio i został stworzony alternatywny system komponentów, oraz zintegrowane narzędzia w IDE (np. kompilator shaderów GLSL, wystarczy je dodać do projektu jak zwykłe pliki z kodem).

• Warstwa 5 (aka. GCL)
Zostało omówione w podpunkcie : „Czym jest Sparta?”

• Warstwa 6
Konkretne kontrolki GCL: GUI, Physics, RTS Maps, FPS... Kontrolki np. Physics mogą korzystać w swoich designerach np. z kontrolek już obecnych w Sparcie np. z GUI.

• Warstwa 7 (w planach, jednak wszystko jest pod to pisane)
Możliwość edycji kontrolek GCL w czasie działania programu (!). Selektory, modyfikatory i cały system komponentów może być przeniesiony w program. W planach jest stworzenie IDE podobnego do Delphi działającego bezpośrednio w OpenGL i w uruchomionej aplikacji/specjalnym trybie edycji. Zamiast kodu Delphi pisałoby się w skryptach LUA (lub innych), analogicznie do tego jak ma to miejsce w Delphi (zdarzenia, object inspector itd.). Nowe eventy mogą współdziałać z tymi napisanymi w Delphi, bądź mogą je nadpisywać. Możliwe by było dodawanie nowych komponentów z poziomu tego właśnie IDE do już istniejących kontrolek GCL.

Jak jest tworzona Sparta

Sparta jest tworzona równolegle z dużym projektem gry komputerowej, nie jest to środowisko pisane samo dla siebie, a hartowane na realnych zastosowaniach:

http://serwis.gamedev.nazwa.pl/screens/18a77df995aaccbcf628808f268d8d3a.jpg
http://mort-game.com
http://www.youtube.com/watch?v=za_JtzO6QGI

Stopień ukończenia Sparty

Sparta to działający prototyp. Kluczowe elementy środowiska programistycznego zostały zaimplementowane. Obecnie rozwijana jest głównie strona samego silnika gier i nowych komponentów GUI na potrzeby projektu Mort i dodatkowe funkcjonalności do IDE przyspieszające proces powstawania gry. Fajerwerki do planowanych prezentacji typu - podłączony nasz autorski silnik fizyczny pod designery - zeszły na dalszy plan (jakieś dodatkowe 2 tygodnie być może niepotrzebnej na chwilę obecną pracy). Jesteśmy w pełni skoncentrowani na kodzie dla naszej gry RTS.

Przyszłość Sparty

Pomimo zamieszczenia tematu i informacji o Sparcie moim łamanym angielskim ;) (link), nie doczekałem się niestety ani jednego komentarza od któregokolwiek pracownika na forum ani na PM...

Jedyna alternatywa dająca zbliżone możliwości do Delphi to platforma .NET i język C#. Wiele myślałem o przepisaniu Sparty, początkowo na Delphi Prism a później na C#, jednak to wymaga dodatkowego czasu a i efekt takiego zabiegu stoi pod znakiem zapytania. Rozwijana Sparta, nawet jeśli nigdy nie stanie się produktem do kupienia, może się opłacić istniejąc jako narzędzie do wewnętrznego użytku, ze względu na nasze przyszłe projekty i możliwość kompilacji na pozostałe systemy operacyjne w całkiem niedalekiej przyszłości (MacOSX, Linux). Do tego dochodzi Delphi Prism, a stąd droga na XBOXA czy najnowszy projekt "Cooper" czyli kompilator Delphi Prism tworzący "natywne" pliki javy mi. dla Androida. Nie wspomnę już nawet o planach stworzenia backendu kompilatora dla ARM :).

Drogi są dwie : nasze własne prywatne IDE, albo współpraca z Embarcadero i kto wie? :)

Na zakończenie kilka smaczków :). Skąd nazwa Sparta? Nazwa nie jest przypadkowa.

Geneza nazwy

Za twórcę Sparty jako bytu politycznego uważa się prawodawcę Likurga. Nie wiadomo, czy jest on postacią historyczną, bowiem już w czasach starożytnych uchodził za postać niemal mityczną. Żywot Likurga pióra Plutarcha zawiera krótki tekst traktujący o ustroju Sparty. Jest to tak zwana Wielka Rhetra napisana w dialekcie doryckim. Likurg miał jakoby otrzymać owe prawa od wyroczni delfijskiej, co znacznie podnosiło ich prestiż. Prawa Likurga miały zakończyć okres walk i niepokojów w Sparcie (około VIII w. p.n.e.).


Zaznaczyłem wyroczni delfijskiej gdyż jest to klasyk. Nazwa Delphi, środowiska (IDE) w którym programujemy i języka który też jest nazywany Delphi, wywodzi się z pogranicza greckiej mitologii i historii. Delphi powstało głównie z myślą o bazach danych, stąd jest szybkie i stabilne, przejrzyste, tym samym przy okazji nadaje się do pisania gier. Głównym targetem Delphi była baza danych "Oracle", stąd też Delphi zostało nazwane Delphi - Delphi oracle było jedną z najistotniejszych świątyń/wyroczni w starożytnej grecji.

Tutaj mamy obraz jednej z Delfijskich kapłanek:


Wracając do cytatu - poniekąd też dostaliśmy prawa od Delfi. Całość jest ściśle zintegrowana z językiem Delphi i IDE, dzięki czemu uzyskaliśmy własnego "clickera" do tworzenia gier.

Co więcej Delphi zawsze towarzyszą Ateny. Motyw Aten najwidoczniejszy jest w ikonkach starszych wersji i obecnie w ikonce RAD Studio 2010. Objawia się w grafice Partenonu w wersji demo (ma trochę mniej kolumn - może to wcale nie Partenon?):



Przy okazji przedstawiam ikonkę samego Delphi:



Co mają Ateny do naszej Sparty?

W wojnach perskich na początku V w. p.n.e. Sparta (miasto o pow. 300 ha i 8 tys. mieszkańców) była połączona sojuszem z Atenami – Termopile. W połowie stulecia doszło jednak do konfliktu między tymi najsilniejszymi greckimi polis. Wojnę tę, zwaną peloponeską wygrała Sparta, dzięki czemu zdobyła hegemonię w Helladzie. Zarówno Ateny jak i Sparta na przemian były opłacane przez Persję.


Po prostu Sparta to idealne wpasowanie się w klimaty - tego co robimy i języka/środowiska pod które piszemy.

Co jest symbolem Sparty? Otóż jest nim grecka litera Lambda, od Lakonii, przedstawiany najczęściej na tarczach. Oficjalne logo stworzone przez naszego grafika Ftorka jest widoczne na samym początku wpisu, nawiązuje do samej lambdy jak i tarczy:



Nazwa i charakter teamu DaThoX. Jesteśmy jak spartanie.

Poza klasycznym tekstem Leonidasa "This is Sparta!" warto nadmienić:

Państwem spartańskim zachwycała się oligarchia ateńska oraz filozofowie na czele z Platonem. Zachwycała ich prostota życia, jasność prawa oraz wysokie morale społeczeństwa mające swój wyraz w poświęceniu się całkowicie ideałowi wielkości Sparty. Do dziś jej historia jest klasycznym w swej oryginalności wycinkiem historii Hellady.


;)

Życie spartanina było ciągłym zmaganiem się z własnymi słabościami. Trening zaczynał się od dnia narodzin


Pierwsza wersja w chwili gdy nie było do końca wiadome czy uda się podłączyć pod RAD Studio w sposób oczekiwany, nazwa kodowa brzmiała "Termopile".

Bitwa została przedstawiona w filmie 300. Cóż. Mieliśmy podobną sytuację.

Spartanie dowiedli wówczas, że wolą zginąć niż się poddać


My jednak łamiąc historyczną tradycję żyjemy, a nasz prototyp działa. :)

PS. Sparta uchodziła za najpotężniejsze państwo starożytnej Grecji:

http://www.spartan3.republika.pl/sparta.htm

Pozdrawiam wszystkich, którzy przebrnęli przez ten cały długi wpis :)!

czwartek, 6 stycznia 2011

Cisza w eterze

Prawie pół roku bez postów :). Pracuję od wielu miesięcy nad bardzo dużym projektem. Całość ma już ponad 150k LOC. Niebawem będę mógł ujawnić nieco więcej szczegółów o prowadzonych od czerwca pracach. Sądzę, że to będzie wielkie zaskoczenie dla wszystkich, zwłaszcza dla programistów Delphi :).

Przez ten cały czas zdążyło wyjść "RAD Studio XE" a w nim masa naprawionych błędów z wersji poprzednich (liczone w tysiącach). Jednak szału nie ma. Po pierwsze, żadnej większej nowości w składni. Np. {$STRONGLINKTYPES ON} obecne już w Delphi 2010 tylko nieudokumentowane (strasznie nie lubię tej dyrektywy ponieważ ma działanie na wszystkie deklarowane typy w projekcie i nie można "silnie zlinkować" wybranych klas, wielka szkoda...).

Pomimo, że mam licencję na XE, nie używam go. Dodanie jednej funkcjonalności do RTTI to stanowczo za mało. TVirtualMethodInterceptor - klasa umożliwiająca przechwycenie/zastąpienie dowolnej metody wirtualnej w instancji klasy to trochę za mało. Dodano też moduł do wyrażeń regularnych i klika mniejszych usprawnień.

Podstawową przeszkodą w używaniu DelphiXE jest debugowanie IDE w IDE... Nowa wersja przysporzyła mi kilku kłopotów np. w OTA zmieniając zachowania niektórych elementów. Nie wspomnę już o beznadziejnej dołączonej do Delphi wersji (DEMO! WTF?) AQTime 7 która wywala się na moich projektach -,-...

Jako ciekawostkę przytoczę post mówiący o historii kompilatora Delphi, napisany przez Allena Bauera (Embarcadero Chief Scientist):

It is mostly C, with very little C++. The Delphi32 bit compiler was a port from a (at the time) Borland Pascal compatible Atari ST compiler that targeted the 68000 CPU. It was called "Pure Pascal." Borland purchased the IP and contracted with the original author to create an x86 backend. While much of the original architecture remains, it is so significantly different that it hardly looks like the original code.

I personally worked with the original author for many months to get the Borland Pascal RTL and Turbo Vision building for 32bit. That work was put aside and things shifted to the new fledgling Delphi project. Even while the Delphi project was in full swing and initially targeted Windows 16bit, work continued on the 32bit compiler. That work had also shifted to add the Delphi specific language features.

The original Turbo Pascal compiler targeted the Z80 CPU and was written in Assembler. At the behest of Philippe Kahn, Anders Hejlsberg ported the assembler to 8086 and targeted the same. Because the 8086 is an evolution of the 8080A (as is the Z80), it shared many of the same opcodes as the 8080A/Z80/8085, so the port was fairly mechanical. This is also why that up through Turbo Pascal 3.0 the compiler would only
create .COM file, which was limited to 64K.

The Turbo Pascal/Borland Pascal/Delphi compilers have never been written in Pascal. Not because it isn't possible, but was simply not practical or economical. Academically, yes, many times it is the goal of the compiler author to get the compiler to the point where it can compile itself. This is usually the first "real-world" use of a new compiler and serves as a validation suite. However, the world of
commercial software has more practical and economic factors that weigh heavily in the development process.


Na deser:

mały obraz z Delphi64 bit (ma wyjść w tym roku) i CHASM (nie ma już BASM):

sobota, 24 lipca 2010

Nowy kompilator Delphi

Huh. Rzeczywistość zaczyna mnie coraz bardziej zadziwiać - nowy kompilator Delphi zapowiada się rewelacyjnie - właściwie to dwa kompilatory. Po pierwsze obecny kompilator jest przepisywany/został przepisany z C (>300K kodu) do C++ by łatwiej było nim zarządzać i naprawiać błędy/wprowadzać nowe właściwości.

Stary kompilator jest trzymany (i będzie) by zachować wszystkie właściwości języka jakie znamy, wszystkie jego niekiedy specyficzne zachowania by można było łagodnie przejść na nowy język Delphi.

Mam nadzieję, że chodzi tylko o zachowanie starego front-end kompilatora, czyli części odpowiedzialnej za składnię, ale z nowym back-endem, czyli częścią generującą kod maszynowy.

Nowy kompilator ma być znacznie elastyczniejszy. Nie tyle ciekawy jestem nowej składni co ostatnio zwłaszcza back-endu:

With a back end compiler architecture - we are not limited to just Intel (x86, x64) only. It will be possible to plug in any number of optimizers and code emitters - whether this work is done by Embarcadero engineers, chip manufacturers or other engineers and community members.


jak to napisał David Intersimone z Embarcadero (Vice President of Developer Relations and Chief Evangelist). WOW!

Kompilator Delphi na Xboxa, PS3, Gameboya, do plików SWF, do Javy ? Jeśli nie ruszy tego Embarcadero, wszystko będziemy mogli stworzyć my - community members!

niedziela, 2 maja 2010

Delphi i TStringGrid cz.1

Kolejny raz pisząc projekt natrafiłem na seryjnie powtarzające się problemy z StringGridem. Najpopularniejsze braki występujące w komponencie to:

1. Wstawianie kolumny/wiersza
2. Kasowanie kolumny/wiersza
3. Wykrywanie zdarzenia zmiany rozmiaru kolumny/wiersza

Założenia

Dostępne w sieci przykłady nie spełniły moich oczekiwań - skupiają się głównie na pisaniu nowego komponentu udostępniającego jedne z powyższych funkcjonalności. Ja nie lubię specjalnie zaśmiecać palety komponentów, stąd też staram się ograniczać i używać tego co mam.

Poniższe rozwiązania nie wymagają instalowania / tworzenia komponentu a jedynie kilku zmian w kodzie modułu z formą.

Przygotowania

Zanim przystąpimy do omawiania powyższych podpunktów, musimy zastosować technikę zwaną "podrabianiem klasy" (ang. Class Spoofing) - bardzo rzadko stosowana, acz bardzo użyteczna tachnika pozwalająca nam uzyskać dostęp do pól i metod z sekcji protected. Standardowa notacja kodu używana w Delphi nie przewiduje specjalnych oznaczeń, jednak ja konsekwentnie używam przedrostka CS. Należy zadeklarować następujący typ który posłuży nam w dalszej części artykułu:

type // class spoofing
TCS_StringGrid = class(TStringGrid);



1. Wstawianie kolumny/wiersza

W razie potrzeby należy samodzielnie dodać kod zabezpieczający przed wyjściem z zakresu parametrów AColumn i ARow.

procedure TfMain.InsertColumn(AStringGrid: TStringGrid; AColumn: integer);
Begin
with TCS_StringGrid(AStringGrid) do
begin
ColCount := ColCount + 1;
MoveColumn(ColCount - 1, AColumn);
Cols[AColumn].Clear;
end;
end;

procedure TfMain.InsertRow(AStringGrid: TStringGrid; ARow: integer);
begin
with TCS_StringGrid(AStringGrid) do
begin
RowCount := RowCount + 1;
MoveRow(RowCount - 1, ARow);
Rows[ARow].Clear;
end;
end;


2. Kasowanie kolumny/wiersza

procedure TfMain.DeleteColumn(AStringGrid: TStringGrid; AColumn: integer);
begin
with TCS_StringGrid(AStringGrid) do
begin
if ColCount - FixedCols = 1 then
Exit;
DeleteColumn(AColumn);
end;
end;

procedure TfMain.DeleteRow(AStringGrid: TStringGrid; ARow: integer);
begin
with TCS_StringGrid(AStringGrid) do
begin
if RowCount - FixedRows = 1 then
Exit;
DeleteRow(ARow);
end;
end;


Powyższe metody mogą zostać zaimplementowane w klasie formy, lub można w ramach treningu stworzyć klasę pomocniczą dla TStringGrida.

WAŻNE

Jeśli implementowaliśmy zdarzenie przesuwania kolumn/wierszy OnRowMove/OnColumnMoved, to powyższe implementacje mogą być dość problematyczne, ponieważ zostanie wywołany event przesunięcia, pomimo, że takowe "logicznie" nie nastąpiło. Należy się przed tym zabezpieczyć poprzez napisanie metod zawieszających i odwieszających te zdarzenia, oraz wrappery na funkcje dodające/kasujące wiersze/kolumny.

procedure TfMain.LockColRowMoveEvent;
begin
StringGrid1.OnRowMoved := nil;
StringGrid1.OnColumnMoved := nil;
end;

procedure TfMain.UnlockColRowMoveEvent;
begin
StringGrid1.OnRowMoved := StringGridRowMoved;
StringGrid1.OnColumnMoved := StringGridColumnMoved;
end;


a tu mamy wrappery na metody dodawania/kasowania, z których należy korzystać zamiast z InsertColumn, InsertRow, DeleteRow i DeleteColumn w przypadku wystąpienia zdarzeń przesuwania kolumn i wierszy dla danego StringGrida:

procedure TfMain.AddCol(ACol: integer);
begin
LockColRowMoveEvent;

InsertColumn(StringGrid1, ACol);

UnlockColRowMoveEvent;
end;

procedure TfMain.AddRow(ARow: integer);
begin
LockColRowMoveEvent;

InsertRow(StringGrid1, ARow);

UnlockColRowMoveEvent;
end;

procedure TfMain.DelCol(ACol: integer);
begin
LockColRowMoveEvent;

DeleteColumn(StringGrid1, ACol);

UnlockColRowMoveEvent;
end;

procedure TfMain.DelRow(ARow: integer);
begin
LockColRowMoveEvent;

DeleteRow(StringGrid1, ARow);

UnlockColRowMoveEvent;
end;


3. Wykrywanie zdarzenia zmiany rozmiaru kolumny/wiersza

Dla czego? Najodpowiedniejszym wyjaśnieniem jest synchronizacja z innym StringGridem np. znajdującym się nad/pod lub obok.

W przykładzie zostanie pokazana technika wykrywania zdarzenia dla kolumn, w razie potrzeby można dopisać analogiczne metody dla wierszy.

Pierwszym krokiem na drodze do wykrycia zdarzenia jest stworzenie klasy pomocniczej, której metody są używane w dalszym kodzie:

type
TStringGridHelper = class helper for TStringGrid
public
function RightCol: integer;
function BottomRow: integer;
end;

{...}

function TStringGridHelper.BottomRow: integer;
begin
Result := TopRow + VisibleRowCount - 1
end;

function TStringGridHelper.RightCol: integer;
begin
Result := LeftCol + VisibleColCount - 1;
end;


Następnym krokiem jest zadeklarowanie nowej klasy

TStringGridColResize = class(TStringGrid)
protected
procedure ColWidthsChanged; override;
end;


której implementacja wygląda następująco (kiedy chcemy synchronizować z innym StringGridem):

procedure TStringGridColResize.ColWidthsChanged;

procedure AlignColSize(ASource, ADest: TStringGrid);
var
i, RightCol: LongInt;
begin
if ASource.ColCount - ASource.FixedCols <> ADest.ColCount -
ADest.FixedCols then
Exit;

RightCol := ASource.RightCol + 1;
if RightCol = ASource.ColCount then
Dec(RightCol);

for i := ASource.LeftCol to RightCol do
ADest.ColWidths[i + (ADest.FixedCols - ASource.FixedCols)] :=
ASource.ColWidths[i];
end;

begin
if Self = fMain.StringGrid1 then
AlignColSize(fMain.StringGrid1, fMain.StringGrid2)
else if Self = fMain.StringGrid2 then
AlignColSize(fMain.StringGrid2, fMain.StringGrid1);

inherited;
end;


Jak widać nie wykrywamy która konkretnie kolumna podlega zmianie rozmiaru, a jedynie wykrywamy zdarzenie bez danych precyzujących, jak co i o ile.

Można oczywiście wykryć której kolumny rozmiar został zmieniony, poprzez przechowywanie rozmiarów kolumn w osobnej tablicy i porównanie ich z nowymi rozmiarami.

Technika jest wydajna nawet w przypadku dużej liczby kolumn, gdyż sprawdzamy rozmiary kolumn jedynie tych widocznych.

Teraz w zdarzeniu stworzenia formy należy podmienić tablicę VMT dla StringGridów, które mają być podatne na wykrycie zdarzenia zmiany rozmiarów kolumn.

procedure TfMain.FormCreate(Sender: TObject);
begin
// patch dla VMT - potrzebne zdarzenie ColResize
PPointer(StringGrid1)^ := TStringGridColResize;
PPointer(StringGrid2)^ := TStringGridColResize;
end;


Podsumowanie

Zaprezentowane techniki mogą się wydać dość uciążliwe jednak do większości zastosowań są bardzo użyteczne i nie sprawią problemów i co ważniejsze nie trzeba tworzyć/instalować dodatkowych komponentów.

W przypadku intensywnej eksploracji warto się pokusić o stworzenie własnego komponentu :).

[ŚCIĄGNIJ PRZYKŁADOWY PROGRAM]

Pozdrawiam Maciej "HNB" Izak

środa, 20 stycznia 2010

Jak wkompilować DLL do EXE

Bardzo stare (ma już z 5 lat) i epickie ^^ czyli:

jak wkompilować DLL do naszego programu.

--- INFO ---
DLLtoPAS jest wersją programu pod starą wersję DLLLoadera.
Przemielone nim moduły będą działać po drobnych przeróbkach.

Obecnie nie mam czasu, chęci ani potrzeby, na napisanie wersji
pod nowy unit.
Jeśli ktoś chce może napisać podobny program pod nową wersję
modułu :).

Jeśli go napiszesz, wyślij na email: hnb[małpa]dathox.com

a zrobię update + credits dla Ciebie.

--- USES ---
Unit jest prosty w obsłudze:

function AddDLL(Name:pchar;Data:pointer;Size:longword):PDLLFileListItem;
procedure RemoveDLLItem(Item:PDLLFileListItem);
procedure RemoveDLL(Name:pchar);
function PatchImports(hLibModule:hmodule):hmodule;

function FindDLLModulePerName(const Name:pchar):PDLLModule;
function FindDLLModulePerWideName(const Name:pwidechar):PDLLModule;
function FindDLLModulePerImageBase(const ImageBase:pointer):PDLLModule;
function FindDLLModulePerAddress(const Address:pointer):PDLLModule;

function GetProcAddressByOrdinal(Module:hmodule;Ordinal:longword):pointer;
function GetProcAddressByHash(Module:hmodule;Hash:longword):pointer;
function GetProcAddressByName(Module:hmodule;ModuleName:pchar):pointer;

function LoadDLLModule(ModuleData:pointer;const ModuleName:pchar;FileSize:longword):PDLLModule;
function FreeDLLModule(DLLModule:PDLLModule):boolean;



Jak to działa w praktyce (mniej więcej) możemy zobaczyć na przykładzie
przetrawionych modułów przez DLLtoPAS.

--- ABOUT ---
Dzięki dla Goriona za pomoc kilka lat temu w napisaniu DLLtoPAS i dla
autora DLL Loadera - BeRo

Pozdrawiam, Maciej HNB Izak
hnb[małpa]dathox.com

[LINK]

czwartek, 10 grudnia 2009

Serializacja klas cz1. cz.2 i cz.3

Zbiór 3 artykułów o serializacji klas w Delphi mojego autorstwa bazujący na mechanizmach używanych przez VCL. Obecnie (od Delphi 2010) serializacja jest możliwa w dużo bardziej prosty i oczywisty sposób o którym postaram się wkrótce napisać, jednak warto poznać ta "Old Schoolową" technikę. Wrzucam wszystkie 3 części:

najważniejsze procedury z dtxUtils używane w dalszym kodzie, bardzo przydatne podczas serializacji klas w Delphi
function ComponentToString(Component: TComponent): string;
var
BinStream:TMemoryStream;
StrStream: TStringStream;
s: string;
begin
BinStream := TMemoryStream.Create;
try
StrStream := TStringStream.Create(s);
try
BinStream.WriteComponent(Component);
BinStream.Seek(0, soFromBeginning);
ObjectBinaryToText(BinStream, StrStream);
StrStream.Seek(0, soFromBeginning);
Result:= StrStream.DataString;
finally
StrStream.Free;

end;
finally
BinStream.Free
end;
end;

procedure StringToComponent(Value: string; Comp: TComponent);
var
StrStream:TStringStream;
BinStream: TMemoryStream;
begin
StrStream := TStringStream.Create(Value);
try
BinStream := TMemoryStream.Create;
try
ObjectTextToBinary(StrStream, BinStream);
BinStream.Seek(0, soFromBeginning);
BinStream.ReadComponent(Comp);
finally
BinStream.Free;
end;
finally
StrStream.Free;
end;
end;

{$WARNINGS OFF}
procedure SaveComponent(AFile: string; AComponent: TComponent;
AText: boolean = true);
var
f: TFileStream;
m: TMemoryStream;
begin
if AText then
try
m := TMemoryStream.Create;
m.WriteComponent(AComponent);
m.Seek(0, soFromBeginning);

f := TFileStream.Create(AFile, fmCreate);
ObjectBinaryToText(m, f);
finally
m.Free;
f.Free;
end else
try
f := TFileStream.Create(AFile, fmCreate);
f.WriteComponent(AComponent);
finally
f.Free;
end;
end;
{$WARNINGS ON}

{$WARNINGS OFF}
procedure LoadComponent(AFile: string; AComponent: TComponent;
AText: boolean = true);
var
f: TFileStream;
m: TMemoryStream;
begin
if AText then
try
f := TFileStream.Create(AFile, fmOpenRead);
m := TMemoryStream.Create;

ObjectTextToBinary(f, m);
m.Seek(0, soFromBeginning);
m.ReadComponent(AComponent);
finally
m.Free;
f.Free;
end else
try
f := TFileStream.Create(AFile, fmOpenRead);
f.ReadComponent(AComponent);
finally
f.Free;
end;
end;
{$WARNINGS ON}


Część 1

(*******************************************************************************
Serializacja klas w Delphi cz.1

DaThoX 2004-2008

Maciej Izak (hnb.code[at]gmail[dot]com)


Serializacja – w programowaniu komputerów proces przekształcania obiektów,
tj. instancji określonych klas, do postaci szeregowej, czyli w strumień
bajtów, z zachowaniem aktualnego stanu obiektu. Serializowany obiekt może
zostać utrwalony w pliku dyskowym, przesłany do innego procesu lub innego
komputera poprzez sieć. Procesem odwrotnym do serializacji jest
deserializacja. Proces ten polega na odczytaniu wcześniej zapisanego
strumienia danych i odtworzeniu na tej podstawie obiektu klasy wraz z jego
stanem bezpośrednio sprzed serializacji.

Powyższa definicja pochodzi z wikipedii
(http://pl.wikipedia.org/wiki/Serializacja)

Serjalizacja w Delphi wygląda dużo bardziej estetycznie niż w innych językach
i moim zdaniem jest znacznie prostsza. Zresztą nie bedę się zbytnio rozpisywał
:) - przejdźmy do kodu

(******************************************************************************)

program Lesson_01;

{$APPTYPE CONSOLE}

uses
SysUtils,
Classes, // <- podstawowe klasy z których bedziemy dziedziczyć
Dialogs,
dtxUtils // <- napisałem procedury ułatwiające serializację klas
;

{-------------------------------------------------------------------------------
Generalnie sprawa jest prosta. Każda klasa którą chcemy zapisywać do pliku musi
dziedziczyć z TComponent. Oczywiście jeśli posiadamy odpowiednio duża wiedzę
możemy się pokusić o napisanie własnych klas do zapisu klas do pliku ale po co?

TComponent jest świetną klasą, zwartą i dzielącą/łączącą wszystko w jedną całość.
Możemy przyjąć że TComponent to Np. TGracz
{------------------------------------------------------------------------------}

type
// wrsja gracza 1 :) - później omówimy kolejne możliwości serializacji
// i nie chcę by ten tutek stracił czytelność
TPlayer1 = class(TComponent)
private
FName: string; // nazwa
FRememberPasswd: boolean; // możemy nie chcieć zapamiętywać hasła do pliku
//by go ktoś nie przechwycił
FPasswd: string; // hasło
public
// ---
protected
// ---
published
// zapis odbywa się dzięki property
property Name: string read FName write FName;
// kolejność property ma OGROMNE znaczenie. Przypuśćmy że pole
//
// property RememberPasswd
//
// byłoby za
//
// property Passwd
//
// wtedy zawartość Passwd mogłaby zostać nie wczytana ponieważ na końcu jej
// deklaracji mamy dyrektywę "stored" i nazwę pola "FRememberPasswd" co
// oznacza:
//
// "zainicjalizuj właściwość Passwd tylko jeśli pole FRememberPasswd
// ma wartość true"
//
// !!!oczywiście za stored możemy umieśćić funkcję (która zostanie wywołana
// podczas odczytu/zapisu)!!!
//
// Z tego wniosek, że za późno zinicjalizowalibyśmy wartość pola
// FRememberPasswd i przez to nie wczytalli hasła :(
property RememberPasswd: boolean read FRememberPasswd write FRememberPasswd;
property Passwd: string read FPasswd write FPasswd stored FRememberPasswd;
end;

{-------------------------------------------------------------------------------
Zaletą serializacji klas w delphi jest to że struktura klasy może się
!zmieniać! co w przypadku plików binarnych niosłoby ze sobą szereg zmian.

ZALETĄ zapisu przez property jest fakt że możemy za read/write wstawić metody
odczytu i zapisu co w połączeniu ze stored daje nam ogromne mozliwości!
{------------------------------------------------------------------------------}

{-------------------------------------------------------------------------------
Na chwilę obecną zanim omówimy bardziej zaawansowane aspekty serializacji w
Delphi sprawdźmy czy faktycznie działa...
{------------------------------------------------------------------------------}

var
Player: TPlayer1;
c: char;
begin
Player := TPlayer1.Create(nil);

Write('Podaj swoj nick : ');
ReadLn(Player.FName);

Write('Podaj haslo : ');
ReadLn(Player.FPasswd);

Write('Zapamietac twoje haslo na pozniej (T/N)? : ');
ReadLn(c);
Player.FRememberPasswd := UpCase(c) = 'T';

// przekształcamy obiekt w tekst :)
// zapisem zajmiemy się w nastepnej części
WriteLn(sLineBreak,

ComponentToString(Player)

);

// zakończ ...
ReadLn;
Player.Free;
end.


Część 2

(*******************************************************************************
Serializacja klas w Delphi cz.2

DaThoX 2004-2008

Maciej Izak (hnb.code[at]gmail[dot]com)

Na dzisiejszej lekcji zajmiemy się obsługą zapisu przypisanej metody do pola
(Delphi umożliwia zapamiętanie jaką metodę przypisaliśmy polu typu
proceduralnego obiektowego)

Poznamy także procedury umozliwiajace odczyt/zapis klasy z/do pliku
(******************************************************************************)
program Lesson_02;

{$APPTYPE CONSOLE}

uses
SysUtils,
Classes, // <- podstawowe klasy z których bedziemy dziedziczyć
Dialogs,
dtxUtils; // <- napisałem procedury ułatwiające serializację klas
{-------------------------------------------------------------------------------
Na dole w głównym bloku begin ... end. znajduje się opis zapisu i odczytu klas
z jak i do pliku ...

------------------------------------------------------------------------------
/// Funkcje pomocnicze z dtxUtils.pas ///
------------------------------------------------------------------------------

---
1 procedure StringToComponent(Value: string; Comp: TComponent);
---
------------------------------------------------------------------------------
Konwertuje dane z łańcucha znaków na dane do obiektu i inicjalizuje
jego dane. Przekazywana klasa Comp musi być wczesniej stworzona.


---
2 function ComponentToString(Component: TComponent): string;
---
------------------------------------------------------------------------------
Konwertuje komponent na łańcuch


---
3 procedure SaveComponent(AFile: string; AComponent: TComponent;
--- AText: boolean = true);
------------------------------------------------------------------------------
Zapisuje komponent do pliku tekstowego. Parametr AText ustawia czy ma być
to w postaci czytelnej dla człowieka (tekstowej) czy też binarnej.
Wersja binarna zajmuje nieco mniej miejsca.


---
4 procedure LoadComponent(AFile: string; AComponent: TComponent;
--- AText: boolean = true);
------------------------------------------------------------------------------
Robi dokładnie to co SaveComponent tylko w drugą stronę

------------------------------------------------------------------------------
/// Funkcje i typy pomocnicze z "Classes.pas" ///
------------------------------------------------------------------------------

---
1 type
--- TStreamOriginalFormat = (sofUnknown, sofBinary, sofText);
------------------------------------------------------------------------------
Typ mówiący o tym w jakiej postaci zapisaliśmy obiekt (binarna/tekstowa)


---
2 procedure ObjectBinaryToText(Input, Output: TStream); overload;
--- procedure ObjectBinaryToText(Input, Output: TStream;
var OriginalFormat: TStreamOriginalFormat); overload;
procedure ObjectTextToBinary(Input, Output: TStream); overload;
procedure ObjectTextToBinary(Input, Output: TStream;
var OriginalFormat: TStreamOriginalFormat); overload;
------------------------------------------------------------------------------
Funkcje konwertujące obiekt w postaci binarnej do tekstowej (i odwrotnie)
operujące na strumieniach


---
3 procedure ObjectResourceToText(Input, Output: TStream); overload;
--- procedure ObjectResourceToText(Input, Output: TStream;
var OriginalFormat: TStreamOriginalFormat); overload;
procedure ObjectTextToResource(Input, Output: TStream); overload;
procedure ObjectTextToResource(Input, Output: TStream;
var OriginalFormat: TStreamOriginalFormat); overload;
------------------------------------------------------------------------------
Funkcje konwertujące postać tekstową do zasobów i odwrotnie


---
4 function TestStreamFormat(Stream: TStream): TStreamOriginalFormat;
---
------------------------------------------------------------------------------
Funkcja sprawdzająca jakiego typu są dane zserializowanego obiektu

{------------------------------------------------------------------------------}
type
// "typ" naszej procedury
TOnPrint = procedure of object;

// wrsja gracza 2 :) - zawiera wszystko to co zawierała klasa z poprzedniej
// części + mała nowość :)
TPlayer2 = class(TComponent)
private
FName: string;
FRememberPasswd: boolean;
FPasswd: string;
FOnPrint: TOnPrint;
public
// ---
protected
// ---
published
property Name: string read FName write FName;
property RememberPasswd: boolean read FRememberPasswd write FRememberPasswd;
property Passwd: string read FPasswd write FPasswd stored FRememberPasswd;

// tytaj zaczynają się nowości :) ...
procedure OnPrint1; // mamy dwie procedury do wyboru
procedure OnPrint2;

// pole pamiętające nasz wybór (możemy przypisać mu dowolną metodę będacą
// w sekcji published z klasy w której ów właściwość jest zadeklarowana)
property OnPrint: TOnPrint read FOnPrint write FOnPrint;
end;


{ TPlayer2 }

procedure TPlayer2.OnPrint1;
begin
WriteLn('Wywolano OnPrint1');
end;

procedure TPlayer2.OnPrint2;
begin
WriteLn('Wywolano OnPrint2');
end;

var
Player: TPlayer2;
c: char;
{-------------------------------------------------------------------------------
Poniżej są zastosowane procedury zapisu odczytu klas do plików i strumieni.
{------------------------------------------------------------------------------}
begin
Player := TPlayer2.Create(nil);

Write('Ktora metode chcesz przypisac do property OnPrint (1 lub 2) : ');
ReadLn(c);
if c = '1' then
Player.OnPrint := Player.OnPrint1
else
Player.OnPrint := Player.OnPrint2;

// Wywołujemy property :)
WriteLn(sLineBreak, 'Za chwile wywolamy metode zapisana w property OnPrint :');
Player.OnPrint;
WriteLn;

WriteLn('Tak wyglada nasza klasa przerobiona na napis :', sLineBreak, sLineBreak,

ComponentToString(Player)

);

// zapisujemy do pliku
SaveComponent('Player.txt', Player);
// i zwalniamy
Player.Free;

WriteLn(sLineBreak,
'Klasa zostala zapisany do ''Player.txt'' i zwolniona z pamieci. ',
'Mozesz otworzyc zapisany plik i go zmodyfikowac - za chwile klasa TPlayer2 ',
'zostanie utworzona na nowo i wlasnie z ''Player.txt'' zostanie wczytana ',
'zawartosc klasy. Zmodyfikuj plik jak chcesz i wcisnij [ENTER] by ',
'zaincjalizowac klase i wywolac property OnPrint.');
ReadLn;

// tworzymy obiekt od nowa
Player := TPlayer2.Create(nil);
LoadComponent('Player.txt', Player);
WriteLn('Tak wyglada nasza klasa po wczytaniu z pliku :', sLineBreak, sLineBreak,

ComponentToString(Player)

);

// Wywołujemy property :)
WriteLn('Za chwile wywolamy metode zapisana w property OnPrint :');
Player.OnPrint;
WriteLn;

// zakończ ...
ReadLn;
Player.Free;
end.


Część 3

(*******************************************************************************
Serializacja klas w Delphi cz.3

DaThoX 2004-2008

Maciej Izak (hnb.code[at]gmail[dot]com)

Na dzisiejszej lekcji dowiemy się jak wpleść w klasę TComponent inne klasy
nie dziedziczące z TComponent;
(******************************************************************************)

program Lesson_03;

{$APPTYPE CONSOLE}

uses
SysUtils,
Classes, // <- podstawowe klasy z których bedziemy dziedziczyć
Dialogs,
dtxUtils; // <- napisałem procedury ułatwiające serializację klas

type
// nasza klasa która chcemy wpleść. Np. gracz ma samochód :)
//
// musimy dziedziczyć z TPersistent - ona zapisuje z RTTI naszej klasy
// właściwości z sekcji published. Moglibyśmy sami napisać taką klasę ale
// ta jest wystarczająco dobra i nic nie dorzuca do TObject. Czyli zamiast
// dziedziczyć po TObject (TKlasa = class lub TKlasa = class(TObject))
// dziedziczymy z TPersistent TKlasa = class(TPersistent)
TCar = class(TPersistent)
private
FName: string;
FMaxSpeed: single;
public
constructor Create(AName: string; AMaxSpeed: single);
published
property Name: string read FName write FName;
property MaxSpeed: single read FMaxSpeed write FMaxSpeed;
end;

{ TCar }

constructor TCar.Create(AName: string; AMaxSpeed: single);
begin
inherited Create;
FName := AName;
FMaxSpeed := AMaxSpeed;
end;

// wrsja gracza 3 :) -
// NOWOŚĆ :D Jeśli klasa ma włączone RTTI to od razu po class(TComponent)
// mamy do czynienia z sekcją published :)
// NOWOŚĆ 2 :D
// sprawdź blok begin ... end. aplikacji. Jeśli nadamy wartość polu name z
// TComponent nasz obiekt w wersji tekstowej będzie wyglądał inaczej :)...
type
TPlayer3 = class(TComponent)
// tu też jest published !!! tylko ciut inne ;) - omówimy później
//
//
private
FCar: TCar;
public
// musim stworzyć i zniszczyć klasę z pola FCar
constructor Create; reintroduce;
destructor Destroy; override;
published
property Car: TCar read FCar write FCar;
end;

{ TPlayer3 }

constructor TPlayer3.Create;
begin
inherited Create(nil);
FCar := TCar.Create('BMW', 210.6);
end;

destructor TPlayer3.Destroy;
begin
FCar.Free;
inherited;
end;

var
Player: TPlayer3;
c: char;
begin
Player := TPlayer3.Create;

// NADAJEMY Name :D
Player.Name := 'HNB';

WriteLn(

ComponentToString(Player)

);
// zakończ ...
ReadLn;
Player.Free;
end.


Powodzenia :)! Powyżej zaprezentowane lekcje to tylko niewielki wstęp do całego tematu. Zachęcam do samodzielnego poszerzania wiedzy, albo do czekania na kolejne części :).

sobota, 28 listopada 2009

Dyrektywy kompilatora Pascala cz.1 - Symbole predefiniowane

ważne: jeśli dostrzegłeś jakieś braki czekam na komentarz!

Zaczniemy od podstaw - omówimy na początek symbole pre-definiowane, które są podstawą do sprawnej kompilacji warunkowej. Na początek trochę nudów i może dla większości rzeczy oczywistych, jednak spokojnie :). Jeden z artykułów będzie o "białych krukach" wśród dyrektyw.

Symbole pre-definiowane to symbole które zostały zdefiniowane przez kompilator w zależności od przeróżnych okoliczności (np. system operacyjny linux lub windows). Dzięki temu jesteśmy w stanie generować różny kod. Standardowo sami możemy definiować symbole o wymyślonych przez nas nazwach, bądź je usuwać:


{$DEFINE SOME_NAME} // definiujemy symbol o nazwie SOME_NAME
{$UNDEF SOME_NAME} // kasujemy symbol o nazwie SOME_NAME


Zaczniemy rysem historycznym (uwielbiam te nazwy kodowe!). Przytoczę wersje kodowe produktów, cytując artykuł "Delphi History" i wzbogacając go o nowe pozycje (uwzględniając najnowszy "RAD Studio, Delphi and C++Builder Roadmap"):

Project Commodore 2010/2011
Project Delphi "X" 2010
Embarcadero RAD Studio 2010 Weaver 2009
CodeGear C++ Builder 2009 Barracuda 2008
CodeGear RAD Studio 2009 - Unicode Tiburón 2008
CodeGear RAD Studio 2007 Highlander 2007, Sept 5
Chrome 2.0 by RemObjects Joyride 2007, Aug 1
CodeGear C++ Builder 2007 Cogswell 2007, June 5
CodeGear Delphi for PHP Astro 2007, Mar 27
CodeGear Delphi 2007 for Vista and AJAX Spacely 2007, Mar 19
Borland Developer Studio 2006 - W32, .Net, C++ and C# DeXter 2005, Oct 10
Borland Delphi 2005 - C# and Delphi Diamondback 2004, Oct 12
Borland Delphi 8 for .Net Octane 2003, Dec 22
Borland C++BuilderX Tomahawk 2003, Aug 28
Borland C#Builder 1.0 Sidewinder (Galileo IDE 1.0) 2003, Jun 16
Borland Delphi.NET compiler (.Net 1.0) Morpheus 2003
Borland Delphi 7 Aurora 2002, Aug 9
Borland C++ Builder 6.0 Riptide 2002, Feb 1
Borland Delphi 6 Illiad 2001, May 21
Borland C++ Builder 5.0 Rampage 2000, Jan 30
Borland Delphi 5 Argus 1999, Aug 10
Borland Delphi/400 outsourced 1999, Feb
Borland Delphi 4 Allegro 1998, Jun 17
Borland Delphi 3 Ivory 1997, Aug 5
Borland Delphi 2 32bit/Win95 Polaris 1996, Feb 10
Borland Delphi 1 16bit/Win3.x Delphi95, Wasabi, Mango, AppBuilder 1995, Feb 14

i teraz poniżej zobaczmy, jakimi symbolami pre-definiowanymi zostały oznaczone poszczególne wersje, nie tylko Delphi ale i C++ (możliwe jest miksowanie kodu C++ i Delphi), w oparciu o artykuł "Borland Compiler Conditional Defines"


{$DEFINE VER210} // Embarcadero RAD 2010 v14.0
{$DEFINE VER200} // CodeGear Delphi 2009 v12.0
{$DEFINE VER200} // CodeGear C++ Builder 2009 v12.0
{$DEFINE VER190} // CodeGear Delphi 2007 for .NET v11.0
{$DEFINE VER185} // CodeGear Delphi 2007 for Win32 v11.0
{$DEFINE VER180} // CodeGear Delphi 2007 for Win32 v11.0
{$DEFINE VER180} // Borland Developer Studio 2006 v10.0
{$DEFINE VER170} // Borland Delphi 2005 v9.0
{$DEFINE VER160} // Borland Delphi 8 for .NET v8.0
{.$DEFINE ?} // C++BuilderX
{$DEFINE VER160} // Borland C#Builder v1.0
{$DEFINE VER150} // Borland Delphi 7 v7.0
{$DEFINE VER140} // Borland Kylix 3 v3.0
{$DEFINE VER140} // Borland C++Builder 6
{$DEFINE VER140} // Borland Kylix 2 v2.0
{$DEFINE VER140} // Borland Delphi 6 v6.0
{$DEFINE VER140} // Borland Kylix v1.0
{$DEFINE VER130} // Borland C++Builder 5
{$DEFINE VER130} // Borland Delphi 5 v5.0
{$DEFINE VER125} // Borland C++Builder 4
{$DEFINE VER120} // Borland Delphi 4 v4.0
{$DEFINE VER110} // Borland C++Builder 3
{$DEFINE VER100} // Borland Delphi 3 v3.0
{.$DEFINE ?} // Borland C++ 5
{$DEFINE VER93} // Borland C++Builder 1
{$DEFINE VER90} // Borland Delphi 2 v2.0
{.$DEFINE ?} // Borland C++ 4.5
{$DEFINE VER80} // Borland Delphi v1.0
{.$DEFINE ?} // Borland C++ 4
{$DEFINE VER70} // Borland Pascal 7 v7.0
{.$DEFINE ?} // Borland C++ 3.1
{$DEFINE VER70} // Turbo Pascal for Windows 1.5 v1.5
{.$DEFINE ?} // Turbo C++ for DOS 3
{.$DEFINE ?} // Borland C++ 3
{.$DEFINE ?} // Turbo C++ for Windows 3 (Win16)
{.$DEFINE ?} // Turbo Pascal for Windows 1.0 v1.0
{.$DEFINE ?} // Borland C++ 2
{$DEFINE VER60} // Turbo Pascal 6 v6.0
{.$DEFINE ?} // Turbo C++ for DOS
{.$DEFINE ?} // Turbo C for DOS 2
{$DEFINE VER55} // Turbo Pascal 5.5 v5.5
{.$DEFINE ?} // Turbo C for DOS v1.5
{$DEFINE VER50} // Turbo Pascal 5 v5.0
{$DEFINE VER40} // Turbo Pascal 4 v4.0
{.$DEFINE ?} // Turbo C for DOS
{.$DEFINE ?} // Turbo Pascal 3 v3.0
{.$DEFINE ?} // Turbo Pascal 2 v2.0
{.$DEFINE ?} // Turbo Pascal 1 v1.0


Nieźle? :D To nie wszystko. Istnieją także symbole definiowane w zależności od środowiska/warunków/wersji w jakim kod został skompilowany (część z nich można znaleźć na tej oficjalnej stronie).

{$DEFINE CONSOLE} // defioniowana w projektach gdzie została użyta dyrektywa {$APPTYPE CONSOLE}
{$DEFINE CONDITIONALEXPRESSIONS} // definiowane w przypadku gdy możliwe jest testowanie warunków poprzez używanie dyrektywy $IF
{$DEFINE DEBUG} // gdy kompilujemy kod w trybie debugowania
{$DEFINE CPU386} // procesor Intel 386 i późniejszy
{$DEFINE WINDOWS} // używamy Windowsa - definicja "depracted" i zalecane jest używanie MSWINDOWS
{$DEFINE MSWINDOWS} // używamy Windowsa
{$DEFINE WIN32} // Windows 32 bitowy
{$DEFINE WIN64} // Windows 64 bitowy
{$DEFINE LINUX} // Linux
{$DEFINE NOT} // zebezpiecznie kou przed próbami automatycznej modyfikacji na różnych platformach
{$DEFINE MACOSX} // MacOSX! :D (pojawia się w kodzie D2010)
{$DEFINE UNICODE} // Wersja Delphi z unicode
{$DEFINE RTL} // wersja Delphi dla .NET

Zgrzeszyłbym nie przedstawiając pre-definiowanych symboli w FreePascalu. FreePascal posiada szereg swoich własnych symboli nieco odmiennych czy wybrakowanych w stosunku do Delphi, jednak istnieje plik zgrabnie ujednolicający definicje FPC i Delphi (patrz niżej opis pliku jedi.inc). Poniższa lista powstała w oparciu o "Appendix G -
Compiler defines during compilation"
gdzie można odnaleźć bardziej kompletne zestawienie.


{$DEFINE FPC_LINK_DYNAMIC} // Kiedy ustawiliśmy linkowanie dynamiczne
{$DEFINE FPC_LINK_STATIC} // Dla linkowania statycznego (domyślnie)
{$DEFINE FPC_LINK_SMART} // Defioniowane dla "sprytnego linkowania" (ang. smartlinking)
{$DEFINE FPC_CROSSCOMPILING} // Kiedy docelowa platforma jest inna niż ta na któej kompilujemy
{$DEFINE FPC} // Definiowane dla FreePascala
{$DEFINE VER2} // Definiowane dla Free Pascala 2.x.x.
{$DEFINE VER2_0} // Definiowane dla Free Pascala 2.0.x.
{$DEFINE VER2_2} // Definiowane dla Free Pascala 2.2.x.
{$DEFINE FPC_DELPHI} // Free Pascal w "Delphi mode" (dyrektywa $MODE DELPHI)
{$DEFINE FPC_OBJFPC} // Free Pascal w "OBJFPC mode" (dyrektywa $MODE OBJFPC)
{$DEFINE FPC_TP} // Free Pascal w "Turbo Pascal mode" (dyrektywa $MODE TP)
{$DEFINE FPC_GPC} // Free Pascal w "GNU Pascal mode" (dyrektywa $MODE GPC)


Jeszcze więcej o symbolach pre-definiowanych możemy znaleźć w pliku jedi.inc - jest to bardzo użyteczny plik, który po dołączeniu za pomocą dyrektywy


unit Unit1;
{$I jedi.inc}
interface {...}


umożliwia nam łatwiejsze sprawdzanie co/jak/która wersja (np. definiuje DELPHI6_UP - symbol definiowany dla Delphi6 i każdego następnego) . Zachęcam do jego analizy. Jako alternatywy można też użyć pliku GX_CondDefine.inc z pluginu gexperts (informacja pochodzi z tej strony)

New art soon. gl&hf