Data w TTreeNode nie przechowuje tego, co powinna

0

Cześć, mam zajebiście prostą aplikację.
I co się okazuje? Że wczoraj działało, dzisiaj zadziałało raz.

Oto całość:
Najpierw tworzę sobie gdzieś node'a i przypisuję mu jakieś dane:

  node.Data:=PChar('C:\Folder\plik.roz');

Następnie w innej procedurze chcę to odczytać:

var
  s: string;
begin
  s:=String(node.Data);
end;

Zgadnijcie, co ma zmienna s.
Otóż zmienna s w tym momencie dostaje jakby wycinek pamięci. Owszem, jest gdzieś 'C:\Folder\plik.roz', ale wokół tego pełno znaków #0, a także kilka innych wartości tekstowych :|

O co tu chodzi?

0

Prześledziłem po kolei, co się dzieje w oknie CPU.
I tak.
W momencie przyrównania: node.Data:=PChar('blabla'), data wskazuje na pewien adres. I w tym adresie faktycznie jest ten ciąg znaków. I jest tam, aż do wywołania Application.Run. Wtedy w tym miejscu pojawiają się inne rzeczy.

Node.Data:=... mam wywoływane z OnCreate formy głównej i chciałbym, żeby tak zostało. Podejrzewam, że jak zrobię to z OnShow to będzie dobrze(OnShow wywołuje się już po starcie aplikacji), ale czy ktoś może mnie oświecić?

[dopisane]

W OnShow oczywiście przeszło. Ale podczas tworzenia innej formy te dane przeskoczyły mi do innego miejsca!!. Dokładnie 176 bajtów dalej. Czemu tak się dzieje??

[dopisane]
Jednak nie przechodzi. Nawet przerzuciłem do Timera. Nie mogę wychwycić miejsca, w którym adres jest zmieniany. W pewnym momencie okno CPU wchodzi w odczytywanie komunikatów i po prostu robi to w pętli. Tak więc nie jestem w stanie wychwycić odpowiedniego momentu. Czemu zmienia mi ten adres? I to zawsze o 176 bajtów do przodu.

0

Wyśledziłem, że to powoduje z jakiegoś powodu tworznie Glypha dla buttona. Więc usunąłem tego Glypha. I teraz dane zmieniają swoje położenie prawdopodobnie gdzieś w momencie wciśnięcia menuItema. Możliwe, że chodzi o instrukcję:

rep stosd

ale ja na asmie się w ogóle nie znam.
W każdym razie wartość rejetru EAX przed wykonaniem tego była 0, tak samo jak wartość ECX
(podaję to, co wyśledziłem z Glyphem).

0

OK, częściowo doszedłem, ale nie wiem, jak to zmienić.
Otóż ta wartość 176 bajtów dalej - ona jest tam od początku. A więc się nie przesuwa. Wartość, na którą wskazuje mi pointer jest po prostu nadpisywana z tego powodu, że:

node.Data:=PChar(fileName);

gdzie fileName to argument procedury, czy najpewniej po wyjściu z procedury zmienna jest niszczona i nic już nie wskazuje na to miejsce ze stringiem, a więc może być nadpisane :)

Jeśli zrobię:

node.Data:=PChar('Blabla');

to wszystko jest ok.

Nie sądziłem, że zmienne mogą mieć aż taką ważność.
W związku z tym pytanie, jak to poprawić?

Jak zrobić, żeby było dobrze, żeby tamta zmienna(fileName) nie została zdejmowana? Albo, żeby to miejsce w pamięci, gdzie mam tego stringa nigdy nie zostało nadpisane przez inne rzeczy?

0

OK, udało się rozwiązać problem. Tak podejrzewam.

Dla potomności przedstawiam rozwiązanie:

procedure procka(FileName: string);
begin
  //tworzenie node'a, a potem przypisanie wartości FileName do pola Data:

  //najpierw musimy zaalokować pamięć dla pola Data:
  node.Data:=AllocMem(length(FileName)+1);
  {Bierzemy sobie o jeden bajt pamięci więcej z tego powodu, że podczas odczytu danych możemy nie znać ich długości. Jeden bajt więcej sprawi to, że dane będą zakończone na pewno znakiem #0. Gdyby tego nie zrobić, owszem jest możliwość, że dane będą zakończone znakiem #0, ale niekoniecznie}

  //zaalokowany fragment pamięci jest pusty, a więc należy do niego skopiować to, co nas interesuje
  CopyMemory(node.Data, PChar(FileName), length(FileName));
end;

I tyle. Oczywiście nie używam tu nigdzie instrukcji FreeMem z tego powodu, że zapisane dane potrzebuję przez cały czas działania programu. Dobrym nawykiem byłoby zwalnianie zaalokowanej pamięci podczas zamykania aplikacji, jednak z moich testów wynika, że nie jest to konieczne(pamięć zostanie zwolniona automatycznie). FreeMem TRZEBA użyć wtedy, gdy już nie potrzebujemy dłużej alokowanego miejsca.

Niech mnie ktoś poprawi, jeśli się mylę.

0

a moze napisac jakas klase ktora bedzie trzymala napis (a w przyszlosci jeszcze jakies inne dane)
i do Node.data podstawiac obiekt tej klasy? Prawie na pewno kiedys przyjdzie Ci do glopwy pomysl zeby dodac cos jeszcze do tego noda i wtedy ta klasa bedzie jak najbardziej na miejscu

0

Na razie nie potrzebuję, a też wątpię, żebym w przyszłości chciał coś jeszcze przechowywać. Mam wystarczająco dużo miejsca: Data, Tag i Text :)

Na razie na nodach w jednym levelu używam data, a na innym text i mam wszystko to, co potrzebuję.

BTW, może ktoś mi powie, czemu:

GetMem(node.Data, rozmiar);

powoduje sytax error: "Left side cannot be assigned", podczas gdy

node.Data:=AllocMem(rozmiar);

działa

0

robiac tak:

Type
  TElement = class
    napis: string;
    constructor Create(n: string);
  end;
(...)
constructor TElement.Create(n: string);
begin
  napis := n;
end;
(...)
node.data := TElement.create('Jakis napis');
(...)
showmessage(TElement(node).napis);

pozbylbys sie wszystkich problemow opisanych w tym watku :-)
jedyna nie-fajnosc to koniecznosc rzutowania.

0

"Left side cannot be assigned" dotyczy parametru node.Data.
Delphi nie dopuszcza podawania właściwości obiektów jako argumenty funkcji przez referencję.

0

Juhas, a zauważyłeś, że przy zapisie rzutujesz na pchar, a przy odczycie na string? te typy nieco się różnią...
spróbuj zrzutować za każdym razem na ten sam typ, np. na string (@String konkretnie - bo pewnie bez tego @ miałeś problem).

0

Wcześniej przy odczycie rzutowałem na string, ale od momentu, gdy zacząłem sam alkokować pamięć, po to alokowałem jeden bajt więcej, żeby móc odczytywać jako PChar ;)

0

Ale przy zapisie rzutowałeś na pchar...

0
ŁF napisał(a)

Ale przy zapisie rzutowałeś na pchar...

No bo @zmienna daje adres inny niż PChar(zmienna) :)

I małpa by mi nic nie dała, bo i tak musiałbym skopiować fragment, na który wskazuje @zmienna :)

0
ŁF napisał(a)

Ale przy zapisie rzutowałeś na pchar...

Zarówno PChar jak i String są wskaźnikami na pierwszy znak łańcucha kończącego się znakiem #0 więc w tym przypadku rzutowanie na różne typy nie ma znaczenia.

0
hes napisał(a)
ŁF napisał(a)

Ale przy zapisie rzutowałeś na pchar...

Zarówno PChar jak i String są wskaźnikami na pierwszy znak łańcucha kończącego się znakiem #0 więc w tym przypadku rzutowanie na różne typy nie ma znaczenia.

Naturalnie, że ma, gdyż tylko PChar kończy się znakiem #0.
Jeśli wczytujesz coś do stringa, to możesz mieć tam nawet i 2 mega samych znaków #0 :)

Oczywiście nie wyświetlisz tego nigdzie.

0

Oczywiście, ponieważ wyświetlana jest część Stringa przed pierwszym wystąpieniem znaku #0, czyli dokładnie jak w przypadku PChar.
String zawsze zakończony jest niejawnym znakiem #0.
Jedyna różnica pomiędzy String a PChar polega na tym, że w przypadku Stringa nie musimy przejmować się alokacją i zwalnianiem pamięci. Ponadto String rezerwuje dodatkowe 12 bajtów przed właściwym łańcuchem (na którego początek wskazuje zarówno String jak i PChar) na dane dot. ilości zajmowanej pamięci, długości łańcucha oraz liczby referencji.

Twoje przypisanie do node.Data nie zadziałało bo pomimo utworzenia wskaźnika (PChar) na zmienną lokalną String zmienna ta została zwolniona po wyjściu z procedury.

0
hes napisał(a)

Oczywiście, ponieważ wyświetlana jest część Stringa przed pierwszym wystąpieniem znaku #0, czyli dokładnie jak w przypadku PChar.
String zawsze zakończony jest niejawnym znakiem #0.

Nie, to PChar.

Weź zaczytaj sobie do stringa coś, co ma #0. Nawet niech będzie to fragment pamięci. Potem sprawdź w debuggerze, co tak naprawdę masz w stringu. I możesz mieć coś takiego:

'Jakiś string'#0#1#2

a jeśli zrobisz rzutowanie na PChar, to w debugerze zobaczysz:

'Jakiś string'

Twoje przypisanie do node.Data nie zadziałało bo pomimo utworzenia wskaźnika (PChar) na zmienną lokalną String zmienna ta została zwolniona po wyjściu z procedury.

Mój problem polegał na tym, że właśnie zrobiłem wskaźnik na zmienną lokalną :)

0

Chyba się nie rozumiemy.

Weź zaczytaj sobie do stringa coś, co ma #0. Nawet niech będzie to fragment pamięci. Potem sprawdź w debuggerze, co tak naprawdę masz w stringu.

Przecież nie mówię, że String nie może zawierać znaków #0. Mówię tylko, że część Stringa za wystąpieniem znaku #0 nie zostanie wyświetlona (np. po przypisaniu do Caption, dodaniu do Memo, wypisaniu na konsoli), ponieważ począwszy od Delphi 2 String zachowuje się jak PChar.
Mówię też, że każdy String, podobnie jak PChar, zakończony jest niejawnym znakiem #0, który już nie jest widoczny w debuggerze. Dlatego takie łatwe jest rzutowanie String na PChar, które polega na przypisaniu do wskaźnika (PChar) adresu pierwszego znaku łańcucha w istniejącym już Stringu i nic poza tym.

Mój problem polegał na tym, że właśnie zrobiłem wskaźnik na zmienną lokalną

Przecież właśnie to napisałem, tylko może trochę niezrozumiale :)

Polecam lekturę http://delphi.cartall.com.pl/string.htm

0
hes napisał(a)

Przecież nie mówię, że String nie może zawierać znaków #0. Mówię tylko, że część Stringa za wystąpieniem znaku #0 nie zostanie wyświetlona

Tak, bo jest to naturalne. Nie wyświetlisz znaku #0. Ale możesz go zamienić na np: '.'
Natomiast... Inaczej, załóżmy coś takiego:

var
  s: string;
  p: PChar;
begin
  s:='12345'+#0;
  p:=PChar(s);

  label1.Caption:=stringReplace(s, #0, '.', []);
  label2.Caption:=stringReplace(string(p), #0, '.', []);
end;

label1 po takiej operacji będzie wyświetlał:
"12345."
Natomiast lablel2: "12345"

:)

Ale chyba wiemy o co chodzi, tylko nie potrafimy sobie tego wyjaśnić :P

0

Ja tylko chciałem wyjaśnić koledze ŁF, że nie jest błędem przypisanie do zmiennej typu Pointer zmiennej typu PChar i rzutowanie jej na przy odczycie na String, tak jak to zrobiłeś, ale chyba niepotrzebnie skomplikowałem temat.

// nie ucz ojca dzieci robić: pchar nie jest tożsamy ze stringiem, jedyna część wspólna to kończenie zerem. o ile rzutowanie stringa na pchar nie sprawi problemów, o tyle w drugą stronę nie będzie już tak ładnie. poza tym skoro string zawiera licznik referencji, to nie ma znaczenia, czy będzie traktowany jako zmienna lokalna, czy globalna - Ł

0
hes napisał(a)

Ja tylko chciałem wyjaśnić koledze ŁF, że nie jest błędem przypisanie do zmiennej typu Pointer zmiennej typu PChar i rzutowanie jej na przy odczycie na String, tak jak to zrobiłeś, ale chyba niepotrzebnie skomplikowałem temat.

Aaaaaaa :D

1 użytkowników online, w tym zalogowanych: 0, gości: 1