вторник, 9 декабря 2008 г.

Почему методы класса должны быть помечены словом "static", чтобы их можно было использовать в качестве функции обратного вызова?

Это перевод Why do member functions need to be "static" to be used as a callback? Автор: Реймонд Чен.

Как мы узнали вчера, обычные методы класса принимают секретный параметр "this", что, конечно же, делает их несовместимыми с сигнатурами функций обратного вызова в Win32.

К счастью, большинство callback-функций предоставляют какой-нибудь способ для указания контекста. Вы можете записать параметр "this" в этот контекст, так что вы сможете потом восстановить исходный объект из своего callback-а. Например как-то так:
class SomeClass {
  ...
  static DWORD CALLBACK s_ThreadProc(LPVOID lpParameter)
  {
    return ((SomeClass*)lpParameter)->ThreadProc();
  }
  DWORD ThreadProc()
  {
    ... основная работа ...
  }
};
Некоторые сигнатуры функций обратного вызова размещают параметр контекста (также известный как "ссылочные данные") первым параметром. Как удобно, потому что секретный параметр "this" также идёт первым параметром. Смотря на доступные нам модели вызова, мы видим, что модель вызова __stdcall в применении к методу класса вполне нам подходит. Давайте возьмём для примера WAITORTIMERCALLBACK - функция обратного вызова от, например, RegisterWaitForSingleObject:

Функция __stdcallМетод __stdcallМетод thiscall
.. данные стека .... данные стека .... данные стека ..
TimerOrWaitFiredTimerOrWaitFiredTimerOrWaitFired <- ESP
lpParameter<- ESPthis<- ESP

Ну, модель "thiscall" не совпадает с callback-ом (левый столбец), но модель с "__stdcall" - да. К счастью, компилятор может быть достаточно умным, чтобы опознать это и оптимизировать статический метод s_ThreadProc так, что он будет делать только переход:
class SomeClass {
  ...
  static DWORD CALLBACK s_ThreadProc(LPVOID lpParameter)
  {
    return ((SomeClass*)lpParameter)->ThreadProc();
  }
  DWORD __stdcall ThreadProc()
  {
    ... основная работа ...
  }
};
Если вы посмотрите на сгенерированный для s_ThreadProc код, то увидите, что он состоит из одной лишь инструкции jmp, потому что компилятор сумел понять, что две модели вызова совместимы, поэтому, фактически, никакого преобразования здесь делать не нужно:
?s_ThreadProc@SomeClass@@SGKPAX@Z PROC NEAR
 jmp     ?ThreadProc@SomeClass@@QAGKXZ
?s_ThreadProc@SomeClass@@SGKPAX@Z ENDP
Теперь некоторые люди могут сделать ещё один шаг и просто привести метод к нужному типу и полностью избавиться от служебного метода s_ThreadProc. Я настоятельно не рекомендую так делать. Я слишком часто видел, как в таких случаях возникают проблемы из-за ошибки в преобразованиях; подробнее об этом в следующий раз.

Хотя в коде выше мы воспользовались совпадением двух моделей с __stdcall, мы всё же не полагаемся на это соответствие. Если вдруг эти две модели перестанут совпадать - наш код всё ещё будет верным. Это важно, если вам нужно будет портировать свой код на машину с другой архитектурой, где бинарное совпадение соглашений вызова уже может не выполняться!

Примечание переводчика: аналогичные примеры для Delphi.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.