NtUtils Library

July 5, 2026 ยท View on GitHub

NtUtils is a framework for Windows system programming in Delphi that provides a set of functions with better error handling and language integration than regular Winapi/Ntapi headers, combined with frequently used code snippets and intelligent data types.

You can find some example code in a dedicated repository.

Dependencies

Library Structure

The library has a layered structure with three layers in total:

  • Headers layer defines data types and annotated function prototypes from Windows and Native API. It brings zero dependencies and contains almost no code. Note that the library is self-sufficient and doesn't import Winapi units that are included with Delphi. It's possible to mix the built-in Winapi.*.pas and library Ntapi.*.pas headers in your program; although, it might require explicitly specifying the namespace prefix in case of conflicting names.
  • NtUtils layer provides most of the functionality of the library by offering hundreds of wrappers for various categories of OS APIs. It depends exclusively on the headers layer and not even on System.SysUtils, so it barely increases the size of compiled executables.
  • NtUiLib layer adds support for reflective data representation meant for the end-users. It depends on NtUtils, System.SysUtils, System.Rtti, and System.Generics.Collections. Update: the library is currently transitioning to our custom lightweight RTTI/reflection engine (without any of above-mentioned dependencies like System.Rtti), so information on this layer is becoming outdated.

Therefore, everything you need is already included with the latest free version of Delphi. As a bonus, compiling console applications without RTTI (aka reflection) yields extremely small executables. See examples for more details.

Unit Auto-discovery

Since including every file from the library into your projects is usually redundant, you can configure Delphi for file auto-discovery. This way, you can specify a unit in the uses section, and Delphi will automatically include it and its dependencies into the project. To configure the folders where Delphi performs the search, go to Project -> Options -> Building -> Delphi Compiler and add the following lines into the Search Path:

.\NtUtilsLibrary
.\NtUtilsLibrary\Headers
.\NtUtilsLibrary\NtUiLib

If the folder names or locations are different for your project, you need to adjust these lines correspondingly.

Error Handling

TNtxStatus

The library indicates failures to the caller by returning unsuccessful TNtxStatus values. TNtxStatus (defined in NtUtils.pas) is a structure that stores an error code (compatible with NTSTATUS, HRESULT, and a Win32 Errors) plus metadata about the nature of the attempted operation, such as the location of the failure, a stacktrace, and other details like expected/requested access mask for open calls or the info class value for query/set calls. To check if TNtxStatus is successful, use its IsSuccess method. To access or set the underlying error code (depending on its type and the caller's preference) use properties such as Status, HResult, HResultAllowFalse, Win32Error, Win32ErrorOrSuccess, IsHResult, IsWin32, etc.

Exceptions

If you prefer using exceptions, you can always call RaiseOnError() on a given TNtxStatus. Note that unless you really want to use exceptions without importing System.SysUtils (which is possible), it's better to include NtUiLib.Exceptions that brings a dedicated ENtError exception class (derived from the built-in EOSError).

Error Representation

NtUiLib.Errors attaches four methods for representing TNtxStatus values as strings. For instance, if the error with value 0xC0000061 comes from an attempt to change a session ID of a token, these methods will return the following information:

MethodReturned string
NameSTATUS_PRIVILEGE_NOT_HELD
DescriptionA required privilege is not held by the client
SummaryPrivilege Not Held
ToStringNtSetInformationToken returned STATUS_PRIVILEGE_NOT_HELD

If you want to go even further and show a pretty message box to the user, NtUiLib.Errors.Dialog offers ShowNtxStatus(). Additionally, including NtUiLib.Exceptions.Dialog will bring necessary reflection support and enrich the dialog even further. Here is an example of how it might look:

An exception

Stack Traces & Debug Symbols

TNtxStatus supports capturing stack traces (disabled by default). To enable it, set NtUtils.CaptureStackTraces to True. Keep in mind that displaying stack traces in a meaningful way requires configuring generation of debug symbols for your executable. Unfortunately, Delphi can only output simple text-based .map files while this feature (and 3-rd party debuggers) require either .dbg (older format) or .pdb (newer format) symbols. To automatically generate .pdb files, do the following:

  1. Enable .map file generation in your project settings: Project -> Options -> Building -> Delphi Compiler -> Linking -> Map File -> "Detailed".
  2. Optionally, if you want to include line number information: Project -> Options -> Building -> Delphi Compiler -> Compiling -> Debugging -> Debug Information -> Select "Debug information". Note that for the change to take effect, you might need to delete existing .dcu files and then recompile your project from scratch.
  3. Download or build map2pdb and place its executable where Delphi can find it.
  4. Add the following post-build event in your project settings (Project -> Options -> Building -> Build Events -> Post-build events -> Commands):
cd $(OUTPUTDIR)
map2pdb.exe -bind:$(OUTPUTFILENAME) $(OUTPUTNAME).map
  1. Select execute conditions via Project -> Options -> Building -> Build Events -> Post-build events -> Execute when "Target is out of date".

The script will produce a .pdb with debug symbols and attach required metadata (a debug directory) to the executable every time you compile the project.

Automatic Lifetime Management

Delphi does not include a garbage collector, so only a few types are managed out-of-the-box: records, strings, dynamic arrays, and interfaces. Classes and record pointers, on the other hand, require explicit cleanup which (in its safe form) means using try-finally blocks and, therefore, complicates the program significantly. To address this issue, the library includes facilities for automatic lifetime management of memory and other resources, implemented in DelphiUtils.AutoObjects as dedicated wrappers:

graph LR;
  subgraph id1[Resources that rely on automatic cleanup]
    IAutoReleasable
  end
  subgraph id2[A THandle value]
    IHandle
  end
  subgraph id3[A Delphi class]
    IObject[IObject&ltT&gt]
  end
  subgraph id4[A memory pointer]
    IPointer[IPointer&ltP&gt]
  end
  subgraph id5[A memory region]
    IMemory[IMemory&ltP&gt]
  end
  subgraph id6[A deferred procedure]
    IDeferredOperation
  end
  IAutoReleasable --> IHandle;
  IAutoReleasable --> IObject;
  IAutoReleasable --> IPointer;
  IPointer --> IMemory;
  IAutoReleasable --> IDeferredOperation;

Classes and Records

The recipe for using automatic lifetime management is the following:

  1. Define every variable that needs to maintain (a potentially shared) ownership over a resource using one of the interfaces:

    • For classes, use IObject<TMyObject>.
    • For dynamic memory accessible via a pointer, use IMemory, also known as IMemory<Pointer>.
    • For (managed) boxed records, use IMemory<PMyRecord>.
  2. Use the Auto helper for allocating/copying/capturing objects:

    • Auto.CaptureObject<TMyObject>(...) to capture ownership of a class object derived from TObject.
    • Auto.AllocateDynamic(...) and Auto.CopyDynamic(...) to capture ownership over a new dynamic memory allocation.
    • Auto.Allocate<TMyRecord>(...) and Auto.Copy<TMyRecord>(...) to capture ownership over a new managed boxed record stored on the heap.
  3. Access the underlying resource by using the corresponding method/property on the wrapper:

    • Self for classes.
    • Data and Size for pointers to records.
  4. Do not manually call Destroy, Free, FreeMem, Finalize, or similar functions. It will happen automatically.

By wrapping resources requiring explicit cleanup into dedicated (genetic) interfaces, we instruct the compiler to generate thread- and exception-safe code for counting references and releasing the underlying resource whenever the wrapper goes out of scope.

For example, here is a safe code for working with a TStringList using the classical approach:

var
  x: TStringList;
begin
  x := TStringList.Create;
  try
    x.Add('Hi there');
    x.SaveToFile('test.txt');
  finally
    x.Free;
  end;
end;

As you can imagine, using more objects in this function can drastically increase its complexity due to nesting try-finally blocks or keeping track of which variables were initialized. Alternatively, here is the equivalent code that uses IObject and scales way better:

uses
  DelphiUtils.AutoObjects;
var
  x: IObject<TStringList>;
begin
  x := Auto.CaptureObject(TStringList.Create);
  x.Self.Add('Hi there');
  x.Self.SaveToFile('test.txt');
end; 

The compiler emits the necessary clean-up code into the function epilogue and makes sure it executes even if exceptions occur. Additionally, this approach allows maintaining shared ownership over the underlying object, which lets you save a reference that can outlive the current function (by capturing it in an anonymous function and returning it, for example). If you don't need this functionality and want to maintain single ownership that frees the object when the function exits, you can simplify the syntax even further:

uses
  NtUtils;
var
  x: TStringList;
begin
  x := Auto.CaptureObject(TStringList.Create).Self;
  x.Add('Hi there');
  x.SaveToFile('test.txt');
end; 

This code is still equivalent to the initial one. Internally, it creates a hidden local variable that stores the wrapper interface and later releases the object, even in face of exceptions.

When working with dynamic memory allocations, it can also be convenient to use left-side casting as following:

var
  x: IMemory<PByteArray>;
begin
  IMemory(x) := Auto.AllocateDynamic(100);
  x.Data[15] := 20;
end;

You can create boxed (allocated on the heap) managed records that allow sharing value types as if they are reference types. Note that they can also include managed fields like Delphi strings and dynamic arrays - the compiler emits code for releasing them automatically:

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;

While most library code uses memory managed by AllocMem/FreeMem, some units define compatible implementations that rely on RtlFreeHeap, LocalFree, and other similar functions. To create a non-owning wrapper, you can use Auto.RefObject<TMyObject>(...), Auto.RefAddress(...), Auto.RefAddressRange(...), or Auto.RefBuffer<TMyRecord>(...);

There are some aliases available for commonly used variable-size pointer types, here are a few examples:

  • IPointer = IPointer<Pointer>;
  • IMemory = IMemory<Pointer>;
  • ISid = IPointer<PSid>;
  • IAcl = IPointer<PAcl>;
  • ISecurityDescriptor = IPointer<PSecurityDescriptor>;
  • etc.

Handles

Resources identified by a THandle value use the IHandle type (see DelphiUtils.AutoObjects), so they do not require explicit closing. Since handles come in many different flavors, to take ownership of a handle, you need a class that implement cleanup for the specific type. For example, NtUtils.Objects defines such class for kernel objects that require calling NtClose. It also attaches a helper method to Auto, allowing capturing kernel handles by value via Auto.CaptureHandle(...). NtUtils.Svc includes a class for making IHandle wrappers that call CloseServiceHandle; NtUtils.Lsa's wrapper calls LsaClose, etc. All of these classes implement IHandle, but you can also find some aliases for it (IScmHandle, ISamHandle, ILsaHandle, etc.), which are available for better code readability. To create a non-owning IHandle, use Auto.RefHandle(...).

(Weak) Interfaces

Since Delphi uses reference counting, it is still possible to leak memory if two objects have a circular dependency. You can prevent it from happening by using weak references. Such reference does not count for prolonging lifetime, and the variable that stores them becomes automatically becomes nil when the target object gets destroyed. Delphi already allows annotating variables and fields with the [Weak] attribute, however, the Weak<I> wrapper offers some advantages such as guaranteeing thread-safe upgrades for all objects derived from the new TAutoInterfacedObject class. See DelphiUtils.AutoObjects for more details about this type.

If you want to use weak references with classes derived from the built-in TInterfacedObject, you can wrap them into IStrong<I> first via Auto.RefStrong(...) to guarantee thread-safety.

Deferred Functions

Another feature available via the Auto helper is deferred function calls. It allows executing an anonymous function upon an interface variable cleanup and is most helpful for queueing operations to function epilogues. Here is an example:

procedure MyProcedure;
begin
  writeln('Entering...');

  Auto.Defer(
    procedure
    begin
      writeln('Exiting...');
    end;
  );

  writeln('About to fail...');
  raise Exception.Create('Error Message');
  writeln('Never reached...');
end;

This code will print the following messages (while also throwing an exception):

Entering...
About to fail...
Exiting...

Deferring a call is equivalent to wrapping the entire function body into try-finally and adding the callback code into the finally statement. However, defers are more flexible because they are possible to store as variables and cancel (via the Cancel method on the returned IDeferredOperation). Effectively, a deferred operation is an anonymous function that automatically executes upon its release.

Note that when using multiple defers in a single scope, it is better to save each result of Auto.Defer(...) to a dedicated local variable since the compiler might attempt to reuse hidden variables (and, thus, execute defers too early). The same applies to other automatic wrappers as well.

Automatic Events

The library provides facilities for automatic multi-callback event delivery. As opposed to the built-in System.Classes.TNotifyEvent that only allows a single subscriber that must be an object method and cannot have extra parameters, DelphiUtils.AutoEvents defines TAutoEvent and TAutoEvent<T> which can accept any number of anonymous function subscribers (so both functions and methods work) and can capture local variables. The library also reworks event lifetime model. Whenever somebody subscribes to an event, they receive an IAutoReleasable-typed registration object. The subscription remains active for as long as this object exists; releasing it unsubscribes the caller from the callback (and cleans up captured resources).

Keep in mind that, by default, if any of callbacks throw exceptions upon invocation, they might prevent other subscribers from executing. To handle exceptions during event delivery, you can provide AutoExceptionHanlder in DelphiUtils.AutoObjects.

Naming Convention

Names of records, classes, and enumerations start with T and use CamelCase (example: TTokenStatistics). Pointers to records or other value-types start with P (example: PTokenStatistics). Names of interfaces start with I (example: ISid). Constants use ALL_CAPITALS. All definitions from the headers layer that have known official names (such as the types defined in Windows SDK) are marked with an SDKName attribute specifying this name.

Most functions use the following name convention: a prefix of the subsystem with x at the end (Ntx, Ldrx, Lsax, Samx, Scmx, Wsx, Usrx, ...) + Action + Target/Object type/etc. Function names also use CamelCase.

OS Versions

The library targets Windows 7 or higher, both 32- and 64-bit editions. Though, some of the functionality might be available only on the latest 64-bit versions of Windows 11. Some examples are AppContainers and ntdll syscall unhooking. If a library function depends on an API that might not present on Windows 7, it uses delayed import and checks availability at runtime. Search for the use of LdrxCheckDelayedImport for more details.

Reflection (aka RTTI)

Delphi comes with a rich reflection system that the library utilizes within the NtUiLib layer. Most of the types defined in the Headers layer are decorated with custom attributes (see DelphiApi.Reflection) to achieve it. These decorations emit useful metadata that helps the library to precisely represent complex data types (like PEB, TEB, USER_SHARED_DATA) in runtime and produce astonishing reports with a single line of code.

Here is an example representation of TSecurityLogonSessionData from Ntapi.NtSecApi using DelphiUiLib.LiteReflection.Types:

RTTI-based report

Unit overview

Here the overview of the purpose of different modules.

Base library modules

Support unitDescription
DelphiUtils.AutoObjectsAutomatic resource lifetime management
DelphiUtils.AutoEventsMulti-subscriber anonymous events
DelphiUtils.ArraysTArray helpers
DelphiUtils.ListsA genetic double-linked list primitive
DelphiUtils.ExternalImportDelphi external keyword IAT helpers
DelphiUtils.RangeChecksRange checking helpers
NtUtilsCommon library types
NtUtils.Files.AsyncAnonymous APC support for async I/O
NtUtils.SysUtilsString manipulation
NtUtils.ErrorsError code conversion
NtUiLib.ErrorsError code name lookup
NtUiLib.ExceptionsSysUtils exception integration
DelphiUiLib.StringsString formatting for UI purposes
DelphiUtils.LiteRTTI.BaseBase TypeInfo parsing for lite RTTI
DelphiUtils.LiteRTTILite RTTI and custom attribute parsing
DelphiUiLib.LiteReflectionType representation via lite RTTI
DelphiUiLib.LiteReflection.TypesLite RTTI formatting for some known types
DelphiUiLib.HysteresisTreeData structure for tracking changes in lists/trees
NtUiLib.ConsoleConsole UI helpers
NtUiLib.TaskDialogTaskDialog-based GUI
NtUiLib.Errors.DialogGUI error dialog
NtUiLib.Exceptions.DialogGUI exception dialog

System API wrappers

System unitDescription
NtUtils.ActCtxActivation contexts
NtUtils.AntiHookingUnhooking and direct syscall
NtUtils.ApiSetsAPI Set Map parsing
NtUtils.BrokerInfraBackground Broker Infrastructure
NtUtils.ComCOM, IDispatch, WinRT
NtUtils.ConsoleLow-level console support
NtUtils.CsrCSRSS/SxS registration
NtUtils.DbgHelpDbgHelp and debug symbols
NtUtils.DbgHelp.DiaPDB parsing via MSDIA
NtUtils.DebugDebug objects
NtUtils.DismDISM API
NtUtils.EnvironmentEnvironment variables
NtUtils.Environment.UserUser environment variables
NtUtils.Environment.RemoteEnvironment variables of other processes
NtUtils.FilesWin32/NT filenames
NtUtils.Files.OpenFile and pipe open/create
NtUtils.Files.OperationsFile operations
NtUtils.Files.DirectoriesFile directory enumeration
NtUtils.Files.FltMgrFilter Manager API
NtUtils.Files.MupMultiple UNC Provider
NtUtils.Files.VolumesVolume operations
NtUtils.Files.ControlFSCTL operations
NtUtils.ImageHlpPE parsing
NtUtils.ImageHlp.SyscallsSyscall number retrieval
NtUtils.ImageHlp.DbgHelpPublic symbols without DbgHelp
NtUtils.JobsJob objects and silos
NtUtils.Jobs.RemoteCross-process job object queries
NtUtils.LdrLDR routines and parsing
NtUtils.LpcAdvanced Local Procedure Calls
NtUtils.LsaLSA policy
NtUtils.Lsa.AuditAudit policy
NtUtils.Lsa.SidSID lookup
NtUtils.Lsa.LogonLogon sessions
NtUtils.ManifestsFusion/SxS manifest builder
NtUtils.MemoryMemory operations
NtUtils.MiniDumpsMinidump format parsing
NtUtils.NtUserWin32k syscalls
NtUtils.NtUser.OleaccOLE accessibility hooks
NtUtils.ObjectsKernel objects and handles
NtUtils.Objects.SnapshotsHandle snapshotting
NtUtils.Objects.NamespaceNT object namespace
NtUtils.Objects.RemoteCross-process handle operations
NtUtils.Objects.CompareHandle comparison
NtUtils.PackagesApp packages & package families
NtUtils.Packages.ExecAliasApp execution aliases
NtUtils.Packages.MrmPackaged resources (.pri)
NtUtils.Packages.SRCacheState repository cache
NtUtils.Packages.WinRTWinRT-based package info
NtUtils.PowerPower-related functions
NtUtils.ProcessesProcess objects
NtUtils.Processes.InfoProcess query/set info
NtUtils.Processes.Info.RemoteProcess query/set via code injection
NtUtils.Processes.ModulesCross-process LDR enumeration
NtUtils.Processes.SnapshotsProcess enumeration
NtUtils.Processes.CreateCommon process creation definitions
NtUtils.Processes.Create.Win32Win32 process creation methods
NtUtils.Processes.Create.ShellShell process creation methods
NtUtils.Processes.Create.WerWindows Error Reporting process creation methods
NtUtils.Processes.Create.NativeNtCreateUserProcess and co.
NtUtils.Processes.Create.ManualNtCreateProcessEx
NtUtils.Processes.Create.ComCOM-based process creation
NtUtils.Processes.Create.CsrProcess creation via SbApiPort
NtUtils.Processes.Create.PackageAppx activation
NtUtils.Processes.Create.RemoteProcess creation via code injection
NtUtils.Processes.Create.CloneProcess cloning
NtUtils.ProfilesUser profiles
NtUtils.Profiles.AppContainerAppContainer profiles
NtUtils.RegistryRegistry keys
NtUtils.Registry.OfflineOffline hive manipulation
NtUtils.Registry.VRegSilo-based registry virtualization
NtUtils.SamSAM database
NtUtils.SectionsSection/memory projection objects
NtUtils.SecuritySecurity descriptors
NtUtils.Security.AclACLs and ACEs
NtUtils.Security.SidSIDs
NtUtils.Security.AppContainerAppContainer & capability SIDs
NtUtils.ShellcodeCode injection
NtUtils.Shellcode.DllDLL injection
NtUtils.Shellcode.ExeEXE injection
NtUtils.SQLiteSQLite3 databases
NtUtils.SvcSCM services
NtUtils.Svc.SingleTaskSvcService implementation
NtUtils.SynchronizationSynchronization primitives
NtUtils.SystemSystem information
NtUtils.TaskSchedulerTask scheduler
NtUtils.ThreadsThread objects
NtUtils.Tokens.InfoThread query/set info
NtUtils.Threads.WorkerThread workers (thread pools)
NtUtils.TokensToken objects
NtUtils.Tokens.ImpersonateToken impersonation
NtUtils.Tokens.LogonUser & S4U logon
NtUtils.Tokens.AppModelToken AppModel policy
NtUtils.TransactionsTransaction (TmTx) objects
NtUtils.Transactions.RemoteForcing processes into transactions
NtUtils.UserManagerUser Manager service (Umgr) API
NtUtils.VirtDiskVirtual disk (*.iso, *.vhdx) API
NtUtils.WerWindows Error Reporting APIs
NtUtils.WimWindows Imaging (*.wim) API
NtUtils.WinSaferSafer API
NtUtils.WinStationTerminal server API
NtUtils.WinUserUser32/GUI API
NtUtils.WinUser.WindowAffinityWindow affinity modification
NtUtils.WinUser.WinstaLockLocking & unlocking window stations
NtUtils.WnfWindows Notification Facility (WNF)
NtUtils.XmlLiteXML parsing & crafting via XmlLite
NtUiLib.AutoCompletionAuto-completion for edit controls
NtUiLib.AutoCompletion.NamespaceNT object namespace auto-completion
NtUiLib.AutoCompletion.SidSID auto-completion
NtUiLib.AutoCompletion.Sid.CommonSimple SID name providers/recognizers
NtUiLib.AutoCompletion.Sid.AppContainerAppContainer & package SID providers/recognizers
NtUiLib.AutoCompletion.Sid.CapabilitiesCapability SID providers/recognizers
NtUiLib.AutoCompletion.WnfKnown WNF state name descriptions
NtUiLib.WinCredCredentials dialog