Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Вызов функции по таймеру


Автор: Lexicss 26.5.2007, 00:40
Привет, народ! Давно уже не заходил на форум. Вот тут упёрся в одну проблемку, может вы поможете разобрать и решить. 
Работаю на Delphi 5 и вот наткнулся на такую ситуацию. Есть у меня одна функция и мне её надо вызвать спустя какой-то интервал времени, делаю это с помощью SetTimer. В чём странность? Так вот: в 98-ой винде работает всё нормально. В XP функция таймера срабатывает дважды, а в 2000-ой мало того что срабатывает функция  дважды, так после второго завершения программа вылетает с исключением "Access violation", притом не важно что выполняется в функции(процедуре) вызываемой по таймеру.
Приведу приметивнейший пример 
Код

procedure MyFunc(hWnd: THandle; uMsg: DWord; idEvent: DWord; dwTime: DWord);

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
  SetTimer(0,0,5000,@MyFunc);
end;

procedure MyFunc(hWnd: THandle; uMsg: DWord; idEvent: DWord; dwTime: DWord);
begin
  if dwTime = 0
  then beep;
end;


Проведя некоторые исследования, я заметил такую особенность, если число параметров в вызываемой функции не превышает 3, то такой ситуации не возникает(из ассемблерного кода видно, что если в функции большех трёх параметров, то 4-ый и последующие уже передаются не через регистры, а через стэк). Ещё одна особенность, если процедуру MyFunc вызывать с соглашение stdcall, то тоже всё нормально. 
Очень важно узнать почему так? Может здесь какая-то несовместимость Delphi 5 и Windows 2000(и выше)? 
И как тогда выкручиваться при использовании SetTimer в Delphi 5, так как программа должна быть универсальной и работать как под 98-ой, как под 2000-ой и др одинаково?

Автор: Palladin 26.5.2007, 00:58
Короче первое что пришло на ум то и пишу, вообще заводиш глобальную переменную к примеру x типа integer, и там же присваеваеш её начальное значение 0, после этого в условие таймера пишеш 
Код

if x=0 then 
 begin 
   SetTimer(0,0,5000,@MyFunc); 
   x:=x+1;
 end;


Покатит smile

Добавлено через 25 секунд
Тогда будет выполнятся лиш один разsmile

Добавлено через 1 минуту и 22 секунды
Так же я не вижу весь код, да и задачи твоей команды, но принцип тот же

Добавлено через 1 минуту и 53 секунды
Отпиши получилось или нетsmile

Автор: Lexicss 26.5.2007, 01:09
Ну логика решения проблемы понятна, но всё же мне очень важно узнать почему функция вызывается дважды мгновенно(притом ещё и вылетаю в исключение). Это ж ведь ненормально.
А весь код и не нужен, он на самом деле громоздкий, а сама суть здесь изложена в этом коде, здесь такая же ситуация получается. 
Могу весь код выложить этой ситуации, дабы можно было его скопировать и проверить у себя.
Код

unit Main;

interface

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

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

procedure MyFunc(hWnd: THandle; uMsg: DWord; idEvent: DWord; dwTime: DWord);

implementation

{$R *.DFM}



procedure TForm1.FormCreate(Sender: TObject);
begin
  SetTimer(0,0,5000,@MyFunc);
end;

procedure MyFunc(hWnd: THandle; uMsg: DWord; idEvent: DWord; dwTime: DWord);
begin
  if dwTime = 0
  then beep;
end;

end.


Автор: Alexeis 26.5.2007, 01:12
А чем компонент TTimer не угодил?

Автор: Palladin 26.5.2007, 01:14
Хм, ну почему эт конечно хороший вопрос, ответа на него у мя нет, может быть функция settimer юзает какието функции Windows а в XP они несколько отличны от одноименной в 98 и др.
вообщем ответа у меня нет, решение есть и я его тееб отписалsmile

Добавлено через 1 минуту и 40 секунд
Да-да Алексей прав, юзай таймер в конце enabled в false да и все, но впринципе это лиш альтернатива моего метода, хотя скорее наоборотsmile

Автор: Alexeis 26.5.2007, 01:17
Цитата(Lexicss @  26.5.2007,  00:40 Найти цитируемый пост)
Ещё одна особенность, если процедуру MyFunc вызывать с соглашение stdcall, то тоже всё нормально. 

  Конечно все калбэк функции по дефлоту должны быть stdcall.

Автор: Lexicss 26.5.2007, 01:46
Цитата
А чем компонент TTimer не угодил? 

Дело в том что программу писал не я, я дорабатываю, следует оставлять всё так как было задумано разработчиком. Притом в 98-ой всегда всё было нормально, а как перешли на 2000-ую вот такая хрень вылезла. 
Цитата
Конечно все калбэк функции по дефлоту должны быть stdcall. 

Вот этого кстати не знал. А почему? Совет, впринципе правильный, но тогда напрашивается вопрос почему в 98-ой всё работает нормально без stdcall. Я вот слышал об этом  соглашении что его обычно применяют в описаниях API-функций.

Я, наверное, задаю слишком много вопросов. Просто моей задачей стоит не  решить конкретную ситуацию і забыть о ней, а именно разобраться почему так получается. Возможно, разобравшись в этом  я(да и не тока я) смогу решить и другие проблемы, где есть такое же, но жучки ещё не дали о себе знать.
Но всё равно спасибо за советы. Если кто-то что-то ещё знает об этом, пишите.

Поделюсь ещё своими наблюдениями.
Если изменить код, подавать в MyFunc не 4, а 3(например) параметра, то тогда всё нормально, работает без всяких повторных вызовов и исключений, и stdcall не нужен. Разбирая эти две ситуации в ассемблерном коде обнаружил - (в 4-х параметровой smile ) стэк как бы смещён на 4 байта, и когда вызывается команда ret(выход из MyFunc), то берётся из него не тот адрес, а как раз попадается ячейка с адресом начала этой функции(поэтому она и запускается снова), в 3-х параметровой - всё нормально, при вызове ret извлекается совершенно другой "некий" адрес и программа продолжает свою нормальную работу. Т.е. такое чувство, что при генерации машинного кода в 4-х параметровой функции пропущена одна команда push.

Автор: MetalFan 26.5.2007, 09:16
а зачем извраты такие? 
все системные callback-функции, как уже говорилось, должны быть с директивой stdcall.

Цитата(Lexicss @  26.5.2007,  01:46 Найти цитируемый пост)
почему в 98-ой всё работает нормально без stdcall

наверняка это просто случайность.

выскажу свои аргументы... 
вот выдержки из хелпа:
Цитата(DelphiHelp)


  • The register and pascal conventions pass parameters from left to right; that is, the left most parameter is evaluated and passed first and the rightmost parameter is evaluated and passed last. The cdecl, stdcall, and safecall conventions pass parameters from right to left.

  • The register convention uses up to three CPU registers to pass parameters, while the other conventions pass all parameters on the stack.


The default register convention is the most efficient, since it usually avoids creation of a stack frame. (Access methods for published properties must use register.) The cdecl convention is useful when you call functions from shared libraries written in C or C++, while stdcall and safecall are recommended, in general, for calls to external code. On Windows, the operating system APIs are stdcall and safecall. Other operating systems generally use cdecl. (Note that stdcall is more efficient than cdecl.)

из чего делаем следующие выводы:
по умолчанию в делфи используется способ передачи до трех параметров через регистры процессора (директива register). параметры передаются слева направо.
в системе по умолчанию используется способ передачи, при котором параметры передаются через стэк, порядок параметров справа налево.
соотв. при обозначении в твоей функции трех параметров и без указания stdcall. внутрь попадает абсолютно ничего не значащие значения регистров. а если указано 4 параметра, то последний извлекается из стэка... чем его видимо и рушит...

Автор: Lexicss 27.5.2007, 10:15
MetalFan, спасибо. 
Вопрос почти решён. Остатётся только непонятка, почему работает в 98-ой без stdcall???  smile 
Притом программа работает уже давно(более 5 лет).

Автор: Yanis 27.5.2007, 10:26
Цитата(Lexicss @  27.5.2007,  11:15 Найти цитируемый пост)
Остатётся только непонятка, почему работает в 98-ой без stdcall???

Это так же как и с 98-м -- куча кода, но почему он работает непонятно smile 

Автор: Lexicss 28.5.2007, 22:21
Как я понял из вышеизложенных постов: Моя процедура MyFunc является callback API-шной и следовательно нуждается в директиве  stdcall. Если я понял что-то не так - поправьте, пожалуйста. 
Но всё же остаётся открытым вопрос, почему в Windows 98 эта функция работатет нормально без stdcall, хоть в ней и больше 3-х параметров? Может ещё какие будут варианты объяснения кроме случайности? Если кто что толкового подскажет, буду признателен. ;)

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)