Ответ на ваше второе редактирование:
Если вы хотите получить ответ, содержащий указатель на экземпляр TMyClass
, вам может не повезти. По сути, процедура, вызываемая Windows, имеет определенную подпись и не является объектным методом. Вы не можете напрямую обойти это, даже с помощью магии __closure
или procedure of object
, за исключением случаев, описанных ниже и в других ответах. Почему?
Windows не знает, что это объектный метод, и хочет вызвать процедуру с определенной сигнатурой.
Указатель больше не является простым указателем - он имеет две половины: экземпляр объекта и метод. Необходимо сохранить Self
, а также метод.
Кстати, я не понимаю, что не так с коротким падением за пределы объектно-ориентированного мира. Код не-OO не обязательно грязный , если используется хорошо.
Оригинальный ответ перед редактированием:
Это невозможно в точности так, как вы пытаетесь это сделать. Метод, который нужен SetTimer
, должен точно соответствовать подписи TIMERPROC
- см. Документацию MSDN . Это простая необъектная процедура.
Однако метод TMyClass.DoIt
является объектным методом. На самом деле он состоит из двух частей: объекта, для которого он вызывается, и самого метода. В Delphi это "procedure of object"
или "closure"
( читайте о процедурных типах здесь ). Таким образом, подписи несовместимы, и вы не можете сохранить экземпляр объекта, который вам нужен для вызова метода объекта. (Существуют также проблемы соглашения о вызовах - стандартные методы Delphi реализуются с использованием соглашения fastcall
, тогда как TIMERPROC
указывает CALLBACK
, который из памяти является макросом, который расширяется до stdcall
. Подробнее о вызове условности и особенно fastcall .)
Итак, что ты делаешь? Вам необходимо отобразить ваш не объектно-ориентированный обратный вызов в объектно-ориентированный код .
Есть несколько способов, и самый простой из них:
Если у вас когда-либо был только один таймер, то вы знаете, что при вызове обратного вызова таймера срабатывает именно этот таймер. Сохраните указатель метода в переменной типа procedure of object
с соответствующей подписью. См. Ссылку документации Embarcadero выше для более подробной информации. Вероятно, это будет выглядеть так:
type TMyObjectProc = procedure of object;
var pfMyProc : TMyObjectProc;
Затем инициализируйте pfMyProc
до nil
. В TMyClass.DoIt
установите для pfMyProc
значение @DoIt
, то есть теперь оно указывает на процедуру DoIt
в контексте этого конкретного экземпляра TMyClass
. Ваш обратный вызов может затем вызвать этот метод.
(Если вам интересно, переменные класса, имеющие такой процедурный тип, как это, хранят обработчики событий внутри. Свойства OnFoo
объекта VCL являются указателями на объектные процедуры.)
К сожалению, эта процедурная архитектура не объектно-ориентирована, но именно так она и должна быть.
Вот как может выглядеть какой-то полный код (я не работаю с компилятором, поэтому он может работать не так, как написано, но он должен быть закрыт):
type TMyObjectProc = procedure of object;
var pfMyProc : TMyObjectProc;
initialization
pfMyProc = nil;
procedure MyTimerCallback(hWnd : HWND; uMsg : DWORD; idEvent : PDWORD; dwTime : DWORD); stdcall;
begin
if Assigned(pfMyProc) then begin
pfMyProc(); // Calls DoIt, for the object that set the timer
pfMyProc = nil;
end;
end;
procedure TMyClass.MyOOCallback;
begin
// Handle your callback here
end;
procedure TMyClass.DoIt;
begin
pfMyProc = @MyOOCallback;
SetTimer(0, 0, 8, @ MyTimerCallback);
end;
Другим способом было бы воспользоваться тем, что у вашего таймера есть уникальный идентификатор. Сохраните отображение между идентификатором таймера и объектом. В обратном вызове преобразуйте идентификатор в указатель и вызовите метод объекта.
Редактировать: Я заметил комментарий к другому ответу, предлагающий использовать адрес вашего объекта в качестве идентификатора таймера. Это будет работать, но это потенциально опасный хак, если у вас два объекта с одним и тем же адресом в разное время и вы не вызываете KillTimer
. Я использовал этот метод, но лично мне он не нравится - я думаю, что лучше вести дополнительную бухгалтерию хранения карты (ID таймера, указателя объекта). Это действительно сводится к личному стилю.