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

Cawfee
Великий Гуру

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 пользователей онлайн

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

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

    • Lemonoas
      От Lemonoas
      Хочу сделать свою работу по моду чтобы работа работала, хочу сделать своими руками
    • Dekmveka
      От Dekmveka
      Здравствуйте, уважаемые пользователи форума PAWNO-RUS.
       
      В этой теме хочу поделиться видеоуроками, ориентированных на новичков, которые сейчас начинают только изучать pawn. 
      Хочу сказать сразу, я не профессиональный блогер с крутым оборудованием, поэтому если что за какие то погрешности в съемке заранее извиняюсь.
      Хотелось бы попросить не писать, что типа pawn и samp уже умер, неактуален, уроков и так полно и так далее и тому подобное. Я лишь просто занимаюсь своим увлечением и решил делиться знаниями с новичками.
      Далее на канале после плейлиста с основами планируется выложить плейлист по MySQL и максимально подробно всё рассказать о MySQL, будет старая добрая рубрика "Мод с нуля" (но на основе плагинов, стандартов и др. актуальных в 2025-2026 году). 
       
      Также хочу сказать, что есть также телеграмм канал. 
      На данный момент там есть ветка для общения, ветка "Инструменты" (там лежат архивы со всем нужным + есть навигационный пост, я просто поделил всё на категории и просто вставил ссылки на архивы которые туда же выложил. Можете зайти посмотреть если интересно). Также в телеграмм канале потом будут исходные файлы с плейлиста "Мод с нуля" и других плейлистах, пока что говорить подробно не буду что буду ещё выкладывать. 
       
      В общем, я даю ссылки, кому интересно, можете смотреть.
      [Часть 1]: Архитектура ядра SAMP || Основы программирования PAWN/PAWNO  -  *кликабельно*
      [Часть 2]: База мода, переменные, format() || Основы программирования PAWN/PAWNO  -  *кликабельно*
      Telegram канал  -  *кликабельно*
       
      Желаю всем приятного просмотра, а также успехов в обучении 
    • Antoxa39
      От Antoxa39
      Здарова бандиты. 
      Вообщем, в этом уроке я расскажу и покажу как подключить Базу Данных к MySQL.
      Не регистрацию,и не в готовом моде где просто пользователя пароль хост и т.д меняете.
      А подключение с нуля.
       
      1.Нам понадобится MySQL.  Я использую R39-6. Скачать 
      2.Приступаем к подготовительному этапу.
      Заходим в Pawn и подключаем MySQL чуть ниже инклюда a_samp. #include <a_mysql>
      Должно быть так.
      После этого отчищаем мод от хлама. Должно получится вот так: жмЫкс
      3.Подключение
      Дальше нам требуется создать переменную,эта переменная будет хранить ИД подключения. в MySQL R39 создаем простую переменную.
      new dbHandle;  В MySQL R41 создаем так:
       new MySQL:dbHandle;  
      теперь создаем Константы
      #define Host "127.0.0.1" #define User "root" // это если вы используете Denwer. Если хостинг указываете другого пользователя. #define DataBase "urok" #define Password_SQL ""//Если вы на хостинге ставите пароль,для Denwer пароль не требуется, чуть позже скажу как его установить Все, теперь переходим в public OnGameModeInit()
      public OnGameModeInit() {     dbHandle = mysql_connect(Host,User,DataBase,Password_SQL); // приравниваем нашу переменную с ИД подключением к коннекту к базе данных. Извиняюсь если коряво обьяснил. Это для R39     return 1; } Для R41
      public OnGameModeInit() {     dbHandle = mysql_connect(Host,User,Password_SQL,DataBase);// В R41 поменялись местами Пароль и База     return 1; } Если сделаем чтобы при подключении к базе выводилось сообщение о том,подключилось или нет. Я сделаю это без switch. Чтобы сильно не загружать смотрящих этот урок.
      Делаем все там же в public OnGameModeInit()
      public OnGameModeInit() {     dbHandle = mysql_connect(Host,User,DataBase,Password_SQL);     if(mysql_errno())     {         printf("Подключение к базе данных %s не удалось.",DataBase); // %s означает строку. То есть будет выводить название базы данных которое указано в #define DataBase     }     else     {         printf("Подключение к базе данных %s успешно",DataBase);     }     return 1; } Все, с этим закончили. Остался последний этап, это отключение базы данных когда мод выключается.
      Идем в паблик OnGameModeExit()
      public OnGameModeExit() {     mysql_close(dbHandle);     return 1; } Все. База данных подключена.
      Чтобы скачать Denwer жмите сюды: плямп
      Для установки можете перейти по этой ссылке: УСТАНОВКА
    • sinvays
      От sinvays
      Продаётся проект Criminal RolePlay с 8-летней историей.

      Проект заморожен и не используется длительное время.
      Потратил на него около 250.000 рублей, продаю за приемлимую цену.

      В комплекте:
      Полноценный игровой мод Мобильный лаунчер с Figma-дизайном Кэш клиентской части Все версии оформления проекта Все группы проекта Criminal RolePlay Шаблоны всех версий сайта, включая ранее не выпущенный шаблон Модуль автодоната YooMoney Помощь с установкой игрового мода на хостинг Цена:
      49 900₽ — за весь комплект.
      Возможен торг при адекватных предложениях.

      Важное:
      Проект продаётся в одни руки. Если в течение месяца не найдётся покупатель, будет рассмотрена продажа по частям.
      Связаться: https://vk.com/rosetta