PDA

Просмотр полной версии : [C] Лайфхак с функциями для Pawn AMX



Daniel_Cortez
13.10.2016, 22:19
Привет всем.

Не так давно в одном из своих личных проектов мне понадобилось сделать нативные функции для Pawn AMX.
Для примера возьмём одну из таких функций: она должна была принимать 2 аргумента (ID участка памяти, в котором нужно отсортировать значения, как в массиве, и порядок сортировки) и нужно было сделать проверку на то, что этих аргументов передано нужное количество (кто знает, может быть в скрипте неправильный заголовок функции с 1 или даже 0 параметрами вместо 2?)
Выглядел код примерно так:


static cell AMX_NATIVE_CALL n_MemSort(AMX *amx, const cell *params)
{
// CheckArgs проверяет кол-во аргументов и, если их меньше, чем нужно, поднимает ошибку.
if (!CheckArgs(2))
return 0;
// Если есть нужное кол-во аргументов - отсортировать значения в участке памяти.
return MemoryPool_Sort(amx, params[1], params[2]);
}

Меня этот результат не устроил сразу по нескольким причинам.
Во-первых, чтобы понять, что кроется в 1-м и 2-м аргументе, нужно открывать заголовочный файл на Pawn (*.inc).
Во-вторых, чем больше таких аргументов в функции, тем легче их перепутать.
В-третьих, последний параметр в CheckArgsNumber нужно подсчитывать вручную. ИЧСХ, с ним тоже легко ошибиться (например, указать 3 вместо 2), особенно из-за copy-paste.

Мне захотелось переложить работу по контролю аргументов функции на компилятор, поэтому я придумал небольшой трюк с перечислением ID аргументов в enum:


static cell AMX_NATIVE_CALL n_MemSort(AMX *amx, const cell *params)
{
enum
{
args_size,
arg_pointer_id,
arg_sort_type,
__dummy_elem_, num_args_expected = __dummy_elem_ - 1
};
if (!CheckArgs())
return 0;
return MemoryPool_Sort(amx, params[arg_pointer_id], params[arg_sort_type]);
}

С таким вариантом у каждого из параметров есть своё название, перепутать params[arg_pointer_id] и params[arg_sort_type] куда сложнее, чем params[1] и params[2], а в CheckArgsNumber не нужно самостоятельно считать количество параметров функции - оно само подсчитывается в args_expected_number.

P.S. Если кому-нибудь будет интересно, вот сама функция CheckArgs, вернее, макрос:


#define CheckArgs() pluginutils::CheckNumberOfArguments(amx, params, num_args_expected)

В нём, в свою очередь, используется функция pluginutils::CheckNumberOfArguments, которую можно найти в файлах pluginutils.h/.cpp из моего шаблона для плагинов:
https://github.com/Daniel-Cortez/samp-plugin-template


Автор: Daniel_Cortez (http://pro-pawn.ru/member.php?100-Daniel_Cortez)


Специально для Pro-Pawn.ru (http://www.pro-pawn.ru)
Копирование данной статьи на других ресурсах без разрешения автора запрещено!

ziggi
13.10.2016, 22:54
Годно, надо попробовать. Для полноты картины не помешало бы выложить функцию CheckArgsNumber (понятно, что функция проста, но всё же).

Daniel_Cortez
23.10.2016, 12:08
Не то, чтобы много откликов, но и отрицательных отзывов тоже нет, поэтому, полагаю, можно переместить это в подраздел "Разработки".
Заодно добавил функцию CheckArgsNumber, она очень простая на самом деле.

VVWVV
23.10.2016, 15:08
Отличный трюк. Самое главное, что мне понравилось - читаемость кода. Теперь не придётся писать какие-то занудные комментарии.

ziggi
19.11.2016, 11:24
Есть ли возможность узнать имя функции в CheckArgsNumber как-то через AMX?
Я пока придумал только это:

inline bool CheckArgsNumber(AMX *amx, const char *func, const cell *params, cell num_expected)
{
if (params[0] / (cell)sizeof(cell) < num_expected) {
logprintf(" * " PLUGIN_NAME ": Incorrect parameter count for \"%s\", %d != %d\n", func, params[0] / 4, num_expected);
amx_RaiseError(amx, AMX_ERR_PARAMS);
return false;
}
return true;
}


if (!CheckArgsNumber(amx, __func__, params, args_expected_number)) {
return 0;
}

UPD.
Хотя лучше, наверное, применять конструкцию вида:

inline bool CheckArgsNumber(AMX *amx, const cell *params, cell num_expected)
{
if (params[0] / (cell)sizeof(cell) < num_expected) {
amx_RaiseError(amx, AMX_ERR_PARAMS);
return false;
}
return true;
}
#define PRINT_PARAM_ERROR() \
logprintf(" * " PLUGIN_NAME ": Incorrect parameter count for \"%s\", %d != %d\n", __func__, params[args_size] / 4, args_expected_number)



if (!CheckArgsNumber(amx, params, args_expected_number)) {
PRINT_PARAM_ERROR();
return 0;
}

Daniel_Cortez
19.11.2016, 17:53
По идее это хост-приложение должно выводить информацию об ошибках времени выполнения в интерпретаторе. Так что, ИМХО, настоящий вопрос здесь - "почему сервер SA:MP этого не делает, оставляя все ошибки скрытыми?"
К слову, достаточно добавить всего лишь пару-тройку строк кода, чтобы, как минимум, отобразить суть ошибки (например, "stack/heap collision" или "array index out of bounds"), но в SA:MP даже этого нет (и не будет, по очевидным причинам).

Если же говорить о том, как узнать имя текущей нативной функции, в этом может помочь регистр CIP (amx->cip).
Чуть позже я выложу код, но в отдельной теме, заодно постараюсь объяснить всю теорию.

UPD: http://pro-pawn.ru/showthread.php?14522

Daniel_Cortez
02.06.2017, 09:01
Статья обновлена. Теперь функция CheckNumberOfArguments используется через макрос CheckArgs, а сама функция перемещена в шаблон плагина для SA-MP (http://pro-pawn.ru/showthread.php?15317). Кроме того, в сообщении об ошибке выводится имя нативной функции, которое узнаётся с помощью GetCurrentNativeFunctionName (http://pro-pawn.ru/showthread.php?14522).