Перехват функций, часть 1: основы
Оглавление:
Внимание! Эта тема закрыта от комментариев. Если у вас есть вопросы по поводу перехватов - внимательно прочтите вторую часть урока, и если что-то останется для вас непонятным, задавайте вопросы в той теме.
Всем привет.
Сегодня я расскажу вам о перехвате функций в Pawn.
Для начала разберёмся, что такое перехваты и с чем их едят.
Перехваты в SA:MP обычно используется для того, чтобы изменить поведение каких-либо функций.
Классическим примером может послужить написание античита на HP.
Предположим, у нас есть глобальный массив, в котором мы будем записывать HP игроков после его выдачи с помощью SetPlayerHealth.
Без перехватов придётся выискивать в моде все вызовы SetPlayerHealth и под каждым из них вручную записывать выдаваемое количество HP в ячейку массива.
В то же время, используя технику перехвата, достаточно всего лишь написать 1 перехватчик, в котором происходит та же запись в массив, и поместить его в самое начало мода, перед подключением a_samp.inc. В дальнейшем перехваты вместе с остальными переменными и функциями античита можно вынести в отдельный инклуд (например, "ac_hp.inc"), который при необходимости можно будет отключить, всего лишь закомментировав строку #include "ac_hp.inc".
Таким образом, техника перехватов даёт возможность "прицепить" свой код к стандартным функциям и коллбэкам без вмешательства в сам мод, в разы упростив дальнейшую разработку.
В этом уроке будет описан только один метод, который был открыт благодаря ipsBruno и Y_Less.
Вся суть данного метода состоит в том, что макропроцессор обрабатывает выражения "#if defined X" в 2 прохода, поэтому если "X" - название ещё не известной компилятору функции/переменной/константы, во время 1-го прохода макропроцессор пропустит такой макрос и сначала соберёт информацию о функциях, переменных и константах в скрипте, завершив 1-й проход.
После этого на 2-м проходе компилятор уже будет знать обо всех переменных, функциях и константах, расположенных не только перед строкой с "#if defined", но и после неё. В результате перехватчик подменит собой перехватываемую функцию, а затем вызовет её, если она существует. Если же перехватываемой функции не существует, будет вызван только перехватчик.
Преимущества такого способа перед другими методами:- Не требуется вызывать CallLocalFunction, благодаря чему не происходит увеличения нагрузки из-за использования нативных функций.
- Не используется модификация кода во время его выполнения (из-за таких модификаций могла возникнуть несовместимость кода с JIT).
Рассматриваемый нами метод перехвата работает примерно так:
Код:
<перехватчик>
<код перехвата>
<вызов перехватываемой функции>
<конец перехватчика>
<функция уже была перехвачена?>
<удалить старый перехват>
<иначе>
<дать знать, что теперь функция перехвачена>
<конец ветвления>
<вставить перехватчик в цепочку вызовов>
<предварительно объявить функцию>
В результате при умелом использовании одна или несколько функций-перехватчиков могут быть выстроены в цепочку: сначала вызывается первый объявленный перехватчик, а за ним и все остальные, пока очередь не дойдёт до оригинальной перехватываемой функции:
Код:
<перехватчик1>
<выполнить набор действий перехватчика>
<вызвать перехватчик2>
<конец перехватчика>
<перехватчик2>
<выполнить набор действий перехватчика>
<вызвать перехватчик3>
<конец перехватчика>
<...>
<перехватчикN>
<выполнить набор действий перехватчика>
<вызвать перехватываемую функцию>
<конец перехватчика>
<перехватываемая функция>
<...>
<конец функции>
В Pawn существуют 2 способа реализации этого метода: один для перехвата коллбэков (функций, автоматически вызываемых сервером; пример: OnPlayerConnect) и один для нативных функций (функций, реализованных в самом сервере; пример: printf, SendClientMessage).
Перехват коллбэков реализуется следующим образом:
Открыть/закрыть// CallbackFunc - название перехватываемой функции
// param1, param2 - параметры перехватываемой функции
public CallbackFunc(param1, param2)
{
// здесь место для Вашего кода, ради которого и осуществляется перехват
// ...
// если перехватываемая функция существует - нужно вызвать и её,
// после чего вернуть значение, которая вернёт перехватываемая функция
// (LibName - название Вашей библиотеки (в случае с Pawn - инклуда),
// в котором перехватывается функция CallbackFunc)
#if defined LibName__CallbackFunc
return Libname__CallbackFunc(param1, param2);
#endif
}
// если объявлен макрос с префиксом "_ALS_" и именем функции после него -
// это значит, что такая функция уже была где-то перехвачена
// если функция CallbackFunc уже была перехвачена - уберём макрос, сделанный
// в предыдущем перехвате (назначение этого макроса будет объяснено далее)
#if defined _ALS_CallbackFunc
#undef CallbackFunc
// если же функция ещё не была перехвачена,
// дадим знать о её перехвате, объявив макрос "_ALS_CallbackFunc"
#else
#define _ALS_CallbackFunc
#endif
// сделаем макрос "CallbackFunc", чтобы заменить название перехватываемой
// функции CallbackFunc на Libname__CallbackFunc во время её объявления в основном скрипте
#define CallbackFunc LibName__CallbackFunc
// отныне перехватываемая функция будет называться не "CallbackFunc", а "LibName__CallbackFunc",
// а название "CallbackFunc" достанется функции-перехватчику;
// остаётся лишь сделать для перехваченной функции опережающее объявление
// с её новым названием, чтобы избежать лишних предупреждений от компилятора
#if defined LibName__CallbackFunc
forward LibName__CallbackFunc(param1, param2);
#endif
Перехват нативных функций производится похожим образом, но, в отличие от первого способа, перехватываемая функция не переименовывается, поэтому не требуется делать для неё новое предварительное объявление (forward).
Открыть/закрыть// функция-перехватчик
stock LibName__NativeFunc(param1, param2)
{
// место для кода перехвата
// ...
// вызов перехватываемой функции, на этот раз без проверки существования функции
return NativeFunc(param1, param2);
}
#if defined _ALS_NativeFunc
#undef NativeFunc
#else
#define _ALS_NativeFunc
#endif
#define NativeFunc LibName__NativeFunc
Теперь разберёмся, как всё это выглядит в деле.
Методы для коллбэков и нативных функций разные, поэтому рассмотрены они будут отдельно.
- Перехват коллбэков на примере OnDialogResponse:
// массив, в котором записаны ID диалогов ,показанных другим игрокам
static player_dialog_ids[MAX_PLAYERS] = {-1, ...};
// перехватчик
{
// если игрок посылает сигнал об ответе на диалог,
// который ему не показывали - это признак использования читов
// (и да, я знаю, что сейчас этой проблемы в SA:MP нет, это всего лишь пример!)
if(player_dialog_ids[playerid] != dialogid)
Kiсk(playerid);
player_dialog_ids[playerid] = -1;
#if defined spd__OnDialogResponse
return spd__OnDialogResponse(playerid, dialogid, response, listitem, inputtext);
#endif
}
#if defined _ALS_OnDialogResponse
#undef OnDialogResponse
#else
#define _ALS_OnDialogResponse
#endif
#define OnDialogResponse spd__OnDialogResponse
#if defined spd__OnDialogResponse
forward spd__OnDialogResponse(playerid, dialogid, response, listitem, inputtext[]);
#endif
- Перехват нативной функции на примере ShowPlayerDialog:
static player_dialog_ids[MAX_PLAYERS] = {-1, ...};
// перехватчик
stock spd__ShowPlayerDialog(playerid, dialogid, type, header[], text[], button1[], button2[])
{
player_dialog_ids[playerid] = dialogid;
return ShowPlayerDialog(playerid
, dialogid
, type
, header
, text
, button1
, button2
); }
#if defined _ALS_ShowPlayerDialog
#undef ShowPlayerDialog
#else
#define _ALS_ShowPlayerDialog
#endif
#define ShowPlayerDialog spd__ShowPlayerDialog
На сегодня всё, удачного скриптинга!
Для тех, кто любит стрелять себе в ногу:
Открыть/закрыть
- Если вы только начинаете практиковаться с перехватами, сверяйте свой код с примерами из этого урока.
- Не выдумывайте своих префиксов вместо "_ALS_".
Директива "#if defined _ALS_<название функции>" используется специально, чтобы ваш перехват правильно работал с другими перехватами той же функции.
Если вместо "_ALS_" использовать другой префикс, потеряется совместимость с другими перехватчиками.
Пример того, как не нужно делать:
Код:
public OnGameModeInit()
{
// ...
#if defined my_lib_OnGameModeInit
my_lib_OnGameModeInit();
#endif
}
#if defined _my_prefix_OnGameModeInit
#undef OnGameModeInit
#else
#define _my_prefix_OnGameModeInit
#endif
#if defined my_lib_OnGameModeInit
forward my_lib_OnGameModeInit();
#endif
#define OnGameModeInit my_lib_OnGameModeInit
Правильный пример:
Код:
public OnGameModeInit()
{
// ...
#if defined my_lib_OnGameModeInit
my_lib_OnGameModeInit();
#endif
}
#if defined _ALS_OnGameModeInit
#undef OnGameModeInit
#else
#define _ALS_OnGameModeInit
#endif
#if defined my_lib_OnGameModeInit
forward my_lib_OnGameModeInit();
#endif
#define OnGameModeInit my_lib_OnGameModeInit
- Как-то давно один "профессионал" спросил меня, мол зачем вся эта чепуха с перехватами, когда всё можно уместить в один #define?
stock my_AddStaticVehicle(modelid, Float:spawn_x, Float:spawn_y, Float:spawn_z, Float:angle, color1, color2)
{
print('Функция AddStaticVehicle перехвачена'); return AddStaticVehicle(modelid
, spawn_x
, spawn_y
, spawn_z
, angle
, color1
, color2
); }
#define AddStaticVehicle my_AddStaticVehicle
Так вот, этот метод в корне неправильный.
Почему? Очень просто, им нельзя навесить больше одного перехвата на одну и ту же функцию.
Не верите? Тогда попробуйте скомпилировать такой код:
// 2-й перехватчик для AddStaticVehicle
stock my2_AddStaticVehicle(modelid, Float:spawn_x, Float:spawn_y, Float:spawn_z, Float:angle, color1, color2)
{
print('Функция AddStaticVehicle перехвачена ещё раз'); return AddStaticVehicle(modelid
, spawn_x
, spawn_y
, spawn_z
, angle
, color1
, color2
); }
#define AddStaticVehicle my2_AddStaticVehicle
// 1-й перехватчик для AddStaticVehicle
// Здесь будет "warning 201: redefinition of constant/macro (symbol "AddStaticVehicle")".
stock my1_AddStaticVehicle(modelid, Float:spawn_x, Float:spawn_y, Float:spawn_z, Float:angle, color1, color2)
{
print('Функция AddStaticVehicle перехвачена'); return AddStaticVehicle(modelid
, spawn_x
, spawn_y
, spawn_z
, angle
, color1
, color2
); }
#define AddStaticVehicle my1_AddStaticVehicle
Получилось скомпилировать код без варнингов и ошибок?
Такой способ подойдёт только если вы используете его в своём моде и только если вы перехватываете функцию один раз (но даже в этом случае не рекомендуется, т.к. вам может понадобиться перехватить функцию более 1 раза).
Но если вы используете этот ленивый способ в выкладываемых на Pro-Pawn работах (да, я смотрю на вас, авторы уроков и мануалов), даже не надейтесь, что ваша работа будет одобрена.
Вы явно ошиблись порталом, если думаете, что можно одобрить полурабочий говнокод, который может нарушить совместимость с другими работами.
Цитата:
Но ведь и так сойдёт! А кому не нравится, пусть сами переделывают!!
Отправляйтесь обратно н***й на govno-info и постите говнокод там.
P.S.: Скоро будет готова 2-я часть урока, в которой будет рассматриваться использование перехватов на практике.
Часть 2.
Автор: Daniel_Cortez
Копирование данной статьи на других ресурсах без разрешения автора запрещено!