Metoda + SetTimer = błędne wartości zmienny

0

Witam!

A więc mam dość kłopotliwy problem mianowicie: mam klasę w której tworzę timer przez SetTimer WinAPI.

SetTimer(0, ID, 50, Addr(TMyClass.TimerProc));

I tu się zaczynają schody... Po utworzeniu klasy i wywołaniu procedury która odpala ten timer zmienne prywatne klasy maja wartości z kosmosu... Wszystko jest tworzone dynamicznie z dala od klas typu TForm i niech tak zostanie.
Czyli pytanie - co zrobić aby zmienne miały poprawne wartości?
Podejrzewam, że procedure TimerProc może się odwoływać do jakiegoś innego miejsca w pamięci niż to gdzie została utorzona klasa.

Będę bardzo wdzięczny za pomoc. Pozdrawiam!

0

TimerProc jest metodą klasową rozumiem

0
unit GraphAnimation;

interface

uses Windows;

type
  TMyClass = class
    private
      Speed, Count, Frame, ID : integer;
      procedure TimerProc; 
    public
      procedure Start;
  end;

implementation

procedure TAnimation.Start;
begin
  SetTimer(0, ID, Speed, @TMyClass.TimerProc);
end;

procedure TAnimation.Animated;
begin
  Frame := Frame mod Count;
  Inc(Frame);				// tutaj jest Breakpoint
end;

end.

Okroiłem kod z tego co niepotrzebne, żeby nie zaśmiecać. Wiec sprawa dokładnie wygląda tak: po utworzeniu klasy wywołuje procedure Start. Oczywiście zmienne Count = 4 a Frame = 0 - ustawiam przy tworzeniu klasy. Tam gdzie zaznaczyłem komentarzem Breakpoint mam ustawiony Watch na obie te zmienne.
Jak program wykona się do Breakpointa zmienne maja wartości Frame = -1989922561 a Count = 1677739234...

0

@TMyClass.TimerProc

To znaczy tyle, że wartość SELF będzie pusta/śmieciowa, bo nie ma dostępu do żadnych składowych. Kompilator nie wie o jaką klasę tobie chodzi, a ty mu rozkazałeś umieścić adres tej metody, bez żadnego kontekstu.

Rozwiązanie: Utwórz procedurę globalną która będzie wiedziała jaką metodę wywołać i którego obiektu, bo tego nie wiadomo teraz.

0
unit GraphAnimation;
 
interface
 
uses Windows;
 
type
  TMyClass = class
    private
      Speed, Count, Frame, ID : integer;
      procedure TimerProc; 
    public
      procedure Start;
  end;

Var
  MyClass: TMyClass;
 
implementation
 
procedure TAnimation.Start;
begin
  SetTimer(0, ID, Speed, @MyClass.TimerProc);
end;
 
procedure TAnimation.Animated;
begin
  Frame := Frame mod Count;
  Inc(Frame);                                // tutaj jest Breakpoint
end;
 
end.

Jeśli dobrze się orientuję to chyba jakoś tak powinno być.

0

Tylko taki detal, że zależy mi żeby kazdy utworzony obiekt posiadal wlasny timer. A jesli dam procedure globalna majac z 10 obiektow a kazdy z nich inna wartosc Speed to bedzie ciezko. Chyba ze wiesz jak to ominac:)

0
Taito napisał(a)

Chyba ze wiesz jak to ominac:)

Wątki, wątki, wątki...

0

SetTimer(0, ID, Speed, @MyClass.TimerProc);

To nie będzie działać. Kompilator co prawda zrozumie o co ci chodzi, ale to nie ty masz wywołać tą procedurę, tylko WinApi, a WinApi nie rozumie wywoływania klas. Kompilator nie może w wskaźniku do procedury jakieś przekazać też o jaką klasę chodzi.

Tylko taki detal, że zależy mi żeby kazdy utworzony obiekt posiadal wlasny timer. A jesli dam procedure globalna majac z 10 obiektow a kazdy z nich inna wartosc Speed to bedzie ciezko. Chyba ze wiesz jak to ominac:)

Programiści WinApi rozumią problem i dlatego definicja callbacka(której się nie trzymasz przy podawaniu swojego callbacka - grozi kreszami) wygląda tak:

MSDN napisał(a)

VOID CALLBACK TimerProc(
In HWND hwnd,
In UINT uMsg,
In UINT_PTR idEvent,
In DWORD dwTime
);

Masz tutaj też idEvent, które zawiera ID podane jako jeden z parametrów WinApi. Na tej podstawie możesz rozpoznać o który obiekt chodzi.

Wątki, wątki, wątki...

Tak, żeby je potem syncować, bawić się w masę rzeczy które nie są potrzebne. Jeżeli te procedury nie mają super dużo do roboty to nie widzę powodów aby bawić się w takie rzeczy.

0

Troche nieogarniam jak wątki miałyby mi tu pomóc... Zreszta przy bardziej rozbudowanej aplikacji gdzie takich obiektow bede mial pewnie dziesiątki jak nie setki = dziesiątki/setki wątków? Samą obsługe obiektow tej klasy pewnie zrobie w wątku ale bez przesady. Chcialbym tylko uzyskac możliwość uzycia SetTimer wewnatrz klasy i zmieniac jej zmienne co pewien czas.

0
Taito napisał(a)

Troche nieogarniam jak wątki miałyby mi tu pomóc...

Tak jest, program piszesz w OOP, a tajmery chcesz oprzeć o funkcje WinAPI, które tak jak -123oho pisze nie rozumieją klas;

Taito napisał(a)

Zreszta przy bardziej rozbudowanej aplikacji gdzie takich obiektow bede mial pewnie dziesiątki jak nie setki = dziesiątki/setki wątków?

A tajmerów z WinAPI zrobisz też tyle samo, co obiektów? Czy ktoś każe Ci uruchamiać osobną instancję wątku dla każdego obiektu? Zamiast babrać się w przestarzałe metody skorzystaj z mniej przestarzałej klasy TThread - stwórz sobie nawet jedną instancję wątku, jeśli tyle Ci wystarczy; Poza tym masz synchronizację, która może się bardzo przydać;

0

Zamiast babrać się w przestarzałe metody

Od kiedy to SetTimer jest przestarzałą metodą? Po prostu jest metodą dla osób które nie wymagają dużej wydajności (tak jak to jest z TThread) ani nie wymagają pierniczenia się z takimi rzeczami jak semafory, sekcje krytyczne i inne problemy w stylu deadlocków.

Poza tym masz synchronizację, która może się bardzo przydać;

A na co komu synchronizacja? Ja nigdy na to nie patrzyłem na to jako ficzur, raczej jako ograniczenie wątków które przez to nie są takie hop siup. O dziwnych błędach wynikających z niewłaściwego użycia synchronizacji nie mówiąc.

Podsumowując, zrobiłbym jeden timer który sprawdzałby wszystkie obiekty i wykonywał te które przekroczyły swój czas. Jeżeli nie wymagasz specjalnej wydajności ani idealnej dokładności to rozwiązanie jak znalazł.

0

Myślałem już wcześniej nad taka opcja - jeden timer obsługuje wszystkie te obiekty. Ale czy to nie za bardzo zamuli cały program? Zwłaszcza, że to tylko część całości w której będą bardziej wymagające procedury/funkcje (sprzętowo). na razie próbuje dojść do ładu z tym callbackiem ale coś nic konkretnego nie moge znaleŹć jak to zrobić :(

0
Taito napisał(a):

Myślałem już wcześniej nad taka opcja - jeden timer obsługuje wszystkie te obiekty. Ale czy to nie za bardzo zamuli cały program? Zwłaszcza, że to tylko część całości w której będą bardziej wymagające procedury/funkcje (sprzętowo).

No i dochodzimy do tego że powinieneś nam opowiedzieć co to ma robić to doradzimy czego najlepiej użyć. Póki co to jest zgadywanie.

na razie próbuje dojść do ładu z tym callbackiem ale coś nic konkretnego nie moge znaleŹć jak to zrobić :(

Bo czasami trzeba samemu coś zrobić. Na MSDN masz całkiem znośną dokumentację, nie widzę problemu w przepisaniu jednej deklaracji na Pascal...

0

Ok nie ma co sobie życia utrudniać - robie to na jednym timerze z pętlą.
Dzięki wszystkim za pomoc.
Pozdrawiam :)

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