NtUtils — это платформа для системного программирования Windows на Delphi, которая предоставляет набор функций с лучшей обработкой ошибок и языковой интеграцией, чем обычные заголовки Winapi/Ntapi, в сочетании с часто используемыми фрагментами кода и интеллектуальными типами данных.
Примеры кода вы можете найти в специальном репозитории .
Библиотека имеет многоуровневую структуру, состоящую всего из трех слоев:
Winapi.*.pas
и библиотеку Ntapi.*.pas
; хотя в случае конфликта имен может потребоваться явное указание префикса пространства имен.System.SysUtils
, System.Rtti
и System.Generics.Collections
.Таким образом, все необходимое уже включено в последнюю бесплатную версию Delphi. В качестве бонуса компиляция консольных приложений без RTTI (так называемого отражения) дает очень маленькие исполняемые файлы. Более подробную информацию смотрите в примерах.
Поскольку включение каждого файла из библиотеки в ваши проекты обычно избыточно, вы можете настроить Delphi для автоматического обнаружения файлов. Таким образом, вы можете указать модуль в разделе uses
, и Delphi автоматически включит его и его зависимости в проект. Чтобы настроить папки, в которых Delphi выполняет поиск, перейдите в Project -> Options -> Building -> Delphi Compiler и добавьте следующие строки в путь поиска:
.NtUtilsLibrary
.NtUtilsLibraryHeaders
.NtUtilsLibraryNtUiLib
Если имена или местоположения папок в вашем проекте отличаются, вам необходимо соответствующим образом настроить эти строки.
Библиотека сообщает об ошибках вызывающей стороне, возвращая неудачные значения TNtxStatus . TNtxStatus
(определенный в NtUtils.pas) — это структура, в которой хранится код ошибки (совместимый с NTSTATUS
, HRESULT
и ошибками Win32), а также метаданные о характере предпринятой операции, такие как место сбоя, трассировка стека и другие сведения, такие как ожидаемая/запрошенная маска доступа для открытых вызовов или значение информационного класса для вызовов запроса/установки. Чтобы проверить успешность TNtxStatus
, используйте его метод IsSuccess
. Чтобы получить доступ к базовому коду ошибки или установить его (в зависимости от его типа и предпочтений вызывающего абонента), используйте такие свойства, как Status
, HResult
, HResultAllowFalse
, Win32Error
, Win32ErrorOrSuccess
, IsHResult
, IsWin32
и т. д.
Если вы предпочитаете использовать исключения, вы всегда можете вызвать RaiseOnError()
для заданного TNtxStatus
. Обратите внимание: если вы действительно не хотите использовать исключения без импорта System.SysUtils
(что возможно), лучше включить NtUiLib.Exceptions, который создает специальный класс исключений ENtError
(полученный из встроенного EOSError
).
NtUiLib.Errors добавляет четыре метода для представления значений TNtxStatus
в виде строк. Например, если ошибка со значением 0xC0000061
возникает из-за попытки изменить идентификатор сеанса токена, эти методы вернут следующую информацию:
Метод | Возвращенная строка |
---|---|
Name | STATUS_PRIVILEGE_NOT_HELD |
Description | A required privilege is not held by the client |
Summary | Privilege Not Held |
ToString | NtSetInformationToken returned STATUS_PRIVILEGE_NOT_HELD |
Если вы хотите пойти еще дальше и показать пользователю красивое окно сообщения, NtUiLib.Errors.Dialog предлагает ShowNtxStatus()
. Кроме того, включение NtUiLib.Exceptions.Dialog обеспечит необходимую поддержку отражения и еще больше обогатит диалог. Вот пример того, как это может выглядеть:
TNtxStatus
поддерживает захват трассировки стека (по умолчанию отключено). Чтобы включить его, установите для NtUtils.CaptureStackTraces
значение True. Имейте в виду, что для полноценного отображения трассировок стека необходимо настроить генерацию символов отладки для вашего исполняемого файла. К сожалению, Delphi может выводить только файлы .map
(настраиваемые через Project -> Options -> Building -> Delphi Compiler -> Linking -> Map File), которых обычно недостаточно. Вам понадобится сторонний инструмент Map2dbg для преобразования их в файлы .dbg
, чтобы API символов мог их понимать. Хотя файлов .dbg
может быть достаточно, лучше обработать их еще больше, преобразовав в современный .pdb
с помощью cv2pdb .
Чтобы автоматически генерировать символы отладки, добавьте в проект следующие события после сборки:
map2dbg.exe $(OUTPUTPATH)
cv2pdb64.exe -n -s. -p$(OUTPUTNAME).pdb $(OUTPUTPATH)
В Delphi нет сборщика мусора, поэтому по умолчанию можно управлять только несколькими типами: записи, строки, динамические массивы и интерфейсы. С другой стороны, классы и указатели требуют явной очистки, которая (в безопасной форме) требует использования блоков try-finally и, следовательно, значительно усложняет программу. Для решения этой проблемы в библиотеку включены средства автоматического управления сроком службы памяти и других ресурсов, реализованные в DelphiUtils.AutoObjects. Используя типы из этого модуля, мы указываем компилятору автоматически генерировать код, безопасный для исключений, для подсчета ссылок и автоматического освобождения объектов в эпилогах функций. Этот модуль определяет несколько интерфейсов для различных типов ресурсов, которые могут потребовать очистки. Он вводит следующую иерархию:
график ЛР;
подграф id1[Любой ресурс]
IAutoReleasable
конец
подграф id2 [значение THandle]
IHandle
конец
подграф id3 [класс Delphi]
IAutoObject[IAutoObject<T>]
конец
подграф id4 [указатель]
IAutoPointer[IAutoPointer<P>]
конец
подграф id5[Область памяти]
IMemory[IMemory<P>]
конец
IAutoReleasable --> IHandle;
IAutoReleasable --> IAutoObject;
IAutoReleasable --> IAutoPointer;
IAutoPointer --> IMemory;
IAutoReleasable
— это базовый тип для всех ресурсов, требующих выполнения действий по (автоматической) очистке. IHandle
служит оболочкой для ресурсов, определенных значением THandle. IAutoObject<T>
— это универсальная оболочка для автоматического выпуска классов Delphi (т. е. всего, что происходит от TObject). IAutoPointer<P>
определяет аналогичный интерфейс для освобождения динамически выделяемых указателей (где размер области не имеет значения). IMemory<P>
предоставляет оболочку для областей памяти известных размеров, доступ к которым можно получить через типизированный указатель, например управляемые и неуправляемые упакованные записи.
Рецепт использования этого средства следующий:
Определите каждую переменную, которая должна поддерживать (потенциально совместное) владение объектом, используя один из интерфейсов:
Используйте автоматический помощник для выделения/копирования/захвата автоматических объектов:
При необходимости используйте левостороннее приведение, которое поможет избежать дублирования информации о типе и сократить синтаксис.
Например, вот безопасный код для работы с TStringList с использованием классического подхода:
var
x: TStringList;
begin
x := TStringList.Create;
try
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
finally
x.Free;
end ;
end ;
Как вы понимаете, использование большего количества объектов в этой функции значительно и нелинейно увеличит ее сложность. В качестве альтернативы, вот эквивалентный код, который использует IAutoObject и значительно лучше масштабируется:
uses
DelphiUtils.AutoObjects;
var
x: IAutoObject<TStringList>;
begin
x := Auto.From(TStringList.Create);
x.Self.Add( ' Hi there ' );
x.Self.SaveToFile( ' test.txt ' );
end ;
Компилятор генерирует необходимый код очистки в эпилоге функции и обеспечивает ее выполнение даже в случае возникновения исключений. Кроме того, этот подход позволяет сохранять совместное владение базовым объектом, что позволяет сохранять ссылку, которая может пережить текущую функцию (например, зафиксировав ее в анонимной функции и вернув ее). Если вам не нужна эта функция и вы хотите сохранить одного владельца, который освобождает объект при выходе из функции, вы можете еще больше упростить синтаксис:
uses
NtUtils;
var
x: TStringList;
begin
x := Auto.From(TStringList.Create).Self;
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
end ;
Этот код по-прежнему эквивалентен исходному. Внутри он создает скрытую локальную переменную, в которой хранится интерфейс, а затем освобождается объект.
При работе с динамическим распределением памяти может быть удобно использовать левостороннее приведение следующим образом:
var
x: IMemory<PByteArray>;
begin
IMemory(x) := Auto.AllocateDynamic( 100 );
x.Data[ 15 ] := 20 ;
end ;
Вы также можете создавать упакованные (выделенные в куче) управляемые записи, которые позволяют совместно использовать типы значений, как если бы они были ссылочными типами. Обратите внимание, что они также могут включать управляемые поля, такие как строки Delphi и динамические массивы — компилятор генерирует код для их автоматического освобождения:
type
TMyRecord = record
MyInteger: Integer;
MyArray: TArray<Integer>;
end ;
PMyRecord = ^TMyRecord;
var
x: IMemory<PMyRecord>;
begin
IMemory(x) := Auto.Allocate<TMyRecord>;
x.Data.MyInteger := 42 ;
x.Data.MyArray := [ 1 , 2 , 3 ];
end ;
Поскольку Delphi использует подсчет ссылок, утечка памяти все равно возможна, если два объекта имеют циклическую зависимость. Вы можете предотвратить это, используя слабые ссылки . Такая ссылка не учитывается при продлении срока службы, и переменная, в которой они хранятся, автоматически становится нулевой, когда целевой объект уничтожается. Вам необходимо обновить слабую ссылку до сильной, прежде чем вы сможете ее использовать. Дополнительные сведения см. в разделе Weak<I> из DelphiUtils.AutoObjects.
Существует несколько псевдонимов для часто используемых типов указателей переменного размера, вот несколько примеров:
Дескрипторы используют тип IHandle (см. DelphiUtils.AutoObjects), который следует описанной выше логике, поэтому они не требуют явного закрытия. Вы также можете найти некоторые псевдонимы для IHandle (IScmHandle, ISamHandle, ILsaHandle и т. д.), которые доступны только для удобства чтения кода.
Если вам когда-нибудь понадобится стать владельцем значения дескриптора в IHandle, вам понадобится класс, который реализует этот интерфейс и знает, как освободить базовый ресурс. Например, NtUtils.Objects определяет такой класс для объектов ядра, требующих вызова NtClose
. Он также присоединяет к Auto
вспомогательный метод, позволяющий захватывать дескрипторы ядра по значению через Auto.CaptureHandle(...)
. Чтобы создать IHandle, не являющийся владельцем, используйте Auto.RefHandle(...)
.
Имена записей, классов и перечислений начинаются с T
и используют CamelCase (пример: TTokenStatistics
). Указатели на записи или другие типы значений начинаются с P
(пример: PTokenStatistics
). Имена интерфейсов начинаются с I
(пример: ISid
). Константы используют ALL_CAPITALS. Все определения уровня заголовков, имеющие известные официальные имена (например, типы, определенные в Windows SDK), помечаются атрибутом SDKName
указывающим это имя.
Большинство функций используют следующее соглашение об именах: префикс подсистемы с x в конце (Ntx, Ldrx, Lsax, Samx, Scmx, Wsx, Usrx, ...) + Действие + Цель/Тип объекта/и т. д. В именах функций также используется CamelCase.
Библиотека предназначена для Windows 7 или выше, как 32-, так и 64-разрядной версии. Однако некоторые функции могут быть доступны только в последних 64-разрядных версиях Windows 11. Некоторые примеры — это AppContainers и отключение системного вызова ntdll. Если библиотечная функция зависит от API, который может отсутствовать в Windows 7, она использует отложенный импорт и проверяет доступность во время выполнения.
Delphi поставляется с богатой системой отражения, которую библиотека использует на уровне NtUiLib . Для достижения этой цели большинство типов, определенных на уровне заголовков , декорируются настраиваемыми атрибутами (см. DelphiApi.Reflection). Эти украшения создают полезные метаданные, которые помогают библиотеке точно представлять сложные типы данных (например, PEB, TEB, USER_SHARED_DATA) во время выполнения и создавать потрясающие отчеты с помощью одной строки кода.
Вот пример представления TSecurityLogonSessionData
из Ntapi.NtSecApi с использованием NtUiLib.Reflection.Types:
Здесь обзор назначения различных модулей.
Группа поддержки | Описание |
---|---|
DelphiUtils.AutoObjects | Автоматическое управление сроком службы ресурсов |
DelphiUtils.AutoEvents | Анонимные события с несколькими подписчиками |
DelphiUtils.Arrays | Помощники TArray |
DelphiUtils.Списки | Генетический примитив двусвязного списка |
DelphiUtils.Async | Определения поддержки асинхронного ввода-вывода |
DelphiUtils.ExternalImport | Помощники IAT по внешним ключевым словам Delphi |
DelphiUtils.RangeChecks | Помощники по проверке диапазона |
НтУтилс | Общие типы библиотек |
NtUtils.SysUtils | Манипулирование строками |
NtUtils.Ошибки | Преобразование кода ошибки |
NtUiLib.Ошибки | Поиск имени кода ошибки |
NtUiLib.Исключения | Интеграция исключений SysUtils |
DelphiUiLib.Strings | Уточнение строк |
DelphiUiLib.Reflection | Базовая поддержка RTTI |
DelphiUiLib.Reflection.Numeric | RTTI-представление числовых типов |
DelphiUiLib.Reflection.Records | RTTI-представление типов записей |
DelphiUiLib.Reflection.Strings | RTTI украшение струн |
NtUiLib.Reflection.Types | Представление RTTI для распространенных типов |
НтУилиб.Консоль | Помощники консольного ввода-вывода |
Нтуилиб.Таскдиалог | Графический интерфейс на основе TaskDialog |
NtUiLib.Errors.Dialog | Диалоговое окно с ошибкой графического интерфейса |
NtUiLib.Exceptions.Dialog | Диалог исключений графического интерфейса |
Системный блок | Описание |
---|---|
НтУтилс.ActCtx | Контексты активации |
NtUtils.AntiHooking | Отсоединение и прямой системный вызов |
NtUtils.Com | COM, IDispatch, WinRT |
NtUtils.Csr | Регистрация CSRSS/SxS |
NtUtils.DbgHelp | DbgHelp и символы отладки |
NtUtils.Debug | Отладка объектов |
NtUtils.Dism | ДИСМ API |
NtUtils.Среда | Переменные среды |
NtUtils.Environment.User | Переменные пользовательской среды |
NtUtils.Environment.Remote | Переменные среды других процессов |
NtUtils.Files | Имена файлов Win32/NT |
NtUtils.Files.Open | Открытие/создание файла и канала |
NtUtils.Files.Operations | Операции с файлами |
NtUtils.Files.Directory | Перечисление каталогов файлов |
NtUtils.Files.FltMgr | API менеджера фильтров |
NtUtils.Files.Mup | Несколько поставщиков UNC |
NtUtils.Files.Volumes | Операции с объемами |
NtUtils.Files.Control | операции ФСКТЛ |
NtUtils.ImageHlp | PE-парсинг |
NtUtils.ImageHlp.Системные вызовы | Получение номера системного вызова |
NtUtils.ImageHlp.DbgHelp | Публичные символы без DbgHelp |
NtUtils.Работы | Объекты заданий и бункеры |
NtUtils.Jobs.Remote | Межпроцессные запросы объектов заданий |
NtUtils.Ldr | LDR-процедуры и синтаксический анализ |
NtUtils.Lsa | Политика АЛП |
NtUtils.Lsa.Audit | Политика аудита |
NtUtils.Lsa.Sid | Поиск SID |
NtUtils.Lsa.Logon | Сеансы входа в систему |
NtUtils.Манифесты | Построитель манифестов Fusion/SxS |
NtUtils.Память | Операции с памятью |
NtUtils.MiniDumps | Разбор формата минидампа |
NtUtils.Объекты | Объекты ядра и дескрипторы |
NtUtils.Objects.Снимки | Обрабатывать моментальные снимки |
NtUtils.Objects.Пространство имен | Пространство имен объектов NT |
NtUtils.Objects.Remote | Межпроцессные операции обработки |
NtUtils.Objects.Compare | Обработка сравнения |
NtUtils.Пакеты | Пакеты приложений и семейства пакетов |
NtUtils.Packages.SRCache | Кэш государственного репозитория |
NtUtils.Packages.WinRT | Информация о пакете на основе WinRT |
NtUtils.Power | Функции, связанные с питанием |
NtUtils.Процессы | Объекты процесса |
NtUtils.Processes.Info | Обработать запрос/установить информацию |
NtUtils.Processes.Info.Remote | Обработка запроса/установки посредством внедрения кода |
NtUtils.Processes.Modules | Межпроцессное перечисление LDR |
NtUtils.Processes.Snapshots | Перечисление процессов |
NtUtils.Processes.Create | Общие определения создания процессов |
NtUtils.Processes.Create.Win32 | Методы создания процессов Win32 |
NtUtils.Processes.Create.Shell | Методы создания процессов оболочки |
NtUtils.Processes.Create.Native | NtCreateUserProcess и компания. |
NtUtils.Processes.Create.Manual | Нткреатепроцессекс |
NtUtils.Processes.Create.Com | Создание процессов на основе COM |
NtUtils.Processes.Create.Csr | Создание процесса через SbApiPort |
NtUtils.Processes.Create.Package | Активация приложения |
NtUtils.Processes.Create.Remote | Создание процесса посредством внедрения кода |
NtUtils.Processes.Create.Clone | Клонирование процессов |
NtUtils.Профили | Профили пользователей и AppContainer |
NtUtils.Реестр | Ключи реестра |
NtUtils.Registry.Offline | Манипулирование ульем в автономном режиме |
NtUtils.Registry.VReg | Разрозненная виртуализация реестра |
NtUtils.Сэм | база данных ЗРК |
NtUtils.Sections | Объекты проекции раздела/памяти |
NtUtils.Security | Дескрипторы безопасности |
NtUtils.Security.Acl | ACL и ACE |
NtUtils.Security.Sid | SID |
NtUtils.Security.AppContainer | AppContainer и SID возможностей |
NtUtils.Шеллкод | Внедрение кода |
NtUtils.Shellcode.Dll | DLL-инъекция |
NtUtils.Shellcode.Exe | EXE-инъекция |
NtUtils.Svc | СКМ-услуги |
NtUtils.Svc.SingleTaskSvc | Реализация услуги |
NtUtils.Синхронизация | Примитивы синхронизации |
NtUtils.System | Информация о системе |
NtUtils.TaskScheduler | Планировщик задач |
NtUtils.Threads | Потоковые объекты |
NtUtils.Tokens.Info | Информация о запросе/установке потока |
NtUtils.Threads.Worker | Обработчики потоков (пулы потоков) |
NtUtils.Токены | Объекты-токены |
NtUtils.Tokens.Impersonate | Олицетворение токена |
NtUtils.Tokens.Logon | Вход пользователя и S4U |
NtUtils.Tokens.AppModel | Политика токена AppModel |
NtUtils.Транзакции | Объекты транзакции (TmTx) |
NtUtils.Transactions.Remote | Принуждение процессов к транзакциям |
NtUtils.UserManager | API службы диспетчера пользователей (Umgr) |
NtUtils.Wim | API образов Windows (*.wim) |
NtUtils.WinSafer | Более безопасный API |
NtUtils.WinStation | API терминального сервера |
NtUtils.WinUser | User32/GUI API |
NtUtils.WinUser.WindowAffinity | Изменение привязки окон |
NtUtils.WinUser.WinstaLock | Блокировка и разблокировка оконных станций |
НтУтилс.XmlLite | Анализ и обработка XML с помощью XmlLite |
NtUiLib.Автозаполнение | Автозаполнение для элементов управления редактированием |
NtUiLib.AutoCompletion.Пространство имен | Автозаполнение пространства имен объектов NT |
NtUiLib.AutoCompletion.Sid | SID автозаполнение |
NtUiLib.AutoCompletion.Sid.Common | Простые поставщики/распознаватели имен SID |
NtUiLib.AutoCompletion.Sid.AppContainer | AppContainer и поставщики/распознаватели SID пакетов |
NtUiLib.AutoCompletion.Sid.Capabilities | Возможности поставщиков/распознавателей SID |
Нтуилиб.ВинКред | Диалоговое окно «Учетные данные» |