Forum Delphi.cz

Delphi => Obecné => Téma založeno: Jirka 09-10-2020, 17:00:28

Název: Portace FreeAndNil do starších verzí Delphi
Přispěvatel: Jirka 09-10-2020, 17:00:28
V souvislosti jiným příspěvkem mě napadla taková kacířská myšlenka
Zda by šlo "přepsat"  procedura FreeAndNil ve starších verzích  Delphi (u mě XE2)
a bezpečně nahradit volání původní procedury touto novou .

Vyzkoušel jsem definovat proceduru jako
Kód: Delphi [Vybrat]
  1. procedure FreeAndNilSydney(const [ref] Obj: TObject);
  2. var
  3.   Temp: TObject;
  4. begin
  5.   Temp := Obj;
  6.   TObject(Pointer(@Obj)^) := nil;
  7.   Temp.Free;
  8. end;

a volání této nové procedury probíhá bez viditelných problémů

1. Lze nějakým bezpečným způsobem přepsat původní FreeAndNil() proceduru  v dané aplikaci ?
2. Pokud nelze postupovat podle bodu 1 , alespoň nahradit volání FreeAndNil() na FreeAndNilSydney() ?

Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: Morrison 09-10-2020, 18:36:08
Nějak nerozumím tomu, v čem je problém s bodem č.2
Nahradit snad je záležitost nějakého Ctrl R (Vyhledat a nahradit). Nebo snad myslíš nahradit to i v komponentách atd.?
Jinak co se bodu č.1 týče, možná ti tu někteří zdejší zkušenější borci poradí, jak to zahákovat, přepsat odkaz v tabulce metod nebo nějaký podobný low-level voodoo, kterému já nerozumím :)
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: pepak 09-10-2020, 20:23:45
1. Lze nějakým bezpečným způsobem přepsat původní FreeAndNil() proceduru  v dané aplikaci ?
Co si představuješ pod pojmem "bezpečným" v tomto kontextu? Je pro tebe přepsání prvních pěti bajtů funkce (adresu zjistíš přes @FreeAndNil) instrukcí skoku a čtyřmi bajty adresy (zjistíš přes @FreeAndNilSydney, nutno přpočítat na relativní adresu) dostatečně bezpečné, zvlášť když to zaIFDEFuješ jenom na ty verze Delphi, ve kterých sis ověřil, že FreeAndNil má aspoň 5 bajtů?
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: pepak 10-10-2020, 03:16:49
Akorát mě tak napadá, a co přesně si od toho "nahrazení" slibuješ? Moje odpověď byla na to, jak to udělat, ale nebude to plnit účel - pokud tedy správně chápu, že bys chtěl, aby se FreeAndNil dalo zavolat jenom na objekty.
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: pf1957 10-10-2020, 17:48:02
V souvislosti jiným příspěvkem mě napadla taková kacířská myšlenka
Zda by šlo "přepsat"  procedura FreeAndNil ve starších verzích  Delphi (u mě XE2)
a bezpečně nahradit volání původní procedury touto novou .
No clovece, zkusil jsem tvoji myslenku v Delphi 10.2 a nestacil, jsem se divit, co to prekladac vytvari  :o :o :o

Treba u generickeho TList<T> vypada definice tridy takto:
Kód: Delphi [Vybrat]
  1.   private type
  2.     arrayofT = array of T;
  3.   var
  4.     FListHelper: TListHelper; // FListHelper must always be followed by FItems
  5.     FItems: arrayofT; // FItems must always be preceded by FListHelper
  6.   ...
  7.  
coz je tezky hack, protoze na FreeAndNilSydney(list[n]) prekladac jako const ref parametr preda adresu elementu TList.FItems[n], cili se hrabe primo v pameti zapouzdrenehio objektu, private/ne-private members   :o A tim to funguje.

Ale kdyz tomu napr. u TStrings predhodim FreeAndNilSydney(list.Objects[n]), tak tam bez uzardeni pise nil buhvi kam (skutecne se mi nechce pocitat ty adresyt v prelozenem kodu) a do sl.Objects[n] zadne nil samozrejme nezapise, protoze hodnota muze byt ve skutecnosti treba v utrobach Windows...

Jak se chova presne 10.4 nevim, protoze ho nemam a zrejme nikdy mit nebudu, ale docela na me pada uzkost pri pomysleni, ze si udelam nejaky
TMyObject a jeho property nekdo preda jako const ref parametr a prekladac misto aby to vykopal tam bude psat buhvi kam... A nekdo pak bude tezce lovit nejake prepisy pameti nebo AV...

Takze kdyz mluvis o bezpecnosti ve spojeni se starsimi verzemi, tak bych osobne byl rad, ze ti to prekladac neprelozi a ohlasi chybu, ze trva na var argumentu.
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: František 11-10-2020, 14:52:33
myslim ze cele toto vzniklo novinkou https://delphi.cz/post/Delphi-104-ocekavane-novinky-RTL-aktualizace.aspx (https://delphi.cz/post/Delphi-104-ocekavane-novinky-RTL-aktualizace.aspx)

Kód: Delphi [Vybrat]
  1. procedure FreeAndNil(const [ref] Obj: TObject); inline;
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: Radek Červinka 11-10-2020, 16:01:25
myslim ze cele toto vzniklo novinkou https://delphi.cz/post/Delphi-104-ocekavane-novinky-RTL-aktualizace.aspx (https://delphi.cz/post/Delphi-104-ocekavane-novinky-RTL-aktualizace.aspx)

Kód: Delphi [Vybrat]
  1. procedure FreeAndNil(const [ref] Obj: TObject); inline;


No hlavne se pak upravila i RTL, kdyz na to kompilator upozornil. Takže, jednoduše nové FreeAndNil se starou RTL použít nelze.
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: pf1957 11-10-2020, 16:34:44
No hlavne se pak upravila i RTL, kdyz na to kompilator upozornil. Takže, jednoduše nové FreeAndNil se starou RTL použít nelze.
Takze v nove verzi uz nelze napsat FreeAndNil(TList<T>[ x ]) ani FreeAndNil(TStrings.Objects[ x ]) aj.  a prekladac oznami chybu?

Nebo to propusti a u TList<T> to bude fungovat spravne diky silenemu hacku a u TStrings to nebude fungovat a navic to bude psat buhvi kam do pameti?

Nebo upravili RTL a prekladac tak, ze to bude fungovat vzdy a vsude, kdyz to prekladac propusti (tj. nil priradi setterem)?
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: Radek Červinka 11-10-2020, 21:24:35
Já myslím, že došlo k nepochopení o co šlo: tato změna má interně stejnou funkci jak generický var obj z předchozí signatury, až na to, že předešlý případ umožňoval předání čehokoliv (var bez typu umožní předat úplně vše), během vývoje tak bylo ukázáno různými třetími stranami i R&D, že tato změna díky kompiláru zachytila špatné volání FreeAndNil s parametrem typu interface (nejčasteji), array, THandle atd. Takže chápu tazatele.

Nyní lze předat jen následníka TObject. Ohledně zápisových property se to chová stejně, tj. není to ideální, ale i tak je to IMHO krok vpřed. Pro zápisové property mi to přijde a co si matně pamatuji, že se vytvoří temp pointer na stacku a ten se nuluje, takže by to mělo být bezpečné, ale setter se nezavolá. Prý i jeden z důvodů pro méně komplexní řešení bylo zachování rozhraní v rámci 10.4.x řady. Jinak FreeAndNil je inline, níže varianta bez inline, s ní jsem byl už úplně ztracen.

Kód: Delphi [Vybrat]
  1. 0060E509 8BEC             mov ebp,esp
  2. 0060E50B 83C4F4           add esp,-$0c
  3. 0060E50E 8955F8           mov [ebp-$08],edx
  4. 0060E511 8945FC           mov [ebp-$04],eax
  5. Unit29.pas.39: FreeAndNil(sl.Objects[0]);
  6. 0060E514 8B45FC           mov eax,[ebp-$04]
  7. 0060E517 8B80E4030000     mov eax,[eax+$000003e4]
  8. 0060E51D 33D2             xor edx,edx
  9. 0060E51F 8B08             mov ecx,[eax]
  10. 0060E521 FF5118           call dword ptr [ecx+$18]
  11. 0060E524 8945F4           mov [ebp-$0c],eax
  12. 0060E527 8D45F4           lea eax,[ebp-$0c]
  13. 0060E52A E859EBE1FF       call FreeAndNil
  14. Unit29.pas.40: if sl.Objects[0] = nil then
  15. 0060E52F 8B45FC           mov eax,[ebp-$04]
  16. 0060E532 8B80E4030000     mov eax,[eax+$000003e4]
  17. 0060E538 33D2             xor edx,edx
  18. 0060E53A 8B08             mov ecx,[eax]
  19. 0060E53C FF5118           call dword ptr [ecx+$18]
  20. 0060E53F 85C0             test eax,eax
  21. 0060E541 750A             jnz $0060e54d
  22. Unit29.pas.41: ShowMessage('nil');
  23. 0060E543 B860E56000       mov eax,$0060e560
  24.  




No hlavne se pak upravila i RTL, kdyz na to kompilator upozornil. Takže, jednoduše nové FreeAndNil se starou RTL použít nelze.
Takze v nove verzi uz nelze napsat FreeAndNil(TList<T>[ x ]) ani FreeAndNil(TStrings.Objects[ x ]) aj.  a prekladac oznami chybu?

Nebo to propusti a u TList<T> to bude fungovat spravne diky silenemu hacku a u TStrings to nebude fungovat a navic to bude psat buhvi kam do pameti?

Nebo upravili RTL a prekladac tak, ze to bude fungovat vzdy a vsude, kdyz to prekladac propusti (tj. nil priradi setterem)?

Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: Radek Červinka 11-10-2020, 21:27:04
Takze v nove verzi uz nelze napsat FreeAndNil(TList<T>[ x ]) ani FreeAndNil(TStrings.Objects[ x ]) aj.  a prekladac oznami chybu?

Druhý případ právě že jo, protože je TObject, první asi podle T.
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: pf1957 11-10-2020, 23:26:19
Já myslím, že došlo k nepochopení o co šlo: tato změna má interně stejnou funkci jak generický var obj z předchozí signatury, až na to, že předešlý případ umožňoval předání čehokoliv (var bez typu umožní předat úplně vše), během vývoje tak bylo ukázáno různými třetími stranami i R&D, že tato změna díky kompiláru zachytila špatné volání FreeAndNil s parametrem typu interface (nejčasteji), array, THandle atd. Takže chápu tazatele.
Tohle ja chapu: puvodni implementace byla hrubka, protoze v signature byl untyped pointer (var obj) a uvnitr tela natvrdo pretypovani na TObject s temi dusledky, co popisujes. Co nechapu, proc nepouzili primocare reseni, ktere se nabizi tj. zmenit signaturu na var TObject. To by zachovalo puvodni kontrolu var + zajistilo silnou typovou kontrolu parametru.

Me jde o neco jineho: kdyz jsi predal do var argumentu property, tak prekladac zcela spravne rval, ze neumi predat const parametr do var argumentu. Ale  kdyz pouzili tu divnou referenci na TObject, tak jsem v D10.2 zjistil, ze tam muzu predavat property, prekladac nerve, ale funkcionalita je v kyblu (a je nejspis nebezpecna, protoze to pise nil buhvi kam). A jako vrchol vseho hnusu jsem narazil u generickeho TList<T> kod, ktery to property predat umoznuje (ne do puvodni signatury var obj):

Kód: ASM [Vybrat]
  1. 0072A007 8B45F8           mov eax,[ebp-$08]     ;instance TList<T>
  2. 0072A00A 8B4020           mov eax,[eax+$20]     ;adresa private memberu TList<T>.FItems (to je ta poznamka, ze musi byt jako 1. za recordem s helperem)
  3. 0072A00D 83C00C           add eax,$0c           ; + offset elementu [3]*sizeof(TObject)
  4. 0072A010 E8BFEAFFFF       call FreeAndNilSydney
  5.  

A ohledne toho Objects[] jsem to zkousel s TListBoxem, protoze tam vim, ze je hodnota Objects v utrobach Windows jako ITEMDATA a jinak nez setterem potazmo volanim LB_SETITEMDATA se vynulovat neda...

Tak me zajimalo, jak to vyresili v 10.4, kde by jak pises melo jit finalni verzi, ktera ma tohle dokoncene. Ale ten priklad s generickym TList<T> uz ve verzi 10.2 naznacuje, ze leccos zacali prasit jeste prted zmenou te FreeAndNil.

Az budu mit cas, tak napisu test, at se bavime o kontkretnich vecech.
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: pf1957 11-10-2020, 23:28:12
Druhý případ právě že jo, protože je TObject
Ale to je IMHO prave spatne
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: Radek Červinka 12-10-2020, 08:55:40
Druhý případ právě že jo, protože je TObject
Ale to je IMHO prave spatne

A to je IMHO právě dobře.
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: pf1957 12-10-2020, 22:44:39
A to je IMHO právě dobře.
Az budu mit cas, tak zkusim napsat priklady, proc si myslim, ze je to vic nez spatne, protoze si myslim, ze je to s property obecne pres const ref neresitelne.
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: pf1957 28-10-2020, 10:06:55
Chtel jsem se prilezitostne podivat na DUnitX, tak jsem vyprasil jednoduchy test nad standardnim TList<T> a nastinenych vlastnich tridach TMyList<T> a TMyChain<T> s navenek stejnym rozhranim co se tyce Items[ x ].
 
Pro verzi 10.2 jsem FreeAndNilSydney implementoval v unit s nastinenymi tridami, pro 10.4. bude treba definovat V10_4 na zacatku unit.

Pokud v 10.2 zapnu inlining, tak to se standardnim TList<T> funguje diky te prasarne, ktera se hrabe v private sekci na pevne danych offsetech. Ostatni celkem neprekvapive nefunguji, protoze prekladac vytahne instanci do automaticke promenne a tu pak preda do FreeAndNilSydney, ktera tu automatickou promennou vynuluje - aspon, ze to neprepisuje pamet, jako jsem mel dojem, ze to dela treba u TListBox.Items.

Kdyz se inlining vypne (direktiva na zacatku unit), tak nefunguje ani standardni TList<T>.

Takze by me zajimalo, jak se to chova v 10.4, ktera ma standardni FreeAndNil zmenenou a to jak s inliningem, tak bez.

Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: starous 29-10-2020, 07:26:12
Takze by me zajimalo, jak se to chova v 10.4, ktera ma standardni FreeAndNil zmenenou a to jak s inliningem, tak bez.


Můžu poprosit o komentář? Díky.
Název: Re:Portace FreeAndNil do starších verzí Delphi
Přispěvatel: pf1957 29-10-2020, 08:53:40
Můžu poprosit o komentář? Díky.
Diky za otestovani. Ja to shrnu:

Pokud jsi definoval nahore V10_4 tj. volala se System.FreeAndNil a slo to prelozit bez chyb a ja tam nemam chybu, tak se to na vlastnim testu chova stejne, jako ve verzi 10.2, kde jsem tomu predhodil vlastni implementaci FreeAndNilSydney, podle te z verze 10.4. Ovsem ve skutecnosti se to jako celek chova v podstate tragicky:
Kdyz uz bych chtel neco takoveho delat, tak rozhodne obecne, protoze vzdycky nas ucivali, ze nejhorsi jsou vyjimky a moje celozivotni praxe to jen potvrzuje. Uz vidim ty nestastniky, kterym nekdo v teamu pouzije FreeAndNil na property a pak to cele hodiny ne-li dny hledaji... To obecne reseni bud direktivou prekladaci nebo anotaci,. napr.
Kód: Delphi [Vybrat]
  1. [Nilable(FItems)]
  2. property Items[Index: Integer]: T read GetItem write SetItem; default;
  3. [Nilable(FAnyObject)]
  4. property AnyObject: T read GetAnyObject write SetAnyObject;
  5.  

Jeste ze uz jsem duchodce a pravdepodobnost, ze budu muset jeste neco delat v Delphi, se blizi k nule, protoze mam dojem, ze v EMBC uz neprogramuji, ale jen prasi, nebo tam zacinaji mit slovo nejaci "pythonisti", kterym pripada normalni, ze pri zapisu
Kód: Python [Vybrat]
  1. objekt.neexistujicici_property = cosi
se nerve a do objektu se prida neexistujici_property jako existujici...