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]