Разработчик
Проверенный
Участник
Пользователь
- Сообщения
- 207
- Реакции
- 420
- Помог
- 10 раз(а)
Введение
К оглавлению
Данная статья является переводом официальной статьи с Wiki AlliedModders с поправками.
Данная статья является переводом официальной статьи с Wiki AlliedModders с поправками.
Handle - специальный тип переменных, используемый в SourcePawn. Является ссылкой (указателем) на некоторый объект из C или C++. У него есть счётчик упоминаний в других плагинах. Пока этот счётчик не равен нулю, SourceMod не уничтожит объект из своей памяти.
Поскольку SourcePawn не имеет сборщика мусора, объекты Handle - своеобразный способ дать плагинам вручную контролировать свою память.
Заметка: потеряв указатель на объект, он не закроется до тех пор, пока все ссылки (Handle) не будут закрыты. Все ссылки на объекты автоматически перестают быть валидными при выгрузке плагина, или ручном закрытии оной (оператор delete, или ф-ия CloseHandle()).
Открытие плагином нового объекта Handle
По факту, объекты открываются функциями ядра/расширений. Для примера, ф-ия OpenDirectory() возвращает Handle папки (тип Directory), из которого мы можем вытянуть список имеющихся там файлов, папок, и прочих объектов (сокеты UNIX) с помощью нативов, предназначенных для работы с папкой.
Код:
Handle hMaps = OpenDirectory("maps");
Закрытие объектов
Уничтожение указателя на объект вручнуюУничтожение указателя может повлечь за собой полное удаление оригинального объекта из памяти, если это будет последний указатель. Для примера, уничтожение указателя на объект соединения с базой (тип IDatabase), повлечёт за собой закрытие соединения, если больше не будет указателей на это соединение. Указатель можно закрыть с помощью оператора языка delete, или функции CloseHandle().
Код:
// Так:
delete g_hDatabase;
// Или так:
CloseHandle(g_hDatabase);
// Закрытие указателя не обнуляет его в переменной, потому если Вы где-то отслеживаете, не равна ли переменная null или INVALID_HANDLE, то имеет смысл приравнять её к null после закрытия.
g_hDatabase = null;
Когда плагин выгружается из памяти сервера, все указатели автоматически становятся невалидными, закрытыми. Это не означает, что закрытие указателей опционально. Возьмём для примера следующий код:
Код:
stock bool FileExistsInFolder(const char[] szPath, const char[] szFile) {
Handle hDirectory = OpenDirectory(szPath);
if (hDirectory == null) { // При открытии хендла, так же может произойти неудача, потому имеет смысл проверять результат
return false;
}
char szOtherFile[PLATFORM_MAX_PATH];
FileType eFileType;
while (ReadDirEntry(hDirectory, szOtherFile, sizeof szOtherFile, eFileType)) {
if (eFileType == FileType_File && // если тип прочитанного объекта из папки - файл
strcmp(szOtherFile, szFile, true) == 0) { // и его имя совпадает
return true;
}
}
delete hDirectory;
return false;
}
И так, когда мы должны уничтожать указатели? Сформируем два правила:
- Если описание натива использует терминологию вроде "Открывает", "Создаёт" или похожие. Как правило, в описании так же пишется, что открытый указатель надо закрывать.
- Если сам объект, на который ссылается указатель, используется временно для некоторых целей. Простейший пример: передача нескольких данных на функцию с помощью объекта типа DataPack.
Когда мы не можем уничтожать указатели?
Есть пара моментов, когда мы не можем уничтожать указатели. Самый банальный пример: тип Plugin. Мы его не можем закрыть, он принадлежит ядру. Мы можем лишь вытягивать информацию из него.
Так же, один плагин не может закрывать указатели другого плагина. Это своеобразная защита от недочётов в API и прочих ошибок. Если Вам надо дать возможность закрыть указатель другим плагином, склонируйте объект на имя нужного плагина (подробнее ниже), свой указатель закройте, и отдайте указатель клона.
О счётчике указателей
Закрытие указателей на объект не всегда означает, что сам объект сразу же будет уничтожен. Пока в памяти есть указатели на него, он будет жить. Как только кол-во указателей достигнет нуля, он будет уничтожен.
Клонирование объектов
Есть некоторые проблемы с объектами, если один плагин делится с другим своим объектом. Простейший пример:- Плагин А создаёт некий объект.
- Плагин Б получает указатель на объект от плагина А.
- Плагин А выгружается из памяти, и все его указатели (включая переданный указатель в плагин Б) становятся недействительными.
- Плагин Б пытается использовать указатель.
Вообще, есть два способа клонирования.
- Когда плагин Б получает уже склонированный указатель на объект от плагина А.
Код:/** * В плагине А */ public Handle GetGlobalArray(Handle hOwner = null) { return CloneHandle(g_hArray, hOwner); // если на функцию передаётся null, то владельцем становится плагин, который клонирование и инициирует. } /** * В плагине Б */ Handle hArray = GetGlobalArray(GetMyHandle()); /* работаем с ним */ delete hArray;
- Когда плагин Б получает оригинальный указатель на объект от плагина А, и вручную клонирует.
Код:/** * В плагине А */ public Handle GetGlobalArray() { return g_hArray; } /** * В плагине Б */ Handle hArray = CloneHandle(GetGlobalArray()); // если второй аргумент не передаётся, то используется стандартное значение по умолчанию (т.е., null). /* работаем с ним */ delete hArray;
Типы объектов
Внизу представлены краткие описания стандартных типов объектов, представляемые самим SourceMod. Другие расширения могут создавать свои типы.BitBuffers
- Тип: bf_write или bf_read
- Закрываемый: Только если разрешено.
- Клонируемый: Только если его можно закрывать.
- API: Ядро.
- Упоминается в bitbuffer.inc
Консольные переменные
- Тип: ConVar
- Закрываемый: Нет.
- Клонируемый: Нет.
- API: Ядро.
- Упоминается в convars.inc
Пакеты данных (DataPack)
- Тип: DataPack
- Закрываемый: Да.
- Клонируемый: Да.
- API: Ядро.
- Упоминается в datapack.inc
Объекты пакетов данных могут создаваться расширениями.
Папки (Directories)
- Тип: Directory
- Закрываемый: Да.
- Клонируемый: Да.
- API: Ядро.
- Упоминается в files.inc
Драйвера Базы данных (Database Drivers)
- Тип: DBDriver
- Закрываемый: Да.
- Клонируемый: Нет.
- API: Ядро.
- Упоминается в dbi.inc
Запросы к Базе данных (Database Queries)
- Тип: IQuery / IPreparedQuery
- Закрываемый: Да.
- Клонируемый: Нет.
- API: Ядро.
- Упоминается в dbi.inc
Соединение с Базой данных (Databases)
- Тип: IDatabase
- Закрываемый: Да.
- Клонируемый: Да.
- API: Ядро.
- Упоминается в dbi.inc
События (Events)
- Тип: Event
- Закрываемый: Нет.
- Клонируемый: Нет.
- API: Ядро.
- Упоминается в events.inc
Файлы (Files)
- Тип: File
- Закрываемый: Да.
- Клонируемый: Да.
- API: Ядро.
- Упоминается в files.inc
Forwards
- Тип: Forward
- Закрываемый: Да.
- Клонируемый: Только если разрешено.
- API: Ядро.
- Упоминается в functions.inc
Структура Ключ-Значение (KeyValues)
- Тип: KeyValues
- Закрываемый: Да.
- Клонируемый: Нет.
- API: Ядро.
- Упоминается в keyvalues.inc
Плагины (Plugins)
- Тип: Plugin
- Закрываемый: Нет.
- Клонируемый: Нет.
- API: Ядро.
- Упоминается в sourcemod.inc
Указатели на плагины не должны храниться глобально в памяти плагина, т.к. плагин может быть выгружен, и его указатель станет недействительным.
Перечисления плагинов (Plugin Iterators)
- Тип: PluginIter
- Закрываемый: Да.
- Клонируемый: Нет.
- API: Ядро.
- Упоминается в sourcemod.inc
Protobuf
- Тип: protobuf
- Закрываемый: Нет.
- Клонируемый: Нет.
- API: Ядро.
- Упоминается в protobuf.inc
Синтаксические SMC-анализаторы (SMC Parsers)
- Тип: SMC Parser
- Закрываемый: Да.
- Клонируемый: Нет.
- API: Ядро.
- Упоминается в textparse.inc
Таймеры (Timers)
- Тип: Timer
- Закрываемый: Да.
- Клонируемый: Нет.
- API: Ядро.
- Упоминается в timers.inc
- Таймер закончил своё выполнение (через возвращение значения Plugin_Stop, или при единичном выполнении (если одноразовый));
- Таймер был убит с помощью KillTimer()
- Все указатели на объект таймера были уничтожены посредством конструкции языка delete или функции CloseHandle().
- Владелец объекта таймера был выгружен из памяти.