[Delphi] Pointer probleem

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Megamind
  • Registratie: Augustus 2002
  • Laatst online: 10-09 22:45
Ik ben bezig met een simpele TCP Chat server in Delphi, met meerdere chatroom ondersteuning.

Nu is de opzet vrij eenvoudig
  • een verbonden client wordt een TClient object
  • een Client verbind en maakt een chatbox aan
  • Deze chatbox heeft een host, welke een pointer is naar de Client
  • Deze Client krijgt weer een pointer ChatBox naar de zojuist gemaakte ChatBox
Klinkt makkelijk op papier, ik heb me ingelezen hoe pointers werken e.d. maar er zijn een aantal dingen niet duidelijk.

Ik krijg deze versimpelde code niet aan de praat, als je op de 2e knop drukt zou je de naam van de host moeten zien, ik heb een pointer opgeslagen naar de user in de TChatbox, maar ik krijg een AV.

Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
  PClient = ^TClient;
  PChatBox = ^TChatBox;

  TChatBox = class(TObject)
  public
    ID: Integer;
    Host: PClient;
    procedure Who;
  end;

  TClient = class(TObject)
    Name: String;
    ChatBox: PChatBox;
    procedure Connect;
    procedure Join(AChatBox: PChatBox);
  end;

  TChatBoxList = class(TObjectList)
  public
    function AddChatBox: PChatBox;
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ChatList := TChatBoxList.Create;
  UserList := TObjectList.Create;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  UserList.Free;
  ChatList.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Client: TClient;
begin
  Client := TClient.Create;
  Client.Connect;
  UserList.Add(Client);
end;

procedure TClient.Connect;
var
  PNewChatBox: PChatBox;
begin
  Name := 'test';

  PNewChatBox := Form1.ChatList.AddChatBox;
  PNewChatBox^.Host := @Self;

  Join(PNewChatBox);
end;

function TChatBoxList.AddChatBox: PChatBox;
var
  NewChatBox: TChatBox;
begin
  NewChatBox := TChatBox.Create;
  NewChatBox.ID := 9;
  NewChatBox.Host := nil;
  Add(NewChatBox);
  Result := @NewChatBox;
end;

procedure TClient.Join(AChatBox: PChatBox);
var
  User: PClient;
begin
  User := AChatBox^.Host;
  ChatBox := AChatBox;
  ShowMessage(User^.Name);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  TChatBox(ChatList.Items[0]).Who;
end;

procedure TChatBox.Who;
var
  User: PClient;
begin
  User := PClient(Host);
  // En hier komt een AV
  ShowMessage(User^.Name);
end;


Maar als ik Join procedure aanroep staat er een showmessage die als het goed is dezelfde Pointer opvraagt en deze uitkomst is gewoon, maar doe ik procedure Who dan gaat het fout :?

Ik zit echt met een raadsel want er is erg weinig op te vinden op het internet, alleen wat basis pointer gebruik, maar niet zoals hier.

Acties:
  • 0 Henk 'm!

  • MicroWhale
  • Registratie: Februari 2000
  • Laatst online: 10:50

MicroWhale

The problem is choice

Zoals ik het hierboven lees is Host al een PClient. Typecasten heeft geen zin, dus je kunt net zo goed:

code:
1
2
3
4
procedure TChatBox.Who;
begin
  ShowMessage(Host^.Name);
end;


gebruiken.

Verder zou de pointer wel eens null kunnen zijn. ff checken dus.

[ Voor 14% gewijzigd door MicroWhale op 26-01-2009 15:22 ]

Het enige belangrijke is dat je vandaag altijd rijker bent dan gisteren. Als dat niet in centen is, dan wel in ervaring.


Acties:
  • 0 Henk 'm!

  • The Fox NL
  • Registratie: Oktober 2004
  • Laatst online: 10:52
Je werkt met classes, en een variabele van het type class is een pointer naar die class. Je moet dus werken zoals je in Button1Click werkt. Gewoon een variabele Client: TClient. Die Client is een pointer (je kan hem ook op nil checken etc.). PClient is dus een pointer die wijst naar een pointer die op zijn beurt wijst naar een TClient instantie. Gewoon afstappen van die pointers.

Dus gooi eerst de twee regels weg, die pointers heb je niet nodig. Daarna je code aanpassen zodat het er ongeveer zo uit gaat zien:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
procedure TClient.Connect;
var
  NewChatBox: TChatBox;
begin
  Name := 'test';

  NewChatBox := Form1.ChatList.AddChatBox;
  NewChatBox.Host := Self;

  Join(NewChatBox);
end;

function TChatBoxList.AddChatBox: TChatBox;
var
  NewChatBox: TChatBox;
begin
  NewChatBox := TChatBox.Create;
  NewChatBox.ID := 9;
  NewChatBox.Host := nil;
  Add(NewChatBox);
  Result := NewChatBox;
end; 


Edit: ik zal alvast verklappen dat je code dan wel 'test' laat zien als je op Button2 drukt.

[ Voor 4% gewijzigd door The Fox NL op 26-01-2009 19:56 ]


Acties:
  • 0 Henk 'm!

  • Megamind
  • Registratie: Augustus 2002
  • Laatst online: 10-09 22:45
Aha ok, ik was namelijk bang dat hierdoor:
NewChatBox.Host := Self;
De TClient hierin werd opgeslagen, terwijl deze in princiepe al ergens anders opgeslagen was, en zodoende er 2 verschillende objecten door elkaar kwamen.

Acties:
  • 0 Henk 'm!

  • The Fox NL
  • Registratie: Oktober 2004
  • Laatst online: 10:52
Megamind schreef op maandag 26 januari 2009 @ 20:31:
Aha ok, ik was namelijk bang dat hierdoor:
NewChatBox.Host := Self;
De TClient hierin werd opgeslagen, terwijl deze in princiepe al ergens anders opgeslagen was, en zodoende er 2 verschillende objecten door elkaar kwamen.
Nee hoor, Self is gewoon een pointer. Dus NewChatBox.Host wijst naar de Client waar jij je Connect op aanroept en niet een gedupliceerd exemplaar.

Verder zie ik dat je in je TClient.Connect een verwijzing hebt naar Form1. Dit is een 'bad practice' en iets wat je niet moet doen. Beter is om Connect een parameter mee te geven zodat je daar je ChatBoxList aan door kan geven.

Acties:
  • 0 Henk 'm!

  • Megamind
  • Registratie: Augustus 2002
  • Laatst online: 10-09 22:45
Dit is een sterk voorbeeld dat ik behoorlijk geript heb van andere dingen dus ik moest een beetje improviseren om het iig werkend te krijgen, maar ik zal in ieder geval naar kijken.

Nu is alleen mijn vraag, wanneer gebruik je wel pointers dan in een praktische toepassing?

Acties:
  • 0 Henk 'm!

  • The Fox NL
  • Registratie: Oktober 2004
  • Laatst online: 10:52
Een praktische toepassingen zijn API calls en records.

Stel je hebt het volgende record:

Delphi:
1
2
3
4
5
type
  TRMyRecord = record
    Id: Integer;
    Name: string;
  end;


Die kun je in een TList stoppen, maar een TList slikt alleen pointers, dus wil je een lijstje met records bijhouden, dan doe je het volgende:

Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
type
  //pointer type naar mijn record
  PRMyRecord = ^TRMyRecord;

procedure FillAndEmptyList;
var
  MyList: TList;
  Item: PRMyRecord;
  i: Integer;
begin
  MyList := TList.Create;
  try
    for i := 0 to 99 do //vul MyList met 100 instanties van TRMyRecord
    begin
      New(Item); //geheugen alloceren voor een nieuw record
      { Het record nu vullen met data.
        Let op: we gebruiken Item.Id ook al is Item een Pointer naar TRMyRecord
        Eigenlijk zou je bij het gebruik van een pointer deze moeten dereferencen
        Maar Delphi is zo slim dat we dat niet hoeven te doen
        We kunnen ook Item^.Id schrijven
      }
      Item.Id := i;
      Item^.Name := IntToStr(i); //Item^.Name werkt dus ook gewoon
      MyList.Add(Item);
    end;

    Item := MyList[12];

    ShowMessage(Format('Id: %d, Name: %s',
     [Item.Id, PRMyRecord(MyList[13]).Name])); { Id: 12, Name: 13 }

    { Nu de lijst weer leegmaken, het gealloceerde geheugen
      geven we vrij }
    while MyList.Count > 0 do
    begin
      Item := MyList[0];
      Dispose(Item); //geheugen vrijgeven
      MyList.Delete(0);
    end;

  finally
    { MyList vrijgeven, we doen dit in een try finally clause,
      zo weten we zeker dat MyList wordt vrijgegeven, mocht er
      iets misgaan }
    MyList.Free;
  end;

end;


Verder heb je het nodig bij API calls:
Delphi:
1
2
3
4
5
6
7
8
function GetModuleFileNameFromHInstance(HInstance: Cardinal): string;
var
  lFileName: array [0..MAX_PATH] of Char;
  lLength: Integer;
begin
  lLength := GetModuleFileName(HInstance, PChar(@lFileName[0]), MAX_PATH);
  SetString(Result, lFileName, lLength);
end;

Je ziet dat ik het adres van het eerste item van de Char array cast naar een PChar (een pointer naar een Char). Ook nu kan Delphi een heleboel voor je doen op de achtergrond, want je kan regel 6 ook zo schrijven:
Delphi:
1
lLength := GetModuleFileName(HInstance, lFileName, MAX_PATH);

Acties:
  • 0 Henk 'm!

  • Megamind
  • Registratie: Augustus 2002
  • Laatst online: 10-09 22:45
Aha ik denk dat het begint te dagen, ik probeerde gewoon veel te veel zelf te doen, maar Delphi doet zelf al veel op de achtergrond, waar ik me geen zorgen om hoef te maken, vandaar dat pointer gebruik niet veel over geschreven staat.

Je uitleg is helemaal helder, bedankt :)
Pagina: 1