Avatar billede hermandsen Juniormester
25. september 2002 - 14:05 Der er 20 kommentarer og
3 løsninger

Der er noget galt med min pointer (tror jeg)!

Hejsa mine kære med-eksperter!

Jeg er ved at lave et program der kan resume downloads over et lokaltnetværk. Til det har jeg oprettet følgende klasser:

TFile (beskriver en enkelt fil)
TFileDownload (arver fra en Thread, henter filen over netværket)
TFileList (array af TFile's, holder styr på køer og TFileDownload's)

Brugeren ser status for filer, liste af filer osv., via et TListView. Property'en TListView.Items.Item.Data (Pointer) henviser til den TFile som ListView'ets Item bruger.

I TFile har jeg også en Pointer til en TListItem. Når TFile så ændrer sig (f.eks. der bliver downloadet nogle bytes), så er det meningen at det skal kunne ses i ListView'et.

Dog får jeg fejl hvis jeg skriver til TFile.ListItem^.Caption (f.eks.)...

Her er et eksempel:
PFile = ^TFile
lvFiles = TListView
ListItem = ^TListItem

PFile(lvFiles.Items.Item[0].Data)^.ListItem^.Caption := 'Hest';

Hvad går der galt? Jeg har debugget meget af natten og skulle mene at både Data og ListItem peger på de rigtige steder... Help! :(

//hermandsen
Avatar billede dkn Nybegynder
25. september 2002 - 14:44 #1
de records du har er de lavet som objects?

fx:
TFile = class(Object)
navn:string;
end;

så creater du TFile, som et normalt object. Når du har det som et object er det nemt at smide i en list, hente data fra den osv.
Avatar billede hermandsen Juniormester
25. september 2002 - 14:52 #2
TFile = class(TObject)
  private
    vName: String;
    vSourceDir: String;
    vDestDir: String;
    vSize: Integer;
    vDownloaded: Integer;
    vID: Integer;
    vStatus: TFileStatus;
    function GetFullSource: String;
    function GetFullDestination: String;
    function IsNewFile: Boolean;
    procedure SetStatus(const Value: TFileStatus);
    procedure UpdateNewListItem; //Opdatere indholdet af TListItem der lige er tilføjet til ListView'et
  public
    ListItem: PListItem;      //Krydsreferance mellem TFile og TListItem, så kan de holde styr på hinanden
    constructor CreateNew(ASourceFile, ADestDir: String; AID: Integer; Item: TListItem);
    constructor CreateFromSaved(AName, ASourceDir, ADestDir: String;
      ASize, ADownloaded, AID: Integer; Item: TListItem);
    constructor CreateResumeFile(SourceFile, DestDir: String; AID: Integer; Item: TListItem); //Bruges endnu ikke, men...
    destructor Destroy; override;
  published
    property Name: String read vName write vName;            //Navn på filen uden mappe
    property SourceDir: String read vSourceDir write vSourceDir; //Kilde mappe, uden filnavn
    property DestDir: String read vDestDir write vDestDir;    //Destinations mappe, uden filnavn
    property Size: Integer read vSize write vSize;            //Filstørelse (bytes)
    property Downloaded: Integer read vDownloaded write vDownloaded; //Antal bytes hentet
    property ID: Integer read vID;                                  //ID for fil (samme som i array'ets index Files[n].ID = n)
    property Status: TFileStatus read vStatus write SetStatus;//Status for filen, bruges endnu ikke
    property FullSource: String read GetFullSource;          //Angiver hele filnavnet for kilden (mappe og filnavn)
    property FullDestination: String read GetFullDestination; //Angiver hele filnavnet for destinationen (mappe og filnavn)
    property NewFile: Boolean read IsNewFile;                //Om der allerede er hentet noget af filen
  end;

  PFile = ^TFile;


Her bruges f.eks. CreateNew:

constructor TFile.CreateNew(ASourceFile, ADestDir: String; AID: Integer; Item: TListItem);
begin
  vName := ExtractFileName(ASourceFile);
  vSourceDir := ExcludeTrailingBackslash(ExtractFilePath(ASourceFile));
  vDestDir := ExcludeTrailingBackslash(ADestDir);
  vSize := GetFileSize(ASourceFile);
  vDownloaded := 0;
  vID := AID;
  vStatus := fsNone;
  New(ListItem); //Jeg ved ikke helt om den skal oprettes...
  ListItem := @Item;
  UpdateNewListItem;
end;


Den bliver created hvis jeg f.eks. henter en fil fra en OpenDialog:

var
  Dir: String;
  I: Integer;
begin
  if openDlgFiles.Execute and SelectDirectory('Select destination directory', '', Dir) then
  begin
    lvFiles.AllocBy := lvFiles.Items.Count + openDlgFiles.Files.Count;
    for I := 0 to openDlgFiles.Files.Count -1 do
      FileList.AddNew(openDlgFiles.Files.Strings[I], Dir, lvFiles.Items.Add);
  end;

FileList = TFileList
AddNew ser således ud:

procedure TFileList.AddNew(SourceFile, DestDir: String; ListItem: TListItem);
var
  I: Integer;
begin
  I := GetNewID;
  SetLength(vFiles, I+1);
  vFiles[I] := TFile.CreateNew(SourceFile, DestDir, I, ListItem);
end;

GetNewID giver bare et tal der holder styr på index'et i array'et... Det behøver du ikke bekymre dig om! ;)
Avatar billede dkn Nybegynder
25. september 2002 - 15:11 #3
hvis du får fejl i
PFile(lvFiles.Items.Item[0].Data)^.ListItem^.Caption := 'Hest';

Kan du så ikke bare pakke den ud i en TFile object:

var
newfile:TFile;
begin
newfile :=pointer(lvFiles.Items.Item[0].Data);
nwefile.vName :='hmm';
lvFiles.Items.Item[0].Data :=nwefile;
end;

Har ikke testet det. Men der er jo ikke andet at gøre end at prøve sig frem.
Avatar billede hermandsen Juniormester
25. september 2002 - 15:15 #4
>>dkn
Problemet består jo i at få ændret i ListView'et fra en TFile... At skrive til en TFile er ikke noget problem! :)
Avatar billede dkn Nybegynder
25. september 2002 - 15:59 #5
hmm ja. Jeg kan ikke se dit problem jeg lavede lige en simpel test og den virker perfekt.


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls, clipbrd;

type
  TForm1 = class(TForm)
    ListView: TListView;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TTest = class(TObject)
  Id:string;
  navn:string;
  data:string;
  end;


var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TFile }


procedure TForm1.Button1Click(Sender: TObject);
var
NewItem:TTest;
Listitem:TListitem;
begin
NewItem:=TTest.Create;
NewItem.Id := '1';
NewItem.navn := 'Hej';
NewItem.data := '10101010';

Listitem := ListView.Items.Add;
Listitem.Caption := NewItem.navn;
Listitem.SubItems.Add(NewItem.data);
Listitem.Data := pointer(NewItem);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
NewItem:TTest;
Listitem:TListitem;
begin
NewItem := pointer(ListView.Items.Item[ListView.ItemIndex].Data);
Showmessage(NewItem.Id+#13#10+NewItem.navn+#13#10+NewItem.data);
end;

end.
Avatar billede hermandsen Juniormester
25. september 2002 - 17:42 #6
Vil der være noget i vejen med:

Listitem.Data := @NewItem;
  i stedet for
Listitem.Data := Pointer(NewItem);

Det er nemlig sådan jeg har lavet det!
Avatar billede doc404 Novice
25. september 2002 - 22:42 #7
@NewItem giver dig adressen på NewItem.
Pointer(NewItem) typecaster indholdet af NewItem til en pointer

..Så jo, der er en fandens forskel ;-)
Avatar billede loke-liscom Nybegynder
25. september 2002 - 23:42 #8
Håber i forstår meningen med det her :
procedure TForm1.Button1Click(Sender: TObject);
var
  LI : TListItem;
//LI er en pointer, alle var. af typen TEtEllerAndet er pointers
begin
  LI := ListView1.Items.Add;
  LI.Caption := 'TEST';
  LI.Data := LI;
  TListItem(LI.Data).Caption := 'tester lige igen....';
end;
//Klaus
Avatar billede loke-liscom Nybegynder
25. september 2002 - 23:49 #9
Prøv at se her :
Listitem.Data := @NewItem;
ListItem.Data = pointer og NewItem = pointer
hvis du skriver ListItem.Data := NewItem er det samme som :
i := 1;
a := 2;
i := a; nu er i = 2 ikke?
hvis du så skriver ListItem.Data := @NewItem det er det samme som :
i := 1; variablen i ligger på adressen 123456789
a := 2; variablen a ligger på adressen 123456786
i := @a; nu er i = 123456786
Avatar billede hermandsen Juniormester
26. september 2002 - 07:03 #10
Altså jeg ved at en klasse kun er en pointer til heap'en... Derfor har jeg lidt svært ved at se den helt store forskel mellem @ListItem og Pointer(ListItem)... Det er måske fordi Pointer(ListItem) giver pointeren til heap'en og @ListItem giver en pointer til pointeren til heap'en?

Jeg prøver det lige lidt senere... Har lidt travlt her til morgen! ;)
Avatar billede zimp Nybegynder
26. september 2002 - 08:36 #11
Jeg har ikke kigget din kode nærmere igennem, men kan det tænkes at problemet er at du har flere threads der tilgår samme TListView? Det kan nemlig give nogle "sjove" exceptions. Det er næsten dømt til at crashe før eller siden hvis der ikke er en eller anden form for synkronisering (TCriticalSection, mutex, Synchronize...).
Avatar billede hermandsen Juniormester
26. september 2002 - 10:35 #12
>>zimp
Du må gerne fortælle mere.... Jeg har aldrig lavet en multithreaded applikation, men det er jo egentlig det jeg er i gang med! :)
Avatar billede zimp Nybegynder
26. september 2002 - 10:42 #13
Ok, vi kan tage et simpelt eksempel. Hvis du har en form med et memo, og laver f.ex. 10 threads der alle sammen kalder Form1.Memo1.Lines.Add('some text') så vil du få et crash før eller siden. VCL er ikke threadsafe, så hvis flere threads arbejder på samme komponent vil det næsten altid gå galt.

Hvordan får du fyldt dit TListView? Smider hver thread selv data i det, eller hvordan?
Avatar billede hermandsen Juniormester
26. september 2002 - 10:54 #14
Programmet henter filer ind i ListView'et med en OpenDialog/SelectDirectory, og det er helt app-baseret... Det eneste sted der bruges Threads er når man henter en fil... Hvis jeg nu sætter et delay ind på f.eks. 500 ms for hver Thread der opdaterer i ListView'et, kan det så gå?
Jeg kan godt se at det går galt hvis du hele tiden opdaterer noget fra flere Threads, men et delay på et par milisek. kan det ikke gå?
Avatar billede zimp Nybegynder
26. september 2002 - 11:04 #15
Prøv at fjerne al tilgang til ListView'et fra dine threads. Lav istedet en (eller flere) funktioner i form-klassen. Hvis vi tager mit memo som eksempel, så kan det se således ud:

var
  VCLSection : TCriticalSection // (syncObjs.pas)

...somewhere...
VCLSection := TCriticalSection.Create;
...............

procedure TForm1.AddMemoLines(Line: string);
begin
  VCLSection.Enter;
    Memo1.Lines.Add(Line);
  VCLSection.Leave;
end;

Hvis threads'ne alle kalder denne funktion, så kan de adde til memo'et lige så tosset de vil, uden crash.

Det eneste du skal være opmærksom på er at der også er en mainthread, dvs. den der kører når man sidder og klikker på formen. Den skal også benytte VCLSection hvis den arbejder på komponenten.
Avatar billede hermandsen Juniormester
26. september 2002 - 11:05 #16
Det jeg synes er underligt er at jeg opdaterer indholdet af en ListItem når der smides en TFile i TFileList'en...

procedure TFile.UpdateNewListItem;
begin
  with ListItem^ do
  begin
    Caption := vName;
    SubItems.Add(PrefixKB(vSize));
    SubItems.Add(PrefixKB(vDownloaded));
    SubItems.Add(GetFileStatus(vStatus));
    SubItems.Add(vSourceDir);
    SubItems.Add(vDestDir);
    Data := @Self;
  end;
end;

Den ses i slutningen af CreateNew constructor'en, og den virker jo fint nok... Kan problemet være at Data-property'en ikke kan sættes til @Self under constructoren?
Avatar billede hermandsen Juniormester
26. september 2002 - 12:25 #17
Jeg har lige siddet med en kammerat og debugget (ret meget faktisk)...

Her er hvad vi kom frem til:

Ved UpdateNewListItem under Data := @Self; siger vi at @Self har adressen $69F890... Hvis jeg typecaster den til en TFile, så har den det indhold den skal...

Når jeg så skal lave en anden operation med TListItem.Data, så har den godt nok den rigtige adresse ($69F890)... Konverterer jeg så den til en TFile, så har den værdien nil!!! Hvorfor sker det?!?
Avatar billede zimp Nybegynder
26. september 2002 - 12:30 #18
Hvad mener du med at konvertere? Typecast? Prøv først at smide den over i en variabel af typen pointer og se hvad der ligger i den.

Split det i det hele taget op i flere linier, så bliver det meget nemmere at debugge.

(F.ex. PFile(lvFiles.Items.Item[0].Data)^.ListItem^.Caption := 'Hest';)
Avatar billede hermandsen Juniormester
26. september 2002 - 15:25 #19
Jeg debugger (Ctrl+F7)...

Så, nu har jeg løst det første problem...

Det gjorde jeg ved at ændre AddNew-proceduren for TFileList:

procedure TFileList.AddNew(SourceFile, DestDir: String; ListItem: TListItem);
var
  I: Integer;
begin
  I := GetNewID;
  SetLength(vFiles, I+1);
  vFiles[I] := TFile.CreateNew(SourceFile, DestDir, I, ListItem);
  ListItem.Data := @vFiles[I]; //<- den er ny!
end;


I mellemtiden er der optrådt et nyt problem (igen en pointer)! :)

Fra min form kalder jeg en procedure:

procedure TFileList.AddToQueue(P: PFile);
var
  I: Integer;
begin
  //Lige meget (start)
  I := GetAvailableThread;
  if I = -1 then
  begin
    P^.Status := fsQueued;
    vQueue.Push(P);
  end //Lige meget (slut)
  else
  begin
    P^.Status := fsDownloading;
    vDownloads[I].Download(P);
  end;
end;

TFile.Status er en property hvor write kalder proceduren:

procedure TFile.SetStatus(const Value: TFileStatus);
begin
  ListItem^.SubItems.Strings[LV_COLUMNS_STATUS] := GetFileStatus(Value);
end;

GetFileStatus og LV_COLUMNS_STATUS skal i ikke bekymre jer om, det virker!!! Det som ikke virker er ListItem^. Den er erklæret som en ^TListItem. I constructoren kalder jeg proceduren New(ListItem), hvilket jeg ikke er helt sikker på er nødvendigt, og den bliver selvfølgelig Dispose'd i destructoren... Det underlige er nu at jeg ikke kan skrive til ListItem'en i den procedure...

Jeg har også lige prøvet at lave TFile's constructor om så der i stedet følger en PFile med som parameter... Det virkede heller ikke! :(
Nogle bud?
Avatar billede hermandsen Juniormester
22. oktober 2002 - 12:36 #20
Hejsa... Hvis i stadig kan huske mig, så tænkte jeg på følgende:

Hvis jeg i min TFile laver pointer-variablen til en TListItem, altså ^TListItem, om til en TListItem-variabel, kan det så komme til at kræve for mange ressourcer, eller bliver det bare det samme som før da det jo egentlig er en pointer til heap'en!?
Avatar billede hermandsen Juniormester
23. oktober 2002 - 01:26 #21
HURRA!!!!!!!!!!! Jeg fik det til at virke... Jeg gjorde som beskrevet for oven ved at bruge en klasse som variabel i stedet for en pointer... Rart!!! :)

I får lidt lidt point for besværet og den lange svartid... ;)

>>zimp
Du må også godt lige lægge et svar, så deler jeg nogle point ud... ;)
Avatar billede hermandsen Juniormester
26. oktober 2002 - 19:29 #22
>>zimp
Du skal til at skynde dig lidt! ;)
Avatar billede hermandsen Juniormester
28. oktober 2002 - 09:15 #23
Nå, too late...
Så er der ellers lukket her...
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester