Cawfee

Перехват функций

В теме 1 сообщение

1. Введение. Суть перехватов.

В данном уроке я постараюсь рассказать про то, что такое перехваты. Мы разберем некоторые примеры перехватов функций, покажем пример, где эти перехваты могут пригодиться.

 

Что такое перехваты? Зачастую возникает ситуация, когда нам необходимо как-то изменить действия, происходящие в функции. Но редактирование уже имеющейся функции (как правило, это стандартные функции) не есть лучший вариант. Почему? Если мы хотим внести какие-то дополнительные действия в нужную нам функцию, придется открывать файл, в котором содержится данная функция, ее редактировать. А если выйдет новая версия файла a_samp? Все наши действия придется повторить сначала, теперь уже в новом файле.

 

Для того собственно и придуман перехват. Так, мы перехватываем заранее объявленную функцию, вследствие чего меняется порядок вызова функций:

перехваченная_функция_1 -> перехваченная_функция_2 -> перехваченная функция_3 -> основная_функция_которую_перехватывали

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

 

Для перехвата функций нам потребуются некоторые директивы (#define, #if defined, #endif), а также сами функции, которые мы внедряем в перехваты.

 

2. Построение перехватов.

Давайте попробуем немного проанализировать перехват функций, не имея пока что представления о том, как их правильно перехватывать.

 

У нас есть функция A. Мы хотим, чтобы в ней происходил вывод текста "Hello world" в логи сервера. Как говорили ранее, внедрять этот текст в код функции A мы не будем (потому, что если выйдет обновление версии файла, в котором содержится эта функция, мы утратим свои корректировки в данной функции). Значит, нам нужно сделать так, чтобы это изменение могли внести без изменения кода файла.

 

Рассмотрим следующий вариант:

#define InterceptFunctionA()\
	printf("Hello world");A();

В данном варианте мы создали макрос. Так, при использовании "InterceptFunctionA()" сначала в логи сервера будет вноситься наш текст, а затем вызываться сама функция. Но в данном методе есть два нюанса: во-первых, у нас теперь новое название функции (а мы не хотим поправлять его во всем моде), во-вторых, данный метод нам не подойдет, если вместе с данной функцией мы хотим выполнить еще с десяток других действий.

 

Так я постарался дать понять Вам, что на самом деле перехваты не создают визуальных неудобств: функция будет называться так, как называлась, свой код в макросы вы не вписываете.

 

В общем и целом метод перехватов, которому мы сейчас обучаемся, выглядит следующим образом:

<перехватчик 1>
	<выполняем действия перехватчика 1>
  	<вызываем следующий перехватчик>
<конец перехватчика>

<перехватчик 2>
	<выполняем действия перехватчика 2>
	<вызываем следующий перехватчик>
<конец перехватчика>
  
<перехватчик 3>
	<выполняем действия перехватчика 3>
	<вызываем перехватываемую функцию>
<конец перехватчика>

Каждый перехватчик есть ни что иное как отдельная функция. И, соответственно, в ходе перехватов мы поочередно вызываем функции, в которых прописаны Ваши действия перехватчика.

 

Перехваты нативных функций и функций, вызывающихся автоматически, несколько отличаются. Поэтому урок мы немного разделим.

Спойлер

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

 


stock prefix_НазваниеПерехватываемойФункции(аргументы функции) // создаем функцию-перехватчик
{
	// наши действия
	return НазваниеПерехватываемойФункции(аргументы функции); // вызываем перехватываемую функцию
}

Обратите внимание на prefix. Это некоторый префикс, который мы задаем перехватываемой функции. Заметьте: для одной и той же функции не должны встречаться одинаковые префиксы. Сами префиксы должны зависеть от цели перехвата. Если это, например, античит, то будет вполне логично сделать префикс "ac" или "anticheat":


stock ac_НазваниеПерехватываемойФункции(аргументы функции)

Итак, мы создали перехватчик - функцию, которая будет вызываться до перехватываемой функции и выполнять наши действия. Но есть один нюанс: для ее вызова придется прописывать всегда "prefix_НазваниеПерехватываемойФункции(аргументы функции)". А как же вызвать этот перехватчик с таким названием,которое имеет сама перехватываемая функция? Объявляем ниже функции макрос для этой цели:


#define НазваниеПерехватываемойФункции prefix_НазваниеПерехватываемойФункции

Казалось бы все! Но нет... Ранее мы говорили о том, что функция может содержать множество перехватчиков. Но таким метод эта синхронность теряется. Поэтому нам необходимо добавить проверку: если существует еще один перехватчик, следует вызвать его, а если не существует - вызвать саму перехватываемую функцию.


#if defined _ALS_НазваниеПерехватываемойФункции
  	#undef НазваниеПерехватываемойФункции
#else
	#define _ALS_НазваниеПерехватываемойФункции
#endif

* не спрашивайте что такое ALS, можно ли его изменить и все в этом духе. Просто возьмите за правило, что только такой префикс и никакой иной.

 

И теперь нам остается лишь сдвинуть подмену названий в самый конец перехватчика. Итого наш вариант будет иметь следующий вид:


stock prefix_НазваниеПерехватываемойФункции(аргументы функции)
{
	// Ваш код
	return НазваниеПерехватываемойФункции(аргументы);
}
#if defined _ALS_НазваниеПерехватываемойФункции
	#undef НазваниеПерехватываемойФункции
#else
	#define _ALS_НазваниеПерехватываемойФункции
#endif
#define НазваниеПерехватываемойФункции prefix_НазваниеПерехватываемой функции

И теперь нам лишь остается такой код вставлять всегда туда, где Вы хотите произвести перехват функции. Ниже я предоставлю пример перехвата функции GivePlayerMoney.


stock ac_GivePlayerMoney(playerid, money)
{
	printf("[LOGS MONEY] playerid: %d, money: %d", playerid, money);
	return GivePlayerMoney(playerid, money);
}
#if defined _ALS_GivePlayerMoney
	#undef GivePlayerMoney
#else
	#define _ALS_GivePlayerMoney
#endif
#define GivePlayerMoney ac_GivePlayerMoney

 

 

Спойлер

Как было сказано ранее, перехват автовызываемых функций все-таки немного отличается. Прежде всего, теперь префикс появляется не в названии перехватчика, а в перехватываемой функции. Также, перед вызовом следующей перехватываемой функции, мы теперь должны проверить: а существует ли она?


public НазваниеПерехватываемойФункции(аргументы функции)
{
	// код перехватчика 
	#if defined prefix_НазваниеПерехватываемойФункции
		return prefix_НазваниеПерехватываемойФункции(аргументы функции);
	#endif
}

Изменение названия и выстраивание цепочки вызовов осуществляется также, как и с нативными функциями:


#if defined _ALS_НазваниеПерехватываемойФункции
	#undef НазваниеПерехватываемойФункции
#else
	#define _ALS_НазваниеПерехватываемойФункции
#endif
#define НазваниеПерехватываемойФункции prefix_НазваниеПерехватываемойФункции

Последнее нововведение заключается в том, что необходимо добавить создание декларации к функции (forward). Причем создавать нужно только тогда, когда существует следующий перехватчик. В конец этого перехватчика добавляем:


#if defined prefix_НазваниеПерехватываемойФункции
	forward prefix_НазваниеПерехватываемойФункции(аргументы функции);
#endif

Итого, наш код перехватчика представляется так:


public НазваниеПерехватываемойФункции(аргументы функции)
{
	// код перехватчика
	#if defined
		return prefix_НазваниеПерехватываемойФункции(аргументы функции);
	#endif
	return 1;
}
#if defined _ALS_НазваниеПерехватываемойФункции
	#undef НазваниеПерехватываемойФункции
#else
	#define _ALS_НазваниеПерехватываемойФункции
#endif
#define НазваниеПерехватываемойФункции prefix_НазваниеПерехватываемойФункции
#if defined prefix_НазваниеПерехватываемойФункции
	forward prefix_НазваниеПерехватываемойФункции(аргументы функции);
#endif

Для конкретики осуществим перехват OnPlayerConnect:


public OnPlayerConnect(playerid)
{
	printf("[CONNECT LOG] playerid: %d", playerid);
	#if defined log_OnPlayerConnect
		return log_OnPlayerConnect(playerid);
	#else
		return 1;
	#endif
}
#if defined _ALS_OnPlayerConnect
	#undef OnPlayerConnect
#else
	#define _ALS_OnPlayerConnect
#endif
#define OnPlayerConnect log_OnPlayerConnect
#if defined log_OnPlayerConnect
	forward log_OnPlayerConnect(playerid);
#endif

 

 

Как правило, перехваты применяются тогда, когда одну и ту же функцию необходимо использовать по-разному в разных файлах. С помощью перехватов мы можем в каждом файле перехватывать отдельные функции и вносить корректировки в их работу.

 

3. Перехваты функций: за и против.

В каждой ситуации есть сторонники какой-либо идеи и ее противники. Сейчас я постараюсь рассказать основные аспекты использования перехвата функций. А использовать их или нет - решать Вам.

 

1. Вы можете манипулировать одной и той же функцией по-разному в разных файлах. Это значит, что Вам не придется каждый раз добавлять различные проверки на то, подключен ли какой-либо файл, чтобы внести код "под него". Так, Вам достаточно лишь подключить нужный файл, чтобы вся цепочка вызовов работала как следует. Соответственно, при отключении файла проблем также не возникнет.

2. Перехват функций есть ни что иное как последовательные вызовы функций. Несомненно, на вызов функций уходит какая-то часть времени, но это время ничтожно для того, чтобы Ваш сервер действительно стал подтормаживать.

3. Если перехватываемую функцию использовать до того, как она перехвачена, функционировать она не будет (как Вы хотите). При этом про проблему в перехвате подумаете далеко не сразу. И главное - компилятор Вам не даст понять, что у Вас это не сработает, ведь со стороны его логики все сделано правильно. Поэтому перехватам вводят альтернативу: создают новую функцию с постфиксом "Ex", в которую уже помещают код перехвата и вызов самой функции:

stock ПерехватываемаяФункцияEx(аргументы функции)
{
	// ваш код
	return ПерехватываемаяФункция(аргументы функции);
}

Нюанс данной реализации в том, что теперь Вам придется использовать только ПерехватываемаяФункцияEx, о чем Вы можете забыть и вызвать не данную функцию, а ее оригинал.

 

4. Авторское право.

Автор данной статьи: @odosenok

Отдельное спасибо за разъяснения: Daniel_Cortez

Отредактировано пользователем odosenok
Причина: исправление опечатки.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для публикации сообщений создайте учётную запись или авторизуйтесь

Вы должны быть пользователем, чтобы оставить комментарий

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!


Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.


Войти

  • Последние посетители   0 пользователей онлайн

    Ни одного зарегистрированного пользователя не просматривает данную страницу

  • Похожий контент

    • Sleash
      От Sleash
      Всем доброго времени суток. Сегодня мне бы хотелось вам рассказать от таких функциях в PAWN-языке, как SetTimer и SetTimerEx.
      Сразу сделаю такое примечание: Для SetTimer и SetTimerEx можно использовать ТОЛЬКО public!!!
      SetTimer
      Итак, для начала приступим к лёгкому и пойдём по нарастающей:
      Первое, это SetTimer. Функция включает в себя 3 параметра:
      native SetTimer(funcname[], interval, repeating); funcname[] - Это название функции, по сути - название вашего pablic в кавычках.
      interval - Интервалы от вызова таймера до срабатывания pablic
                     Так же если стоит повторение таймера, то будет работать как интервал между таймерами.
                     Измеряется в миллисекундах. 1000 миллисекунд равно 1 секунде
      repating - Тут всё просто: будет ли повторяться ваша функция. Может иметь значения: true - будет выполняться повторение ИЛИ false - вызывается 1 раз.
      Давайте разберём пример для наглядности:
      forward Info(); public Info() {     SendClientMessageToAll(0xFFFFFFFF, "Наш сайт: pawno-rus.ru"); } // И теперь давайте вызовем данный таймер при запуске мода public OngameModeInit() {     SetTimer("Info", 1000*60*20, true);     return true } Давайте разберёмся по интервалу: 1000*60*20. Так как я писал ранее, что 1000 млСек = 1 сек, то: 1 сек * 60 = 1 минута; 1 минута * 20 = 20 минут.
      Таким образом данный таймер будет выводить каждые 20 минут для всех игроков на сервере в чат: "pawno-rus.ru".
       
      SetTimerEx
       
      По сути с SetTimer - всё, теперь перейдём к следующей функции: SetTimerEx:
      native SetTimerEx(funcname[], interval, repeating, const format[], {Float,_}:...); Тут добавляются ещё 2 параметра:
      const format[] - формат данных для ввода в функцию. (Такие как: s, d, f);
      {Float,_}:... - переменные или значение для данных из const fromat[]
      Из данных объяснения мало что можно понять незнающему или начинающему скриптеру, поэтому давайте разберём на примере кода, который при входу даёт на авторизацию две минуты игроку:
      forward autorez(playerid); public autorez(playerid) {     if(/*проверка на то залогинился ли игрок*/)     {         SendClientMessage(playerid, 0xFFFFFFFF, "Вы были кикнуты по причине: Время на авторизацию истекло.");         Kick(playerid);         return true;     }     return true; } //Дальше уже добавлям сам таймер public OnPlayerConnect(playerid) {     SetTimerEx("autorez", 1000*60*2, false, "d", playerid);     return true; } Если первые три пункта мы уже разбирали. то начнём сразу с 4-го:
      4. Тут в кавычках надо вставлять тип данных. Наподобие функции format(output[], len, "%s %d %f", string, int, float), только без знака процента (%)
      5. Тут уже указывают сами данные, так же разберём как и предыдущий: format(output[], len, "%s %d %f ", string, int, float)
       
      Я надеюсь что кому-то помог, а если что непонятно, то спрашивайте, всегда помогу.
    • NoVate
      От NoVate
      Доброго времени суток 
       
      Многие новички задавались вопросом: "Как создать свой RP сервер?", "С чего начать разработку?", "Это сложно и я не справлюсь".
      После таких вопросов многие уходили с этой темы и оставляли её на последней полке. Некоторые брались и сталкивались с большими проблемами. И только часть из этих людей доходили до какого-то результата.
       
      Моя цель: сделать создание сервера в SA:MP проще. Чтобы каждый из тех, кто интересовался созданием сервера мог спокойно взять готовую, а что самое главное - базовую сборку и под неё уже создавать различные свои задумки. SA:MP на данный момент не особо актуален, а тем более самый обычный клиент, НО, как мне кажется, если сделать что-то невероятное, то аудитория заметит это и ей будет достаточно интересно "пощупать" данное новшество.
       
      Для тех, кто начнёт писать: "Ты видел свой код?", "Ты сделал не правильно в 777-ой строке" и так далее. Сразу отвечу, цитируя:
       
      Кому стало интересно и кто хотел бы ознакомиться со сборком - прошу в мой Github (не реклама).
      Буду делать коммиты по мере свободного времени и написании кода.
      Если есть какие-то идеи, то пишите в мой Discord сервера (не реклама), который можно будет найти на той же странице.
       
      Спасибо всем за прочтение и хорошего настроения 
    • DEST
      От DEST
      ДИНАМИЧЕСКИЕ ЗОНЫ (streamer плагин)
       
      ОБЩЕЕ ОПИСАНИЕ: 
      Как я заметил, многие интересуются, как создать действие для игрока в какой-либо определенной точке карты. Например, как выдать игроку бутылку пива автоматически при заходе в клуб? Конечно, можно это сделать, создав секундный таймер и выполнять действия там, но как по мне, динамические зоны справятся  с этим лучше. 
      ТРЕБОВАНИЯ: 
      Для работы необходимо: 
      1) Плагин streamer. 
      2) Инклуд streamer. 
      Желательно иметь актуальную версию плагина и инклуда, хотя это не обязательно. 
      ТЕХНИЧЕСКОЕ ОПИСАНИЕ РАБОТЫ: 
      Принцип работы данных зон прост: когда игрок заходит в зону, которая создается кстати, в виде геометрических фигур (об этом позже) для него срабатывает коллбэк - OnPlayerEnterDynamicArea. После того, как игрок покидает динамическую зону для него срабатывает другой коллбэк - OnPlayerLeaveDynamicArea. В оба эти коллбэка поступают одинаковые аргументы - playerid, areaid. Где playerid - ид игрока, который вошел / покинул зону, а areaid - ид самой зоны. 
      ТИПЫ СОЗДАВАЕМЫХ ЗОН: 
      1) Круг. Для создания данной зоны потребуются X и Y координата центра круга, а также радиус создаваемой зоны. 
      2) Прямоугольник. Для создания данной зоны, необходимо иметь координаты противоположных углов, образующих прямоугольник. 
      3) Сфера. Для данной зоны требуются X, Y, Z координаты центра сферы и ее размер (радиус). 
      4) Куб. Точно также как и прямоугольник, только потребуются еще и Z координаты противоположных углов. Рисунок не требуется. 
      Пример использования:
      new zone_army; public OnGameModeInit(playerid) { zone_army = zone51 = CreateDynamicCube(-13.0555,1702.1824, 15.0,405.9110, 2071.6646, 850.0); return 1; } public OnPlayerEnterDynamicArea(playerid, areaid) { if(areaid == zone_army) SendClientMessage(playerid,-1,"Покиньте охряняемую зону. Иначе будет открыт огонь."); return 1; } public OnPlayerLeaveDynamicArea(playerid, areaid) { if(areaid == zone_army) SendClientMessage(playerid,-1,"Благодарим за понимание."); return 1; } Пример взят с сайта forum-pawno.ru. 
      СВЯЗАННЫЕ ФУНКЦИИ: 
      DestroyDynamicArea(areaid); - уничтожение динамической зоны.  IsValidDynamicArea(areaid); - проверка за существование динамической зоны с данным ID.  TogglePlayerDynamicArea(playerid, areaid, toggle); - скрытие динамической зоны для игрока.  TogglePlayerAllDynamicAreas(playerid, toggle); - скрытие всех динамических зон для игрока.  IsPlayerInDynamicArea(playerid, areaid); - проверка на нахождение игрока в динамической зоне с определенным ID.  AttachDynamicAreaToPlayer(areaid, playerid); - прикрепление динамической зоны с определенным ID к игроку.  AttachDynamicAreaToVehicle(areaid, vehicleid); - прикрепление динамической зоны с определенным ID к машине.  DestroyAllDynamicAreas(); - уничтожение всех динамических зон.  CountDynamicAreas(); - подсчет количества динамических зон.  Внимание! К игрокам и машинам можно прикрепить только зоны с типом круг и сфера. 
      На этом все! 
      Благодарности: 
      Благодарность выражается следующим веб-ресурсам: 
      forum-pawno.ru
      forum.sa-mp.com
      Автор: @DEST.
       
    • MartinJoys
      От MartinJoys
      Всем привет на связи MartinJoys | Cherlock
      Cегодня мы научимся делать вип аккаунты по времени, с автоматическим удалением по истечению времени.
      Перейдем в самый вверх вашего мода и впишем туда этот код:
       
      Теперь необходимо установить подключение к базе при включении мода, перейдем в OnGameModeInit и впишем туда этот код:
       
      mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_DB, MYSQL_PASS); //устанавливаем подключение switch(mysql_ping()) {     case 1: print("Соединение установлено"); // Если подключена БД.     case -1: print("Нет соединения с БД"); // Если не подключена БД. }    
      Теперь если все будет нормально то в консоли появится инфа о том что соединение установлено.
      Сейчас сделаем отключение от базы при выключении/перезагрузке мода, перейдем в OnGameModeExit и впишем:
      mysql_close(); Теперь нужно сделать проверку, переходим в OnPlayerConnect и вписываем:
       
       
      Теперь построим команду для выдачи випки. Внимание команда построена на YCM
       
      Теперь сделаем функцию выдачи випки, перейдем в низ вашего мода и напишем следующий код:  
      Теперь нужно создать таблицу в нашей бд:
      Создаем таблицу с именем Donate, делаем 2 столбца Name и Days.
      В первом укажем значение string(24), а во втором int(11).
       
      Необходимые для работы инклуды а так же плагины можете скачать по этой ссылкам:
      http://rghost.ru/40382201
      http://rghost.ru/40382221
      http://rghost.ru/40382234
      http://rghost.ru/40382569

      Автор: Я MartinJoys | Cherlock
       
    • xiemoniyaz
      От xiemoniyaz
      C:\Users\111\OneDrive\Ðàáî÷èé ñòîë\rrrr\gamemodes\arizona.pwn(78772) : error 017: undefined symbol "GEO_MAX_COUNTRY_NAME_LENGTH" C:\Users\111\OneDrive\Ðàáî÷èé ñòîë\rrrr\gamemodes\arizona.pwn(78772) : error 009: invalid array size (negative, zero or out of bounds) C:\Users\111\OneDrive\Ðàáî÷èé ñòîë\rrrr\gamemodes\arizona.pwn(78772) : error 036: empty statement C:\Users\111\OneDrive\Ðàáî÷èé ñòîë\rrrr\gamemodes\arizona.pwn(78772) : fatal error 107: too many error messages on one line Compilation aborted. Pawn compiler 3.2.3664          Copyright (c) 1997-2016, ITB CompuPhase 4 Errors.  
       
      cmd:get(playerid, params[]) {     extract params -> new player; else return SendClientMessage(playerid, -1, !"/get [playerid]");     new string[GEO_MAX_COUNTRY_NAME_LENGTH+GEO_MAX_REGION_NAME_LENGTH+GEO_MAX_CITY_NAME_LENGTH+GEO_MAX_PROVIDER_NAME_LENGTH+GEO_MAX_MOBILE_STATUS_LENGTH+GEO_MAX_PROXY_LENGTH-6+118];     format(string, sizeof(string),     "Страна: %s\n\     Регион: %s\n\     Город: %s\n\     Провайдер: %s\n\     Использует ли моб.сеть: %s\n\     Использует ли прокси: %s",     GetPlayerIpCountry(player),     GetPlayerIpRegion(player),     GetPlayerIpCity(player),     GetPlayerIpProvider(player),     GetPlayerIpMobileStatus(player),     GetPlayerIpProxyStatus(player));     ShowPlayerDialog(playerid, 0, DIALOG_STYLE_MSGBOX, !" ", string, !"Закрыть", !"");     return true; }