Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Pointer bei Pascal


Peter18 - Mo 19.01.15 18:01
Titel: Pointer bei Pascal
Ein freundliches Hallo an alle,

anscheinend habe ich bei den Pointern in Pascal irgend etwas noch nicht richtig verstanden. Experimente mit der CAPI liefern Ergebnisse, doch was ich sehe scheint nicht richtig zu sein. Die Funktion "CAPI_GET_MESSAGE" gibt einen Pointer auf die eigentliche Nachricht zurück. Im Kopfteil stehen Informationen wie Länge und Art der Nachricht. Vielleicht habe ich die Schnittstelle auch nicht richtig deklariert, so das der Pointer in die "Wüste" zeigt.

Die Deklaration der Schnittstelle:

Delphi-Quelltext
1:
2:
  Function  CAPI_GET_MESSAGE    (     D_AppID                : Longint;
                                  Var lpCapiBuffer           : Pointer    ): DWord; stdcallexternal 'Capi2032.dll';


Die Prozedur "CheckCall" wird von einem Timer gestartet:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
procedure TForm1.CheckCall( Sender: TObject );
var
  Buffer : Pointer;
  S      : String;

begin
  Res := CAPI_GET_MESSAGE( AppID, Buffer );

  S := '';
  if Res = $01104 then S := '   Warteschlange leer';
  if Res = $01102 then S := '   Command Error'     ;
  if Res <> $01104 then NoMsg := false;
  if not NoMsg then
  begin
    Form1.Memo1.Lines.Add( 'CAPI_GET_MESSAGE' );
    Form1.Memo1.Lines.Add( 'Res:  ' + IntToHex( Res,             8 ) + S );
    Form1.Memo1.Lines.Add( 'Buff: ' + IntToHex( Integer(Buffer), 8 )     );
    Form1.Memo1.Lines.Add( '' );
    if Res = $01104 then NoMsg := true;
  end;
  if Res = 0 then pLookAtMesssage( Buffer );
end;


Die Prozedur "pLookAtMesssage" soll die Nachricht auswerten:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
procedure pLookAtMesssage( Buffer : Pointer );
var
  PBuffer : P_MessageHeader;

begin
  PBuffer := Buffer;
  Form1.Memo1.Lines.Add( 'Buffer:'  );
  Form1.Memo1.Lines.Add( 'Ptr:        ' + IntToHex( Integer(Buffer),     8 ) );
  Form1.Memo1.Lines.Add( 'Len:        ' + IntToHex( PBuffer.Length,      8 ) );
  Form1.Memo1.Lines.Add( 'AppId:      ' + IntToHex( PBuffer.AppId,       8 ) );
  Form1.Memo1.Lines.Add( 'Command:    ' + IntToHex( PBuffer.Command,     8 ) );
  Form1.Memo1.Lines.Add( 'Subcommand: ' + IntToHex( PBuffer.Subcommand,  8 ) );
  Form1.Memo1.Lines.Add( 'MsgNr:      ' + IntToHex( PBuffer.MsgNr,       8 ) );
  Form1.Memo1.Lines.Add( ''  );  
end;


Der Kopf ist folgendermaßen deklariert:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
Type MessageHeader = Record
  Length     : DWord;
  AppId      : DWord;
  Command    : Byte ;
  Subcommand : Byte ;
  MsgNr      : DWord;
end;

Type P_MessageHeader = ^MessageHeader;


"Subcommand" sollte einen Wert von $80 bis $83 haben,aber die Ergebnisse sehen so aus:
Zitat:
LISTEN_REQ
Res: 00000000
Msg: 00000000

CAPI_GET_MESSAGE
Res: 00000000
Buff: 0014E278

Buffer:
Ptr: 0014E278
Len: 0006000E
AppId: 00128105
Command: 00000001
Subcommand: 00000000
MsgNr: 00060000

CAPI_GET_MESSAGE
Res: 00001104 Warteschlange leer
Buff: 00000000

CAPI_GET_MESSAGE
Res: 00000000
Buff: 0014E278

Buffer:
Ptr: 0014E278
Len: 0006002F
AppId: 002B8202
Command: 00000001
Subcommand: 00000004
MsgNr: 80080001

CAPI_GET_MESSAGE
Res: 00001104 Warteschlange leer
Buff: 00000000


uall@ogc - Mo 19.01.15 22:49

Der Record wird wohl falsch sein, nimm mal einen "packed record".
Wenn lpCapiBuffer zurückgegeben wird, kannst du statt "var" besser "out" nehmen.

Ansonsten schau mal hier:
http://www.rueckerweb.net/isdncapi.html

kann auch sein dass TotalLength nur ein WORD (und kein DWORD ist), dann verschiebt sich alles und in deinem Beispiel ist die länge dann 0006, die AppId 002f002b und der Command 82.
Solltest da nochmal in den Spezifikationen nachschauen.


Peter18 - Di 20.01.15 16:09

Hallo Daniel,

danke für Deine Antwort! "packed record" scheint nicht notwendig zu sein, den beides funktioniert gleichermaßen, aber der Tipp mit "Word" statt "DWord" war gut. Da in der "CAPI-Beschreibung" nur Prozessorregister angegeben waren, habe ich anscheinend einen Fehler eingebaut. Nun sehe ich die richtigen Daten mit:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
Type MessageHeader = packed Record
  Length     : Word;
  AppId      : Word;
  Command    : Byte;
  Subcommand : Byte;
end;

Der Link ist auch gut, den hab ich leider bei meiner Suche nicht gefunden. Wie die Rufnummer extrahiert wird scheint mir etwas umständlich, aber ich habe die Software noch nicht so weit studiert.

Meine Idee war einen Pointer mit dem Typen eines offenen Arrays auf die Nummer zu setzen und dann Byte für Byte zu lesen. Wenn er einem Pascal-String entspricht kann man ihn vielleicht direkt herauskopieren. Aber da ist wieder der "Pascal-Pointer" (C ist einfacher). Ich habe einen Typ mit offenen Array erstellt:

Delphi-Quelltext
1:
Type Nr_Array = Array of Byte;                    

Dann den Zeiger der darauf zeigt:

Delphi-Quelltext
1:
Type P_Nr = ^Nr_Array;                    

In der Procedure ist die Variable folgendermaßen deklariert:

Delphi-Quelltext
1:
PNr : P_Nr;                    

Wenn ich dann folgender maßen zugreife

Delphi-Quelltext
1:
2:
    PNr := @PConnectInd.CalledPartyNumber;
    S   := IntToHex( PNr^[0], 2 );

erhalte ich einen Zugriffsfehler.

Ich hoffe auf einen Tipp, wo der Fehler liegt.

Grüße von der nebligen Nordsee (Ein Dichter liegt über dem Land)

Peter


WasWeißDennIch - Di 20.01.15 16:17

Für dynamische Arrays musst Du zunächst Speicher reservieren, das geht mit SetLength.


Peter18 - Di 20.01.15 16:56

Hallo WasWeißDennIch,

auch Dir Dank für die Antwort! Aber auch da gibt es eine Zugriffsverletzung. Ich habe auch probiert den Pointer hoch zu zählen, Banane, Fehler. In der Hilfe steht zwar Addition auf Pointer ist möglich aber es gibt kein Beispiel, an dem ich mich orientieren könnte.

Grüße von der nebligen Nordsee

Peter


Peter18 - Mi 21.01.15 17:15

Ein freundliches Hallo an alle,

schade keine weiteren Antworten, dann eine neue Pointerfrage und diese schließe ich ab. Der Teil funktioniert ja jetzt.

Grüße von der Nordsee

Peter


uall@ogc - Mi 21.01.15 21:26

Standardmäßig ist "packed record" und "record" nicht das gleiche, da ohne "packed" die Werte 4-Byte aligned werden.

Zum Testen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
type
  TTest = record // 8 Byte
    B: Byte;
    c: DWord;
  end;

  TTest2 = packed record // 5 Byte
    B: Byte;
    c: DWord;
  end;

begin
  Caption := Format('%d %d', [SizeOf(TTest), SizeOf(TTest2)]);
  // Ausgabe: 8 - 4
end;


Es kann aber sein, dass du Code eingebunden hast, der das Alignment austellt, näheres findest du unter:

http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devcommon/compdirsalignfields_xml.html

Wobei "packed" = {$A1} entspricht und ohne Angabe ein {$A4} verwendet wird.

Wenn du also Structs von c++ uebersetzt bzw. von der WinAPI, nimm besser ein "packed record".


SMO - Mo 16.02.15 18:50

user profile iconPeter18 hat folgendes geschrieben Zum zitierten Posting springen:

Die Prozedur "CheckCall" wird von einem Timer gestartet:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
procedure TForm1.CheckCall( Sender: TObject );
// ...
    Form1.Memo1.Lines.Add( 'CAPI_GET_MESSAGE' );
// ...
  if Res = 0 then pLookAtMesssage( Buffer );
end;


Da "CheckCall" eine Methode von Form1 ist, brauchst du Form1 nicht nochmal explizit anzugeben. Es wäre auch besser, in "pLookAtMesssage" nicht explizit auf Form1.Memo1 zuzugreifen. Denn das sind globale Abhängigkeiten, die man vermeiden möchte; außerdem müsstest du sämtliche Referenzen anpassen, falls du mal "Form1" oder "Memo1" umbenennst (und keine moderne Delphi-Version mit Refactoring hast, die die Bezeichner automatisch umbenennen kann). Etwas besser wäre also:


Delphi-Quelltext
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:
// Den Konventionen nach beginnen Typen mit T und Pointer mit P,
// aber diesen Konventionen muss man nicht unbedingt folgen.
type 
  PMessageHeader = ^TMessageHeader;
  TMessageHeader = packed record
    Length     : Word;
    AppId      : Word;
    Command    : Byte;
    Subcommand : Byte;
  end;          

procedure TForm1.CheckCall( Sender: TObject );
// ...
    Memo1.Lines.Add( 'CAPI_GET_MESSAGE' );
// ...
  if Res = 0 then pLookAtMesssage( Buffer; Memo1.Lines );
end;


procedure pLookAtMesssage( Buffer : PMessageHeader; InfoOutput: TStrings );
begin
  InfoOutput.Add( 'Buffer:'  );
  // Pointer auf Integer zu casten sollte man sich abgewöhnen, denn das geht
  // auf 64-Bit-Plattformen in die Hose, weil Pointer dort 64 Bit groß sind,
  // Integer aber immer noch 32. Benutze stattdessen UIntPtr oder NativeUInt.
  InfoOutput.Add( 'Ptr:        ' + IntToHex( UIntPtr(Buffer),    8 ) );
  InfoOutput.Add( 'Len:        ' + IntToHex( Buffer.Length,      8 ) );
  InfoOutput.Add( 'AppId:      ' + IntToHex( Buffer.AppId,       8 ) );
  InfoOutput.Add( 'Command:    ' + IntToHex( Buffer.Command,     8 ) );
  InfoOutput.Add( 'Subcommand: ' + IntToHex( Buffer.Subcommand,  8 ) );
  InfoOutput.Add( 'MsgNr:      ' + IntToHex( Buffer.MsgNr,       8 ) );
  // Alternative zu IntToHex: Format mit %x, achtstellig = %.8x
  // InfoOutput.Add( Format('MsgNr:      %.8x', [Buffer.MsgNr]));
  InfoOutput.Add( ''  );  
end;



Den Sinn und Zweck von "packed" hat uall@ogc bereits erklärt.
Es fehlt noch der wichtige Unterschied zwischen Statischen und dynamischen Arrays.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
type
  Nr_Array = Array of Byte;
  P_Nr = ^Nr_Array;

var
  PNr : P_Nr;
begin
  PNr := @PConnectInd.CalledPartyNumber;
  S   := IntToHex( PNr^[0], 2 );


Natürlich klappt das so nicht! Dynamische Arrays, wie auch Strings, sind bereits implizite Pointer. Sie zeigen auf einen dynamisch allozierten Speicherbereich, der vor der Adresse, auf die gezeigt wird, noch Zusatzinfos wie Länge und Referenzcount enthält. An der Adresse von "PConnectInd.CalledPartyNumber" stehen diese Daten nicht, weil es sich eben nicht um einen Delphi-DynArray-Typ handelt. Man sollte also keine Pointer auf Strings und Dynamische Arrays casten, wenn man nicht genau weiß, was man macht.
Was du suchst, ist ein offenes statisches Array, oder einen Byte-Pointer.



Delphi-Quelltext
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:
// Methode 1: Standardtyp aus SysUtils:
//  PByteArray = ^TByteArray;
//  TByteArray = array[0..32767] of Byte;
var
  PNr : PByteArray;
begin
  PNr := PByteArray(@PConnectInd.CalledPartyNumber);
  S   := IntToHex( PNr[0], 2 );


// Methode 2: Eigener, "offener" Array-Typ.
// Bereichsüberprüfung sollte natürlich ausgeschaltet sein, sonst gibt es Fehlermeldungen
// wenn versucht wird, auf Elemente nach [0] zuzugreifen
type
  PNrArray = ^TNrArray;
  TNrArray = array [0..0of Byte;

var
  PNr : PNrArray;
begin
  PNr := PNrArray(@PConnectInd.CalledPartyNumber);
  S   := IntToHex( PNr[0], 2 );


// Methode 3: Die "eleganteste", der Standardtyp PByte (= ^Byte) hat standardmäßig
// Pointerarithmetik und Arrayzugriff aktiviert -> {$POINTERMATH ON}
// Jedenfalls in modernen Versionen von Delphi, ab 2009 glaube ich.
var
  PNr : PByte;
begin
  PNr := PByte(@PConnectInd.CalledPartyNumber);
  S   := IntToHex( PNr[0], 2 );