PDA

Просмотр полной версии : [C++] Шаблон плагина для SA-MP



Daniel_Cortez
01.06.2017, 19:28
Привет всем.

Если вы интересовались созданием плагинов для SA-MP, то могли заметить, что на оффе есть тема (http://forum.sa-mp.com/showthread.php?t=295798), в которой рассказывается, как можно написать плагин для SA-MP, однако там приводится пример только для Visual Studio под Windows. Информация о компиляции под Linux в той теме практически отсутствует. Я решил пойти немного в другом направлении и разработать легко конфигурируемый шаблон плагина для SA-MP, который можно настроить под свои задачи.

Описание:

Что реализовано на данный момент:
Возможность указания в одном файле (https://github.com/Daniel-Cortez/samp-plugin-template/blob/master/CMakeLists.txt#L20) базовых настроек проекта:
название плагина;
версия (semver (http://semver.org/lang/ru/));
поддержка ProcessTick (по умолчанию отключена, чтобы не тратить лишние ресурсы на вызов неиспользуемой функции);
список исходных файлов плагина (по умолчанию в нём только main.cpp).

Автоматическая генерация проектов под Windows (Visual Studio) и Linux (Code::Blocks, CodeLite, GNU Make).

Проверка соответствия версий инклуда и плагина при загрузке последнего.

Удобная организация обработки аргументов функции (http://pro-pawn.ru/showthread.php?14397).

Набор вспомогательных функций (файлы pluginutils.h и pluginutils.cpp):


GetPublicVariable - позволяет получить значение из скриптовой переменной с атрибутом public. Возвращает true, если переменная существует, иначе возвращает false. Само значение переменной возвращается по ссылке в параметре value.

bool GetPublicVariable(AMX *amx, const char *name, cell &value);
SplitVersion - разделяет код версии плагина/инклуда из одного числа на 3 составляющих: мажорная версия, минорная версия и патч. Пример использования можно найти в main.cpp.

void SplitVersion(cell version, int &major, int &minor, int &build);
CheckIncludeVersion - проверяет соответствие версий инклуда и плагина. Возвращает true, если версии соответствуют, иначе возвращает false и выводит в лог сообщение об ошибке.

bool CheckIncludeVersion(AMX *amx);
AlignCell, AlignCellArray - переставляет местами байты в ячейке и массиве ячеек соответственно. Полезно, если нужно передать/получить из виртуальной машины массив байтов (т.н. "упакованный массив"), т.к. на системах с порядком байт Little Endian порядок байтов массива в виртуальной машине отличается от оного на физической (например, элементы массива с порядком 0, 1, 2, 3, 4, 5, 6, 7 в виртуальной машине хранятся в порядке 3, 2, 1, 0, 7, 6, 5, 4). К сведению, порядок Little Endian распространён на большинстве процессоров, в т.ч. семейства x86.

cell AlignCell(cell value);

void AlignCellArray(cell a[], size_t num_elements);
CopyAndAlignCellArray - копирует массив ячеек с перестановкой байтов "на лету".

void CopyAndAlignCellArray(cell *dest, cell *src, size_t num_cells);
GetPackedArrayCharAddr - вычисляет адрес элемента упакованного массива.

unsigned char *GetPackedArrayCharAddr(cell arr[], cell index);
GetCurrentNativeFunctionName - возвращает имя нативной функции, из которой вызвана данная функция. Может пригодиться при выводе сообщений в консоль/лог. Пример использования можно найти в main.cpp.

const char *GetCurrentNativeFunctionName(AMX *amx)
CheckNumberOfArguments - проверяет количество параметров, переданных нативной функции. Возвращает true, если есть минимально требуемое число параметров, иначе возвращает false и выводит в консоль/лог сообщение о недостаточном количестве параметров в следующем формате. Пример использования можно найти в main.cpp.

bool CheckNumberOfArguments(AMX *amx, const cell *params, int num_expected)
ReplaceNative - заменяет нативную функцию с указанным именем. Возаращает true, если функция с указанным именем используется в скрипте и успешно подменена, иначе возаращает false. Если параметр orig не равен NULL, в него записывается адрес оригинальной нативной функции. Пример использования - в main.cpp.

bool ReplaceNative(AMX *amx, const char *name, AMX_NATIVE ntv, AMX_NATIVE *orig)





Использование:
Установить CMake (https://cmake.org/download/).
Установить компилятор и среду разработки (под Windows рекомендуется Visual Studio, под Linux - Code::Blocks или CodeLite)
Клонировать репозиторий samp-plugin-template или скачать и распаковать архив с исходниками (см. ссылки в конце статьи).
В файле CMakeLists.txt отредактировать настройки проекта (секция в начале файла, помеченная комментарием "Settings").
Запустить программу cmake-gui. В поле "Where is the source code" указать папку, в которую вы клонировали репозиторий или распаковали архив, а в "Where to build the binaries" - тот же самый путь, но в конце добавить "/build" (пример "C:/samp-plugin-template" и "C:/samp-plugin-template/build").
Нажать кнопку "Configure". Откроется окно, в котором следует указать среду разработки, в которой вы будете разрабатывать плагин, и нажать "Finish".
Когда конфигурирование закончится, нажать "Generate".
Зайти в папку "build", открыть с помощью выбранной среды разработки файл проекта и скомпилировать плагин. После компиляции инклуд будет автоматически скопирован в одну папку с плагином.


Примечание: если вызвать из скрипта функцию HelloWorld_CheckArgsTest, будет выведено сообщение об ошибке (неправильное число аргументов), т.к. в инклуде для этой функции указано неправильное число аргументов (функции требуется хотя бы 1 аргумент, но в инклуде их нет вообще). Это сделано специально, чтобы проверить работу функции pluginutils::CheckNumberOfArguments.

Лицензирование:
Сборочный скрипт CMakeLists.txt доступен под условиями лицензии ISC (https://opensource.org/licenses/ISC).
Файл pluginutils.h - под лицензией zlib (https://opensource.org/licenses/zlib).
Остальные файлы (за исключением SA-MP plugin SDK, естественно) выпущены под лицензией Creative Commons Zero (CC0) (https://creativecommons.org/publicdomain/zero/1.0/) (отказ от авторских прав). Можете не бояться поставить свой копирайт в plugin.inc.in (https://github.com/Daniel-Cortez/samp-plugin-template/blob/master/plugin.inc.in#L2) и main.cpp (https://github.com/Daniel-Cortez/samp-plugin-template/blob/master/main.cpp#L2) - это всего лишь "каркас" для плагина, для меня нет смысла резервировать свои авторские права за такими мелочами. Тем не менее, было бы здорово взамен увидеть свой ник в списке благодарностей в документации к вашему плагину.


Репозиторий: 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)
Копирование данной статьи на других ресурсах без разрешения автора запрещено!

f55555
01.06.2017, 19:33
Как всегда постарался, молодец!
p.s не планируешь обновить учебник Pro-Pawn?

ziggi
01.06.2017, 20:48
Удобно, такое бы пораньше)

$continue$
23.07.2017, 22:10
Так понимаю поддержки MinGW нет?

Daniel_Cortez
24.07.2017, 14:18
Так понимаю поддержки MinGW нет?
Нет, для этого либо придётся вносить изменения в SA-MP Plugin SDK, либо делать какие-то костыли с параметрами компилятора/линковщика. Пытался осуществить последнее, но так ничего и не получилось сделать с именами экспортируемых функций (в итоге вместо "Load" получалось "Load@4", с остальными функциями то же самое). Если есть идеи, как это исправить, буду рад принять PR.

Для тех, кто хочет реализовать поддержку MinGW:


if(MINGW)
add_definitions("/DHAVE_STDINT_H /D__need_size_t")
endif()

Эти два макроса помогут устранить ошибки при определении типов cell и size_t.

$continue$
24.07.2017, 14:50
Сейчас устанавливаю Cygwin. Попробую сделать под него поддержку. Отпишу, если что.

P.S:
А как CodeBlock тогда работает? Там же MinGW на борту. Или ты только в Linux с ним пробовал с настройкой компилера?
Или в окнах использовал MSVC?

P.S.S: в идеале бы добавить поддержку шланга (CLang), MinGW, Cygwin.

Если есть идеи, как это исправить, буду рад принять PR

Daniel_Cortez
24.07.2017, 16:16
Сейчас устанавливаю Cygwin. Попробую сделать под него поддержку. Отпишу, если что.
Ок. Как я уже говорил, там, скорее всего, всё сводится к добавлению параметров для компилятора или линковщика, которые и для MinGW, и для Cygwin должны быть одинаковыми. Проблема лишь в том, что если для решения проблемы нужны параметры для линковщика, то они всё равно из свойства LINK_FLAGS будут передаваться через gcc/g++, который не факт что распознает их. Либо так, либо делать делать сборку вручную, через add_custom_command.



P.S:
А как CodeBlock тогда работает? Там же MinGW на борту. Или ты только в Linux с ним пробовал с настройкой компилера?
Или в окнах использовал MSVC?
Именно так. SDK с самого начала делали только под MSVC



P.S.S: в идеале бы добавить поддержку шланга (CLang), MinGW, Cygwin.
Первый уже справляется под Linux.

Daniel_Cortez
26.07.2017, 15:12
Обновление от 26.07.17:
Добавлена поддержка MinGW.
Исправлен баг в функции pluginutils::GetCurrentNativeFunctionName.
Плагин больше не выдаёт сообщения вида "Include file version does not match with the plugin version" при загрузке фильтрскриптов, скомпилированных без включения инклуда от плагина.

Bib
30.08.2017, 19:32
Можно какой-нибудь пример с указанием исходников? Что только не пробовал, всё равно ошибки :sad:

Daniel_Cortez
30.08.2017, 20:40
Можно какой-нибудь пример с указанием исходников? Что только не пробовал, всё равно ошибки :sad:
Это шаблон, а не библиотека, тут никакого самостоятельного примера не сделаешь - это будет просто форк шаблона. Можно разве что сделать урок по использованию, но в 1-м посте и так уже есть инструкция.

Daniel_Cortez
10.01.2018, 21:36
Шаблон обновлён до версии 1.1.
Добавлена функция pluginutils::ReplaceNative(), предназначенная для подмены/перехвата нативных функций.
Добавлен пример перехвата нативных функций в main.cpp.
Исправлена ошибка при генерации проекта с помощью CMake под Linux.
Исправлен варнинг "symbol is never used: <название плагина>_ver" в файле *.inc.
Реализация части функций из pluginutils.h была перенесена в новый файл pluginutils.cpp для ускорения компиляции.

Daniel_Cortez
27.10.2018, 19:47
Версия 1.2.
Исправлена сборка под 64-разрядными дистрибутивами Linux (плагин должен собираться под 32-разрядную архитектуру).
Отдельное спасибо $continue$ за помощь в решении проблемы.
Добавлен поиск заголовочных файлов stdint.h и inttypes.h, чтобы избежать ситуации с неправильным определением типов int32_t и uint32_t.

UnO
15.03.2019, 17:47
Вот такую ошибку cmake сообщает:

Looking for include file alloca.h - not found
Как исправить?

x86
16.03.2019, 04:36
Вот такую ошибку cmake сообщает:

Как исправить?

Вероятно, автор должен был предусмотреть это. В вашем же случае можно в конфиг-файле cmake заменить alloca.h на malloc.h (хотя лучше дождаться пока тс сделает обновление).

Desulaid
16.03.2019, 11:13
Вероятно, автор должен был предусмотреть это. В вашем же случае можно в конфиг-файле cmake заменить alloca.h на malloc.h (хотя лучше дождаться пока тс сделает обновление).

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

В твоём же решении ты предлагаешь заменить alloca.h на malloc.h... Что? Ты предлагаешь заменить файл, который делает другую деятельность, отличную от malloc.h.
Впрочем, давай я тебя удивлю.
https://i.imgur.com/O2rcZaJ.png

О господи, оно же собралось! Как же это возможно?!

execution
10.10.2020, 14:47
Без конца в концоль флудит "Hello from hook_IsPlayerConnected", так и должно?

Daniel_Cortez
11.10.2020, 17:13
Без конца в концоль флудит "Hello from hook_IsPlayerConnected", так и должно?
Перепроверил на своём ПК, сообщение выводится только один раз. Первая причина, которая приходит в голову - возможно, что баг проявляется в сочетании с другим плагином, который тоже перехватывает IsPlayerConnected(). Чтобы проверить гипотезу, я скопировал плагин "helloworld.dll" в папке "plugins", назвав копию "helloworld_copy.dll", добавил "helloworld_copy" в server.cfg (при этом оставив "helloworld", ибо цель проверить, как два плагина перехватывают одну и ту же функцию). Итог: при вызове IsPlayerConnected() флуда нет (сообщения повторяются 2 раза, но так и должно быть), вряд ли дело в моём коде.

Daniel_Cortez
11.10.2020, 18:46
Тем временем, релиз 1.2.
Добавлены функции для чтения и записи строк:

char *GetCString(AMX *amx, cell address, int &error);
bool SetCString(AMX *amx, cell address, cell size, const char *str, bool pack = false);

std::string GetCXXString(AMX *amx, cell address, int &error);
bool SetCXXString(AMX *amx, cell address, cell size, const std::string &str, bool pack = false);

execution
11.10.2020, 19:23
Перепроверил на своём ПК, сообщение выводится только один раз. Первая причина, которая приходит в голову - возможно, что баг проявляется в сочетании с другим плагином, который тоже перехватывает IsPlayerConnected(). Чтобы проверить гипотезу, я скопировал плагин "helloworld.dll" в папке "plugins", назвав копию "helloworld_copy.dll", добавил "helloworld_copy" в server.cfg (при этом оставив "helloworld", ибо цель проверить, как два плагина перехватывают одну и ту же функцию). Итог: при вызове IsPlayerConnected() флуда нет (сообщения повторяются 2 раза, но так и должно быть), вряд ли дело в моём коде.

Кстати на счёт других плагинов, использовал лишь streamer.dll и убрав его - всё нормализовалось.

UPD: Ещё вопрос: если мне необходимо будет использовать стандартные функции samp, то мне необходимо будет подключать sampgdk?

Daniel_Cortez
11.10.2020, 20:24
Кстати на счёт других плагинов, использовал лишь streamer.dll и убрав его - всё нормализовалось.
Очень странно... Проверил с последним релизом стримера (2.9.4), никакого флуда нет, независимо от порядка подключения streamer и helloworld в server.cfg.


UPD: Ещё вопрос: если мне необходимо будет использовать стандартные функции samp, то мне необходимо будет подключать sampgdk?
Функция ReplaceNative() перед подменой сохраняет адрес функции-оригинала, чтобы в дальнейшем её можно было вызвать из подменной функции. В main.cpp есть пример.

execution
12.10.2020, 15:04
Функция ReplaceNative() перед подменой сохраняет адрес функции-оригинала, чтобы в дальнейшем её можно было вызвать из подменной функции. В main.cpp есть пример.
То есть, чтобы использовать функцию в плагине, необходимо её перехватить? И так-же с вызовом каллбеков (OnPlayerConnect)?
Ибо подключил sampgdk но почему-то ничего не вызывается (по крайне мере каллбеки).

Daniel_Cortez
12.10.2020, 23:55
То есть, чтобы использовать функцию в плагине, необходимо её перехватить?
Нет, это просто я не так понял вопрос (сначала подумал, что речь идёт о вызове перехваченной функции, когда, видимо, на самом деле имелся в виду просто вызов нативных функций).
Соль в том, что интерпретатор может раскрыть адрес нативной функции только если она используется в скрипте (AMX) - если нет, то его никак не узнаешь. Это ограничение можно обойти, создав фейковый экземпляр AMX, в котором используются нужные нативки, инициализировать его, вытащить оставленные интерпретатором адреса нативок из таблицы используемых нативных функций, затем деинициализировать и удалить экземпляр AMX. Так сделано в sampgdk и примерно так же я мог бы сделать и в этом шаблоне, вот только вопрос: стоит ли оно того?


И так-же с вызовом каллбеков (OnPlayerConnect)?
Я не делал никаких надстроек для вызова public-функций.

execution
13.10.2020, 11:05
Так сделано в sampgdk и примерно так же я мог бы сделать и в этом шаблоне, вот только вопрос: стоит ли оно того?

А подключать sampgdk возможно? Дело в том, что почему-то оно не хочет работать.

PLUGIN_EXPORT bool PLUGIN_CALL OnPlayerConnect(int playerid)
{
SendClientMessage(playerid, -1, " ");
return 1;
}

При использовании SendClientMessage, выдаёт:
1. LNK2019 ссылка на неразрешенный внешний символ __imp__sampgdk_SendClientMessage в функции _OnPlayerConnect@4 anticheat C:\Users\execution\Desktop\Anticheat\samp-plugin-template-master\build\main.obj 1
2. LNK1120 неразрешенных внешних элементов: 1 anticheat C:\Users\execution\Desktop\Anticheat\samp-plugin-template-master\build\Debug\anticheat.dll 1

А если не использовать функцию, скомпилируется, но PLUGIN_EXPORT bool PLUGIN_CALL OnPlayerConnect(int playerid) - не вызывается.
Смотрел как использовали другие и, например, в плагине streamer incongito всё нормально работает

Daniel_Cortez
15.10.2020, 15:54
Каким образом подключаешь sampgdk? Если только включением заголовочного файла, то этого недостаточно: нужно также добавить подключить его в CMakeLists.txt с помощью add_subdirectory() (https://cmake.org/cmake/help/latest/command/add_subdirectory.html), чтобы скомпилировать и включить в проект реализации функций sampgdk.

execution
16.10.2020, 10:27
Каким образом подключаешь sampgdk? Если только включением заголовочного файла, то этого недостаточно: нужно также добавить подключить его в CMakeLists.txt с помощью add_subdirectory() (https://cmake.org/cmake/help/latest/command/add_subdirectory.html), чтобы скомпилировать и включить в проект реализации функций sampgdk.

Почему-то у меня теперь проект не компилируется. (cmd.exe завершился к кодом 1)

Пробовал такие варианты (закинул sampgdk.h, sampgdk.h на всякий случай и в бинарник и в соурсник)

add_subdirectory(sampgdk)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/sampgdk.h)
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/sampgdk)

В set(PLUGIN_SRC так-же указал.

p.s. качать всю папку sampgdk нет необходимости. Достаточно sampgdk.c, sampgdk.h, верно?

Daniel_Cortez
16.10.2020, 15:24
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/sampgdk.h)

Зачем здесь ".h"? В посте выше я не просто так оставил ссылку - там буквально в самом начале написано, что нужно указать директорию, в которой есть CMakeLists.txt того проекта, который нужно подключить к своему ("The source_dir specifies the directory in which the source CMakeLists.txt and code files are located").


p.s. качать всю папку sampgdk нет необходимости. Достаточно sampgdk.c, sampgdk.h, верно?
Если не разбираешься, лучше взять всю папку (насколько я понял, можно только удалить подпапки "doc" и "plugins", остальное может понадобиться).

UPD: Попробовал подключить sampgdk самостоятельно, в CMakeLists.txt после секции "Settings" добавил следующее:


set(SAMPSDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/SDK" CACHE PATH "" FORCE)
set(SAMPSDK_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/SDK" CACHE PATH "" FORCE)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/sampgdk)
set(PLUGIN_LINK_DEPENDENCIES ${PLUGIN_LINK_DEPENDENCIES} "sampgdk")

Но даже после этого нужно установить:
Python - в README от sampgdk сказано, что подойдёт версию 2.7, однако модуль PLY (см. далее) требует минимум Python 3.6;
пакетный менеджер pip - если верить этой (https://pip.pypa.io/en/stable/installing/) странице, нужно скачать установочный скрипт (https://bootstrap.pypa.io/get-pip.py) и выполнить его командой "python get-pip.py"; после установки отобразится путь, в который установился pip, этот путь нужно добавить в системную переменную PATH;
модуль "ply" ("pip install ply"; но сначала нужно перезапустить командную строку, чтобы загрузить обновлённую переменную PATH с добавленным путём установки pip).

execution
16.10.2020, 18:25
Странно. Скачав папку sampgdk и добавил доп. настройки в CMake (который ты скинул), выдаёт ошибку:

LNK1104 не удается открыть файл "sampgdk\Debug\sampgdk4d.lib"

Это возможно отключить/обойти, или я что-то не так сделал?

Python 3.8 есть. Так-же хотел уточнить, т.к. в интернете не нашёл эту информацию: Что необходимо указывать в PLUGIN_LINK_DEPENDENCIES и PLUGIN_COMPILE_DEFINITIONS?

Daniel_Cortez
19.10.2020, 15:52
Странно. Скачав папку sampgdk и добавил доп. настройки в CMake (который ты скинул), выдаёт ошибку:

LNK1104 не удается открыть файл "sampgdk\Debug\sampgdk4d.lib"

Это возможно отключить/обойти, или я что-то не так сделал?
Скорее всего, что-то не так сделал, у меня компилировалось без проблем.


Python 3.8 есть. Так-же хотел уточнить, т.к. в интернете не нашёл эту информацию: Что необходимо указывать в PLUGIN_LINK_DEPENDENCIES и PLUGIN_COMPILE_DEFINITIONS?
PLUGIN_LINK_DEPENDENCIES - список зависимостей (библиотек), с которыми должен линковаться плагин. В отрывке кода из поста выше на последней строке как раз есть добавление "sampgdk" в список.
PLUGIN_COMPILE_DEFINITIONS - для указания через командную строку макросов (пример: "-D<имя макроса>=[значение]"), которые могут пригодиться при использовании сторонних библиотек.

execution
20.10.2020, 10:29
На самом деле странно. Даже скачал всё заново и сделал всё по инструкции - всё равно тоже самое:

main.cpp
#include <cstddef>

#include "SDK/amx/amx.h"
#include "SDK/plugincommon.h"
#include "pluginconfig.h"
#include "pluginutils.h"
#include "sampgdk.h"

CMakeLists.txt (добавил лишь твои инструкции а также sampgdk.h/c в set(PLUGIN_SRC)
#==============================================================================#
# SA-MP plugin template v1.1 #
# #
# Copyright (c) 2017-2018, Stanislav Gromov #
# #
# Permission to use, copy, modify, and/or distribute this software for any #
# purpose with or without fee is hereby granted, provided that the above #
# copyright notice and this permission notice appear in all copies. #
# #
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES #
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF #
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR #
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES #
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN #
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF #
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #
#==============================================================================#

cmake_minimum_required(VERSION 3.0)
include(CheckIncludeFiles)

#==============================================================================#
# Settings #
#==============================================================================#
set(PLUGIN_NAME "anticheat")
set(PLUGIN_VERSION_MAJOR 0)
set(PLUGIN_VERSION_MINOR 0)
set(PLUGIN_VERSION_BUILD 1)

set(PLUGIN_SUPPORTS_PROCESSTICK FALSE)
set(PLUGIN_SRC
"main.cpp"
)
set(PLUGIN_LINK_DEPENDENCIES "")
set(PLUGIN_COMPILE_DEFINITIONS "")
#==============================================================================#

set(SAMPSDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/SDK" CACHE PATH "" FORCE)
set(SAMPSDK_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/SDK" CACHE PATH "" FORCE)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/sampgdk)
set(PLUGIN_LINK_DEPENDENCIES ${PLUGIN_LINK_DEPENDENCIES} "sampgdk")

project(${PLUGIN_NAME}
LANGUAGES CXX
VERSION ${PLUGIN_VERSION_MAJOR}.${PLUGIN_VERSION_MINOR}.${PLUGIN_VERSION_BUILD}
)

# Check include files availability
set(REQUIRED_INCLUDE_FILES
"inttypes.h"
"stdint.h"
"alloca.h"
)
foreach(INCLUDE_FILE ${REQUIRED_INCLUDE_FILES})
string(REGEX REPLACE "\\.|/" "_" DEFINITION_NAME "HAVE_${INCLUDE_FILE}")
string(TOUPPER ${DEFINITION_NAME} DEFINITION_NAME)
check_include_files("${INCLUDE_FILE}" ${DEFINITION_NAME})
if(${DEFINITION_NAME})
set(PLUGIN_COMPILE_DEFINITIONS ${PLUGIN_COMPILE_DEFINITIONS} "${DEFINITION_NAME}=1")
endif()
endforeach()

string(TOLOWER ${PLUGIN_NAME} PLUGIN_NAME_LOWERCASE)
configure_file("plugin.inc.in" "${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_NAME_LOWERCASE}.inc")
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_BINARY_DIR})

set(PLUGIN_SRC
"${PLUGIN_SRC}"
"SDK/amx/amx.h"
"SDK/plugincommon.h"
"SDK/amxplugin.cpp"
"${CMAKE_CURRENT_BINARY_DIR}/pluginconfig.h"
"pluginutils.h"
"pluginutils.cpp"
"sampgdk.h"
"sampgdk.c"
)
if(UNIX)
set(PLUGIN_SRC
"${PLUGIN_SRC}"
"SDK/amx/sclinux.h"
)
set(PLUGIN_COMPILE_DEFINITIONS ${PLUGIN_COMPILE_DEFINITIONS} "LINUX")
endif()
if(MINGW)
set(PLUGIN_COMPILE_DEFINITIONS ${PLUGIN_COMPILE_DEFINITIONS} "HAVE_STDINT_H=1" "__need_size_t")
endif()

# Make sure the target is 32-bit
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4)
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32")
endif()
endif()

set(PLUGIN_SUPPORTS_FLAGS "SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES")
if(PLUGIN_SUPPORTS_PROCESSTICK)
set(PLUGIN_SUPPORTS_FLAGS "${PLUGIN_SUPPORTS_FLAGS} | SUPPORTS_PROCESS_TICK")
endif()
configure_file("pluginconfig.h.in" "${CMAKE_CURRENT_BINARY_DIR}/pluginconfig.h")

add_library(${PLUGIN_NAME_LOWERCASE} SHARED ${PLUGIN_SRC})
target_link_libraries(${PLUGIN_NAME_LOWERCASE} ${PLUGIN_LINK_DEPENDENCIES})
set_property(TARGET ${PLUGIN_NAME_LOWERCASE} APPEND_STRING PROPERTY "COMPILE_DEFINITIONS" ${PLUGIN_COMPILE_DEFINITIONS})
set_property(TARGET ${PLUGIN_NAME_LOWERCASE} PROPERTY PREFIX "")
if(MSVC)
set(STR_PLUGIN_DEF_PROCESSTICK "")
if(PLUGIN_SUPPORTS_PROCESSTICK)
set(STR_PLUGIN_DEF_PROCESSTICK "ProcessTick")
endif()
configure_file("plugin.def.in" "${CMAKE_CURRENT_BINARY_DIR}/plugin.def")
set_property(TARGET ${PLUGIN_NAME_LOWERCASE}
APPEND_STRING PROPERTY LINK_FLAGS "/DEF:\"${CMAKE_CURRENT_BINARY_DIR}/plugin.def\""
)
endif()
if(MINGW)
set_property(TARGET ${PLUGIN_NAME_LOWERCASE}
APPEND_STRING PROPERTY LINK_FLAGS "-Wl,-k"
)
endif()
add_custom_command(
TARGET "${PLUGIN_NAME_LOWERCASE}" POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_NAME_LOWERCASE}.inc
${CMAKE_CURRENT_BINARY_DIR}/$<CONFIGURATION>/${PLUGIN_NAME_LOWERCASE}.inc
)

install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_NAME_LOWERCASE}.inc" DESTINATION "include")
install(TARGETS ${PLUGIN_NAME_LOWERCASE}
LIBRARY DESTINATION "plugins"
RUNTIME DESTINATION "plugins"
)
if(WIN32)
set(CPACK_GENERATOR "ZIP")
elseif(UNIX)
set(CPACK_GENERATOR "TGZ")
endif()
set(CPACK_PACKAGE_NAME "${PLUGIN_NAME_LOWERCASE}")
set(CPACK_PACKAGE_VERSION_MAJOR ${PLUGIN_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PLUGIN_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PLUGIN_VERSION_BUILD})
include(CPack)

Папку sampgdk и sampgdk.h/c закинул в source.

oukibt
13.11.2020, 17:43
Здравствуйте. А что делать если я получаю ошибку при загрузке плагина (failed).
Ранее я пробовал делать плагин без этого шаблона, и получал ошибку о несоответствии архитектуры с требуемой (plugin does not conform to architecture. failed.).
В настройках (CMakeLists.txt) изменил лишь название проекта. Компилировал через VS 2019, ошибок при компиляции не было.

Morphone
18.11.2020, 21:47
Перепроверил на своём ПК, сообщение выводится только один раз. Первая причина, которая приходит в голову - возможно, что баг проявляется в сочетании с другим плагином, который тоже перехватывает IsPlayerConnected(). Чтобы проверить гипотезу, я скопировал плагин "helloworld.dll" в папке "plugins", назвав копию "helloworld_copy.dll", добавил "helloworld_copy" в server.cfg (при этом оставив "helloworld", ибо цель проверить, как два плагина перехватывают одну и ту же функцию). Итог: при вызове IsPlayerConnected() флуда нет (сообщения повторяются 2 раза, но так и должно быть), вряд ли дело в моём коде.

Проблема актуальна. Тысячу раз выводится сообщение.
Проверил по очереди отключать другие плагины, обнаружил, что это из-за sscanf

oukibt
22.11.2020, 06:11
Собственно, я так и не понял, как совместить между собой этот шаблон и sampgdk. Я бы давно использовал sampgdk, но там, почему-то, не работает перехват кастомных нативных функций. Я много у кого об этом спрашивал, и получал один и тот же ответ, сделать примерно вот так:


PLUGIN_EXPORT unsigned int PLUGIN_CALL Supports()
{
return SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES;
}

Но увы, это не работало, и при запуске мода с использованием кастомной функции я получал это уведомление (https://cdn.discordapp.com/attachments/231799180127895553/779588443235155978/unknown.png).

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

oukibt
24.11.2020, 17:53
Проблема решена.

В файл main.def нужно добавить
AmxLoad и AmxUnload, ошибки тогда не будет, но консоль просто будет закрываться. Для решения использовать этот код:


extern void *pAMXFunctions;
void *(*logprintf)(const char *fmt, ...);

PLUGIN_EXPORT bool PLUGIN_CALL Load(void **ppData)
{
pAMXFunctions = ppData[PLUGIN_DATA_AMX_EXPORTS];
logprintf = (void *(*)(const char *fmt, ...))ppData[PLUGIN_DATA_LOGPRINTF];
if (NULL == pAMXFunctions || NULL == logprintf)
return false;
return sampgdk::Load(ppData);
}

Greenkey
07.06.2021, 00:56
Наверное, уже несколько неактуально, но все же спрошу. Подскажите, пожалуйста, в чем может быть проблема, если сама библиотека успешно собирается, но подключить ее к серверу не удается (в логах сервера пишет, что плагин не загружен).

До этого я создал новый проект в своей IDE (shared library), куда импортировал все файлы, предложенные автором, @Daniel_Cortez. Ну, и скомпилировал, предварительно задав компилятор i686-w64-mingw32 и выставив тип сборки Release (хотя и с Debug пробовал).

Ах, да, запускаю на сервере CR 0.3.7. Но, насколько помню, это допустимо, поскольку адреса функций у CR 0.3.7 и SA совпадают.

Daniel_Cortez
08.06.2021, 16:10
Наверное, уже несколько неактуально, но все же спрошу. Подскажите, пожалуйста, в чем может быть проблема, если сама библиотека успешно собирается, но подключить ее к серверу не удается (в логах сервера пишет, что плагин не загружен).

До этого я создал новый проект в своей IDE (shared library), куда импортировал все файлы, предложенные автором, @Daniel_Cortez. Ну, и скомпилировал, предварительно задав компилятор i686-w64-mingw32 и выставив тип сборки Release (хотя и с Debug пробовал).

Ах, да, запускаю на сервере CR 0.3.7. Но, насколько помню, это допустимо, поскольку адреса функций у CR 0.3.7 и SA совпадают.
Что за странная мода выдумывать что-то с созданием проекта через IDE, а потом удивляться, почему плагин не работает? В 1-м посте написано, как нужно генерировать проект с помощью CMake. Попробуйте собрать так, как сказано там, и проверьте загрузку собранного плагина сначала на сервере SA-MP, а затем на CR-MP - в таком порядке хотя бы будет понятно, в чём примерно проблема.

Greenkey
09.06.2021, 16:03
Что за странная мода выдумывать что-то с созданием проекта через IDE, а потом удивляться, почему плагин не работает? В 1-м посте написано, как нужно генерировать проект с помощью CMake. Попробуйте собрать так, как сказано там, и проверьте загрузку собранного плагина сначала на сервере SA-MP, а затем на CR-MP - в таком порядке хотя бы будет понятно, в чём примерно проблема.

Я использую IDE CLion, которая поддерживает работу с CMake. Поэтому напрямую создал проект, и в CMakeLists.txt скопировал Ваш CMakeLists. Собрать через cmake-gui проект CLion невозможно :sad:

Daniel_Cortez
09.06.2021, 16:29
Я использую IDE CLion, которая поддерживает работу с CMake. Поэтому напрямую создал проект, и в CMakeLists.txt скопировал Ваш CMakeLists. Собрать через cmake-gui проект CLion невозможно :sad:
Тогда помочь могу мало чем, ибо шаблон тестировался только с генерацией проекта из CMake. Создание напрямую из других IDE в теории возможно, но не гарантируется из-за кривизны реализации генерации проектов из CMakeLists в самих IDE.
В любом случае, попробуйте сначала подключить плагин к серверу SA-MP.

Greenkey
10.06.2021, 23:49
Тогда помочь могу мало чем, ибо шаблон тестировался только с генерацией проекта из CMake. Создание напрямую из других IDE в теории возможно, но не гарантируется из-за кривизны реализации генерации проектов из CMakeLists в самих IDE.
В любом случае, попробуйте сначала подключить плагин к серверу SA-MP.

В один момент задумался: возможно ли использовать компилятор Microsoft Visual Studio в CLion (коль уж с этим компилятором получается вполне рабочая библиотека). После недолгих поисков узнал, что сделать это проще простого. Собрав проект в CLion с использованием компилятора Microsoft Visual Studio, я наконец-то получил рабочую dll-библиотеку.

Правда... Это несколько смешно: иметь Microsoft Visual Studio, но использовать CLion и компилятор MSVC. Уж больно люблю я JetBrains. А Visual Studio крайне удобна для десктопных приложений.

В любом случае большое Вам спасибо за потраченное на меня время. Будьте здоровы.