Struktura TMessage

0

Czytam drugi rozdział podręcznika do Delphi i nie za bardzo rozumiem strukturę TMessage.
Chodzi o case skąd się bierze parametr Integer w niej ? :|
http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_5#id-Struktura-TMsg

type
  TMessage = packed record

    Msg: Cardinal;
    case Integer of
      0: (
        WParam: Longint;
        LParam: Longint;
        Result: Longint);
      1: (
        WParamLo: Word;
        WParamHi: Word;
        LParamLo: Word;
        LParamHi: Word;
        ResultLo: Word;
        ResultHi: Word);

  end;

Napisałem mini-programik który miał mi wyjaśnić to i owo, ale po jego użyciu rozumiem jeszcze mniej.
Kod

unit Unit1;

interface

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

type
  TMainForm = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    pos: TLabel;
    pos_y: TLabel;
    pos_x: TLabel;
    wiadomosc: TLabel;
  private
    procedure WmLButtonDown(var Msg : TMessage); message WM_LBUTTONDOWN;
  public
    { Public declarations }
  end;
  function CardToStr(Number: Cardinal) : String;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

{ TMainForm }

procedure TMainForm.WmLButtonDown(var Msg: TMessage);
begin
  pos.Caption := IntToStr(Msg.LParam);
  pos_x.Caption := IntToStr(LOWORD(Msg.LParam));
  pos_y.Caption := IntToStr(HIWORD(Msg.LParam));
  wiadomosc.Caption := CardToStr(Msg.Msg);
end;

function CardToStr(number: Cardinal) : String;
var
s: string;
begin
 s := '';
 if number = 0 then s := '0' else
 while number > 0 do begin
   s := chr(number mod 10+48) + s;
   number := number div 10;
 end;
 Result := s;
end;
end.

Formularz:

object MainForm: TMainForm
  Left = 0
  Top = 0
  Caption = 'MainForm'
  ClientHeight = 213
  ClientWidth = 426
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Label1: TLabel
    Left = 112
    Top = 80
    Width = 37
    Height = 13
    Caption = 'Normal:'
  end
  object Label2: TLabel
    Left = 112
    Top = 99
    Width = 10
    Height = 13
    Caption = 'Y:'
  end
  object Label3: TLabel
    Left = 112
    Top = 118
    Width = 10
    Height = 13
    Caption = 'X:'
  end
  object pos: TLabel
    Left = 176
    Top = 80
    Width = 105
    Height = 13
  end
  object pos_y: TLabel
    Left = 176
    Top = 99
    Width = 105
    Height = 13
  end
  object pos_x: TLabel
    Left = 176
    Top = 118
    Width = 105
    Height = 13
  end
  object wiadomosc: TLabel
    Left = 112
    Top = 144
    Width = 52
    Height = 13
    Caption = 'wiadomosc'
  end
end
0

z tego co pamiętam jeszcze o rekordach wariantowych to

  1. jeśli selektor jest nienazwany (tak jak w TMessage) to nie jest on w żaden sposób trzymany w pamięci a jest jedynie po to, bo za case coś musi być.
  2. jeśli selektor jest nazwany (wyglądało by to wtedy np. tak case rodzaj: Integer of) to jest on normalnym polem w rekordzi (w pascalu w zależności od jego wartości były dostępne odpowiednie pola wariantowe, w Delphi tego nie ma)
  3. część wariantowa zajmuje zawsze tyle ile jej największa opcja (polska języka trudna języka) - znaczy się wynika to z tego, że każdy wariant zajmuje ten sam obszar pamięci co pozostałe, w tym wypadku
    WParam zajmuje to samo miejsce co WParamLo i WParamHi i analogicznie pozostałe
0

Od pięciu lat programuję w PHP od 3 przy użyciu OOP.
Może ja jakiś nienormalny jestem, ale dla mnie cała idea rekordów jest poroniona.
Niestety nie bardzo do mnie trafia to tłumaczenie.
Proszę cię żebyś wytłumaczył to jak przeciętnemu idiocie a nie programiście.

  • Przy okazji możesz mi uświadomić że wcale ta idea poroniona nie jest ;-)

Chciałbym się dowiedzieć skąd wiadomo jaka będzie struktura tego rekordu.
Skoro jest w nim case to znaczy że istnieje jakaś zależność od wprowadzanych danych i jego struktury. -

  • Przynajmniej tak mi się wydaje. - W związku z tym skąd wiadomo jaka będzie struktura tego typu danych w danym momencie.
0
type
  TMessage = packed record
    Msg: Cardinal;
    case Integer of
      0: (
        WParam: Longint;
        LParam: Longint;
        Result: Longint);
      1: (
        WParamLo: Word;
        WParamHi: Word;
        LParamLo: Word;
        LParamHi: Word;
        ResultLo: Word;
        ResultHi: Word);
  end;

Zależność jest bardzo prosta: case MUSI być ostatnim elementem rekordu, więc wszystko, co jest wcześniej jest liczone standardowo jak w zwykłych rekordach, natomiast od miejsca, gdzie są kolejne rozejścia case'a, jest liczone od tego samego adresu. Zatem rekord ten ma postać:

Offset:
0                4                             8                            12                            16
+----------------+
| Msg (4 bajty)  |
+----------------+-----------------------------+-----------------------------+-----------------------------+
                 | WParam (4 bajty)            | LParam (4 bajty)            | Result (4 bajty)            |
                 +-----------------------------+-----------------------------+-----------------------------+
                 | WParamLo (2) | WParamHi (2) | LParamLo (2) | LParamHi (2) | ResultLo (2) | ResultHi (2) |
                 +-----------------------------+-----------------------------+-----------------------------+

W językach programowania taki 'case', a zatem rekordy o różnej strukturze, zajmujące ten sam obszar pamięci, zwane są uniami.

Nie tylko nie jest to poroniona idea, ale i bardzo przydatna. Komunikat o określonym typie (pole Msg) może nieść informację w postaci 2 liczb longint, albo 1 longint i 2 word, albo 4 word - bez kombinowania i ręcznego wyłuskiwania odpowiednich części danego pola. Ten sam obszar pamięci, zależnie od komunikatu, może być wykorzystywany w różny sposób.

Integer nie jest bezpośrednio polem. Jeśli jest nienazwanym typem liczbowym, pozwala tylko podczas kompilacji zapewnić, że ilość możliwości nie jest spoza zakresu danego typu, bo możliwości sposobów dostępu może być więcej niż 2. Tutaj są dwie możliwości, więc mógłby być tam równie dobrze boolean; najczęściej znajduje się tam typ wyliczeniowy lub o obciętym zakresie, ewentualnie właśnie boolean.

Jeśli jest tam pole nazwane to nie dość, że określa ilość możliwości, to jeszcze samodzielnie stanowi pole. Jest to przydatne, gdy dla różnych wartości tego pola inne pola mają inne typy, na przykład:

type TTypProduktu = (napoje, warzywa, pieczywo);

     TCena = packed record
       Nazwa: string;
       case Typ:TTypProduktu of
         napoje:   (litry: single;);
         warzywa:  (kilogramy: longint);
         pieczywo: (sztuki: dword);
     end;

Zależnie z jakim typem produktu masz do czynienia, możesz używać innego typu zmiennej. A skoro zajmują tą samą pamięć, nie zużywasz jej bezpośrednio, w przeciwności do sytuacji:

type TCena = packed record
       Nazwa: string;
       litry: single;
       kilogramy: longint;
       sztuki: longint;
     end;

Ważna jest tylko świadomość, że, zapisując do nakładających się pól, nadpisujesz dane z innego case'a.

Do czego się to może przydać? Ot choćby do czegoś takiego:

TParametr = packed record
      case boolean of
      TRUE:(
        EnergiaCzynnaA: double;
        EnergiaBiernaA: double;
        EnergiaPozornaA: double;
        EnergiaCzynnaB: double;
        EnergiaBiernaB: double;
        EnergiaPozornaB: double;);
      FALSE:(
        Energie: array[0..5] of double;);
     end;

Jak wykonujesz obliczenia z udziałem takiej struktury, łatwiej może być posługiwać się zmiennymi nazwanymi, a jak wypisujesz na ekran, może tablica będzie wygodniejsza. (Przykład z zawodowego życia wzięty.)

0

ja jeszcze dodam, że

  1. selektor nazwany
type
  TMessage = packed record
    Msg: Cardinal;
    case Cos: Integer of
      0: (
        WParam: Longint;
        LParam: Longint;
        Result: Longint);
      1: (
        WParamLo: Word;
        WParamHi: Word;
        LParamLo: Word;
        LParamHi: Word;
        ResultLo: Word;
        ResultHi: Word);
  end;
Offset:
0                4                8                            12                            16                            20
+----------------+----------------+
| Cos (4 bajty)  | Msg (4 bajty)  |
+----------------+----------------+-----------------------------+-----------------------------+-----------------------------+
                                  | WParam (4 bajty)            | LParam (4 bajty)            | Result (4 bajty)            |
                                  +-----------------------------+-----------------------------+-----------------------------+
                                  | WParamLo (2) | WParamHi (2) | LParamLo (2) | LParamHi (2) | ResultLo (2) | ResultHi (2) |
                                  +-----------------------------+-----------------------------+-----------------------------+
  1. z tego co mi wiadomo to najpierw były rekordy wariantowe ale z nazwanym selektorem a dopiero potem unie w C

  2. przykładem najlepiej obrazującym po co to jest struktura TRect

  TRect = packed record
    case Integer of
      0: (Left, Top, Right, Bottom: Longint);
      1: (TopLeft, BottomRight: TPoint);
  end;

gdzie TPoint to

  TPoint = packed record
    X: Longint;
    Y: Longint;
  end;
  1. co do wymogu aby część wariantowa była na końcu to można to bardzo łatwo obejść - wystarczy zrobić rekord składający się z rekordu :p

  2. główna idea jest taka, że pamięć zajmowana przez całą część wariantową jest równa rozmiarowi największego wariantu tej części - nie wiem jak to prościej napisać

  3. nie wiesz, który wariant jest aktywny ponieważ nie ma czegoś takiego jak aktywny wariant (kiedyś, w pascalu tak było)

0

Hmm. Zdaje się że pustka między moimi uszami powoli się wypełnia.
Jeszcze chciałbym się dowiedzieć kiedy jest rezerwowana pamięć dla zmiennej,
bo nie za bardzo pojmuję dlaczego poniższy kod działa poprawnie.
Powiedzmy:

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TForm2 = class(TForm)
  private
  public
  end;
  TZamowienie = packed record
    Potrawa : String;
    case Integer of
      0: (
        Cola : Integer;
      );
      1: (
        Herbata : Integer;
      );
    end;

var
  Form2: TForm2;
  Zamowienie: TZamowienie;
implementation

{$R *.dfm}
begin
  Zamowienie.Potrawa := 'Kebab';
  Zamowienie.Cola := 2;
  Zamowienie.Herbata := 1;
end.

Czy to znaczy że pamięć dla zmiennej jest rezerwowana w momencie przypisania jej wartości ?
Czy tak też dzieje się w przypadku zmiennych deklarowanych w sekcji var ? Czy zapisywane są one w pamięci przy przypisaniu im wartości ?

0

W przypadku zmiennych globalnych (tak jak tutaj) pamięć jest rezerwowana z rozpoczęciem wykonania procesu. Wyjątkiem tu jest oczywiście string alokowany dynamicznie (choć początkowo również z inicjacją procesu). Zmienne w ramach funkcji/procedur/metod są alokowane wraz z wywołaniem.

Zatem już po uruchomieniu programu, Zamowienie zajmuje 8 bajtów: 4 na adres stringa, 4 na pole integer. Przypisanie czegoś do stringa nie nadpisze danych w Cola/Herbata, bo w rekordzie znajduje się tylko wskaźnik na string, a nie sam string. Natomiast przypisanie 1 do Herbata, nadpisze pamięć, której wcześniej przypisano 2 jako Coli.

Może to ci coś wyjaśni:

type
  TZamowienie = packed record
    Potrawa : byte;
    case Napoj:byte of
      0: (
        Cola : byte;
      );
      1: (
        Herbata : byte;
        Kawa : byte;
      );
    end;

var
  Zamowienie:TZamowienie;

begin
Caption:=IntToStr(sizeof(Zamowienie));
//Rozmar 4 bajty

PDWORD(@Zamowienie)^:=$11223344;
//Przypisanie do obszaru pamięci wartości szesnastkowych kolejno:
// $44 $33 $22 $11 (standardowo odwrócona kolejność bajtów dla liczby 4 bajtowej)

{Pola mają wartości kolejno:
.Potrawa = $44
.Napoj = $33
.Cola = $22
.Herbata = $22 (bo zajmuje ten sam obszar pamięci co Cola)
.Kawa = $11
}
end;
0

Ok. Dziękuję bardzo trafia to do mnie.
Mam tylko problem z przykładem:

unit Unit2;

interface

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

type
  TForm2 = class(TForm)
    Label1: TLabel;
  private
    { Private declarations }
  public
    { Public declarations }
  end;
  TZamowienie = packed record
    Potrawa : byte;
    case Napoj:byte of
      0: (
        Cola : byte;
      );
      1: (
        Herbata : byte;
        Kawa : byte;
      );
    end;

var
  Form2: TForm2;
  Zamowienie: TZamowienie;
implementation
begin
Form2.Label1.Caption := IntToStr(sizeof(Zamowienie));

//Rozmar 4 bajty

PDWORD(@Zamowienie)^:=$11223344;
//Przypisanie do obszaru pamięci wartości szesnastkowych kolejno:
// $44 $33 $22 $11 (standardowo odwrócona kolejność bajtów dla liczby 4 bajtowej)

{Pola mają wartości kolejno:
.Potrawa = $44
.Napoj = $33
.Cola = $22
.Herbata = $22 (bo zajmuje ten sam obszar pamięci co Cola)
.Kawa = $11
}
end.

Kiedy uruchamiam:
First chance exception at $7C812A5B. Exception class EAccessViolation with message 'Access violation at address 00454709 in module 'Project2.exe'. Read of address 00000360'. Process Project2.exe (1548)

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