NtUtils ist ein Framework für die Windows-Systemprogrammierung in Delphi, das eine Reihe von Funktionen mit besserer Fehlerbehandlung und Sprachintegration als normale Winapi/Ntapi-Header, kombiniert mit häufig verwendeten Codefragmenten und intelligenten Datentypen, bietet.
Einige Beispielcodes finden Sie in einem speziellen Repository .
Die Bibliothek hat einen Schichtenaufbau mit insgesamt drei Schichten:
Winapi.*.pas
und Bibliotheks- Ntapi.*.pas
Header in Ihrem Programm zu mischen; Allerdings kann es bei Namenskonflikten erforderlich sein, das Namespace-Präfix explizit anzugeben.System.SysUtils
, System.Rtti
und System.Generics.Collections
ab.Daher ist alles, was Sie brauchen, bereits in der neuesten kostenlosen Version von Delphi enthalten. Als Bonus führt das Kompilieren von Konsolenanwendungen ohne RTTI (auch Reflection genannt) zu extrem kleinen ausführbaren Dateien. Weitere Einzelheiten finden Sie in den Beispielen.
Da das Einbinden jeder Datei aus der Bibliothek in Ihre Projekte normalerweise überflüssig ist, können Sie Delphi für die automatische Dateierkennung konfigurieren. Auf diese Weise können Sie eine Einheit im Abschnitt uses
angeben, und Delphi wird sie und ihre Abhängigkeiten automatisch in das Projekt einbeziehen. Um die Ordner zu konfigurieren, in denen Delphi die Suche durchführt, gehen Sie zu Projekt -> Optionen -> Erstellen -> Delphi-Compiler und fügen Sie die folgenden Zeilen zum Suchpfad hinzu:
.NtUtilsLibrary
.NtUtilsLibraryHeaders
.NtUtilsLibraryNtUiLib
Wenn die Ordnernamen oder Speicherorte für Ihr Projekt unterschiedlich sind, müssen Sie diese Zeilen entsprechend anpassen.
Die Bibliothek zeigt dem Aufrufer Fehler an, indem sie nicht erfolgreiche TNtxStatus -Werte zurückgibt. TNtxStatus
(definiert in NtUtils.pas) ist eine Struktur, die einen Fehlercode (kompatibel mit NTSTATUS
, HRESULT
und Win32-Fehlern) sowie Metadaten über die Art des versuchten Vorgangs speichert, z. B. den Ort des Fehlers, einen Stacktrace usw andere Details wie erwartete/angeforderte Zugriffsmaske für offene Aufrufe oder der Infoklassenwert für Abfrage-/Set-Aufrufe. Um zu überprüfen, ob TNtxStatus
erfolgreich ist, verwenden Sie die IsSuccess
-Methode. Um auf den zugrunde liegenden Fehlercode zuzugreifen oder ihn festzulegen (je nach Typ und Präferenz des Aufrufers), verwenden Sie Eigenschaften wie Status
, HResult
, HResultAllowFalse
, Win32Error
, Win32ErrorOrSuccess
, IsHResult
, IsWin32
usw.
Wenn Sie Ausnahmen bevorzugen, können Sie jederzeit RaiseOnError()
für einen bestimmten TNtxStatus
aufrufen. Beachten Sie, dass es besser ist, NtUiLib.Exceptions einzuschließen, das eine dedizierte ENtError
Ausnahmeklasse mitbringt (abgeleitet vom integrierten EOSError
), es sei denn, Sie möchten wirklich Ausnahmen verwenden, ohne System.SysUtils
zu importieren (was möglich ist).
NtUiLib.Errors fügt vier Methoden zur Darstellung von TNtxStatus
Werten als Zeichenfolgen hinzu. Wenn der Fehler mit dem Wert 0xC0000061
beispielsweise auf einen Versuch zurückzuführen ist, eine Sitzungs-ID eines Tokens zu ändern, geben diese Methoden die folgenden Informationen zurück:
Verfahren | Zurückgegebene Zeichenfolge |
---|---|
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 |
Wenn Sie noch weiter gehen und dem Benutzer ein hübsches Meldungsfeld anzeigen möchten, bietet NtUiLib.Errors.Dialog ShowNtxStatus()
an. Darüber hinaus wird die Einbeziehung von NtUiLib.Exceptions.Dialog die notwendige Reflexionsunterstützung bringen und den Dialog noch weiter bereichern. Hier ist ein Beispiel, wie es aussehen könnte:
TNtxStatus
unterstützt die Erfassung von Stack-Traces (standardmäßig deaktiviert). Um es zu aktivieren, legen Sie NtUtils.CaptureStackTraces
auf True fest. Beachten Sie, dass für die sinnvolle Anzeige von Stack-Traces die Konfiguration der Generierung von Debug-Symbolen für Ihre ausführbare Datei erforderlich ist. Leider kann Delphi nur .map
Dateien ausgeben (konfiguriert über Projekt -> Optionen -> Erstellen -> Delphi-Compiler -> Verknüpfen -> Kartendatei), was im Allgemeinen nicht ausreicht. Sie benötigen ein Map2dbg- Tool eines Drittanbieters, um sie in .dbg
Dateien zu konvertieren, damit die Symbol-API sie verstehen kann. Während .dbg
Dateien möglicherweise ausreichen, ist es besser, sie noch weiter zu verarbeiten, indem Sie sie über cv2pdb in das moderne .pdb
konvertieren.
Um Debugsymbole automatisch zu generieren, fügen Sie Ihrem Projekt die folgenden Post-Build-Ereignisse hinzu:
map2dbg.exe $(OUTPUTPATH)
cv2pdb64.exe -n -s. -p$(OUTPUTNAME).pdb $(OUTPUTPATH)
Da Delphi keinen Garbage Collector enthält, werden nur wenige Typen standardmäßig verwaltet: Datensätze, Zeichenfolgen, dynamische Arrays und Schnittstellen. Klassen und Zeiger hingegen erfordern eine explizite Bereinigung, die (in ihrer sicheren Form) die Verwendung von try-finally -Blöcken erfordert und daher das Programm erheblich verkompliziert. Um dieses Problem zu beheben, enthält die Bibliothek Funktionen zur automatischen Lebensdauerverwaltung für Speicher und andere Ressourcen, die in DelphiUtils.AutoObjects implementiert sind. Durch die Verwendung von Typen aus diesem Modul weisen wir den Compiler an, automatisch ausnahmesicheren Code zum Zählen von Referenzen und zum automatischen Freigeben von Objekten in Funktionsepilogen zu generieren. Dieses Modul definiert mehrere Schnittstellen für verschiedene Arten von Ressourcen, die möglicherweise bereinigt werden müssen. Es führt die folgende Hierarchie ein:
Diagramm LR;
Untergraph id1[Beliebige Ressource]
IAutoReleasable
Ende
Untergraph id2[Ein THandle-Wert]
IHandle
Ende
Untergraph id3[Eine Delphi-Klasse]
IAutoObject[IAutoObject<T>]
Ende
Untergraph id4[Ein Zeiger]
IAutoPointer[IAutoPointer<P>]
Ende
Untergraph id5[Ein Speicherbereich]
IMemory[IMemory<P>]
Ende
IAutoReleasable -> IHandle;
IAutoReleasable -> IAutoObject;
IAutoReleasable -> IAutoPointer;
IAutoPointer -> IMemory;
IAutoReleasable
ist der Basistyp für alle Ressourcen, die Maßnahmen zur (automatischen) Bereinigung erfordern. IHandle
dient als Wrapper für Ressourcen, die durch einen THandle-Wert definiert werden. IAutoObject<T>
ist ein generischer Wrapper für die automatische Freigabe von Delphi-Klassen (d. h. alles, was von TObject abgeleitet ist). IAutoPointer<P>
definiert eine ähnliche Schnittstelle zum Freigeben dynamisch zugewiesener Zeiger (wobei die Größe der Region keine Rolle spielt). IMemory<P>
stellt einen Wrapper für Speicherbereiche bekannter Größe bereit, auf die über einen typisierten Zeiger zugegriffen werden kann, z. B. verwaltete und nicht verwaltete Box-Datensätze.
Das Rezept für die Nutzung dieser Funktion lautet wie folgt:
Definieren Sie jede Variable, die den (potenziell gemeinsamen) Besitz eines Objekts aufrechterhalten muss, über eine der Schnittstellen:
Verwenden Sie den Auto- Helfer zum Zuordnen/Kopieren/Erfassen automatischer Objekte:
Verwenden Sie bei Bedarf die Umwandlung auf der linken Seite, um die Duplizierung von Typinformationen zu vermeiden und die Syntax zu verkürzen.
Hier ist beispielsweise ein sicherer Code für die Arbeit mit TStringList unter Verwendung des klassischen Ansatzes:
var
x: TStringList;
begin
x := TStringList.Create;
try
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
finally
x.Free;
end ;
end ;
Wie Sie sich vorstellen können, führt die Verwendung von mehr Objekten in dieser Funktion zu einer erheblichen und nichtlinearen Erhöhung der Komplexität. Alternativ ist hier der entsprechende Code, der IAutoObject verwendet und sich viel besser skalieren lässt:
uses
DelphiUtils.AutoObjects;
var
x: IAutoObject<TStringList>;
begin
x := Auto.From(TStringList.Create);
x.Self.Add( ' Hi there ' );
x.Self.SaveToFile( ' test.txt ' );
end ;
Der Compiler gibt den erforderlichen Bereinigungscode in den Funktionsepilog aus und stellt sicher, dass er auch dann ausgeführt wird, wenn Ausnahmen auftreten. Darüber hinaus ermöglicht dieser Ansatz die Beibehaltung des gemeinsamen Eigentums am zugrunde liegenden Objekt, wodurch Sie eine Referenz speichern können, die die aktuelle Funktion überdauern kann (indem Sie sie beispielsweise in einer anonymen Funktion erfassen und zurückgeben). Wenn Sie diese Funktionalität nicht benötigen und einen einzelnen Eigentümer beibehalten möchten, der das Objekt beim Beenden der Funktion freigibt, können Sie die Syntax noch weiter vereinfachen:
uses
NtUtils;
var
x: TStringList;
begin
x := Auto.From(TStringList.Create).Self;
x.Add( ' Hi there ' );
x.SaveToFile( ' test.txt ' );
end ;
Dieser Code entspricht immer noch dem ursprünglichen. Intern wird eine versteckte lokale Variable erstellt, die die Schnittstelle speichert und das Objekt später freigibt.
Wenn Sie mit dynamischen Speicherzuweisungen arbeiten, kann es praktisch sein, die Umwandlung auf der linken Seite wie folgt zu verwenden:
var
x: IMemory<PByteArray>;
begin
IMemory(x) := Auto.AllocateDynamic( 100 );
x.Data[ 15 ] := 20 ;
end ;
Sie können auch geschachtelte (auf dem Heap zugeordnete) verwaltete Datensätze erstellen, die die gemeinsame Nutzung von Werttypen ermöglichen, als wären sie Referenztypen. Beachten Sie, dass sie auch verwaltete Felder wie Delphi-Strings und dynamische Arrays enthalten können – der Compiler gibt Code für die automatische Freigabe aus:
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 ;
Da Delphi die Referenzzählung verwendet, ist es immer noch möglich, dass Speicher verloren geht, wenn zwei Objekte eine zirkuläre Abhängigkeit aufweisen. Sie können dies verhindern, indem Sie schwache Referenzen verwenden. Solche Referenzen zählen nicht zur Verlängerung der Lebensdauer und die Variable, die sie speichert, wird automatisch zu Null , wenn das Zielobjekt zerstört wird. Sie müssen eine schwache Referenz auf eine starke aktualisieren, bevor Sie sie verwenden können. Weitere Einzelheiten finden Sie unter Weak<I> von DelphiUtils.AutoObjects.
Für häufig verwendete Zeigertypen variabler Größe stehen einige Aliase zur Verfügung. Hier einige Beispiele:
Handles verwenden den IHandle- Typ (siehe DelphiUtils.AutoObjects), der der oben erläuterten Logik folgt, sodass sie kein explizites Schließen erfordern. Sie können auch einige Aliase für IHandle (IScmHandle, ISamHandle, ILsaHandle usw.) finden, die lediglich aus Gründen der Lesbarkeit des Codes verfügbar sind.
Wenn Sie jemals den Besitz eines Handle-Werts in ein IHandle übernehmen müssen, benötigen Sie eine Klasse, die diese Schnittstelle implementiert und weiß, wie die zugrunde liegende Ressource freigegeben wird. Beispielsweise definiert NtUtils.Objects eine solche Klasse für Kernelobjekte, die den Aufruf von NtClose
erfordern. Außerdem fügt es eine Hilfsmethode an Auto
an, die das Erfassen von Kernel-Handles nach Wert über Auto.CaptureHandle(...)
ermöglicht. Um ein nicht besitzendes IHandle zu erstellen, verwenden Sie Auto.RefHandle(...)
.
Namen von Datensätzen, Klassen und Aufzählungen beginnen mit T
und verwenden CamelCase (Beispiel: TTokenStatistics
). Zeiger auf Datensätze oder andere Werttypen beginnen mit P
(Beispiel: PTokenStatistics
). Namen von Schnittstellen beginnen mit I
(Beispiel: ISid
). Konstanten verwenden ALL_CAPITALS. Alle Definitionen aus der Header-Ebene, die bekannte offizielle Namen haben (z. B. die im Windows SDK definierten Typen), werden mit einem SDKName
Attribut markiert, das diesen Namen angibt.
Die meisten Funktionen verwenden die folgende Namenskonvention: ein Präfix des Subsystems mit x am Ende (Ntx, Ldrx, Lsax, Samx, Scmx, Wsx, Usrx, ...) + Aktion + Ziel/Objekttyp/usw. Funktionsnamen verwenden auch CamelCase.
Die Bibliothek zielt auf Windows 7 oder höher ab, sowohl 32- als auch 64-Bit-Editionen. Allerdings sind einige der Funktionen möglicherweise nur in den neuesten 64-Bit-Versionen von Windows 11 verfügbar. Einige Beispiele sind AppContainer und ntdll-Systemaufruf-Unhooking. Wenn eine Bibliotheksfunktion von einer API abhängt, die unter Windows 7 möglicherweise nicht vorhanden ist, verwendet sie einen verzögerten Import und prüft die Verfügbarkeit zur Laufzeit.
Delphi verfügt über ein umfangreiches Reflexionssystem, das die Bibliothek innerhalb der NtUiLib- Ebene nutzt. Um dies zu erreichen, werden die meisten in der Headers -Ebene definierten Typen mit benutzerdefinierten Attributen versehen (siehe DelphiApi.Reflection). Diese Dekorationen geben nützliche Metadaten aus, die der Bibliothek helfen, komplexe Datentypen (wie PEB, TEB, USER_SHARED_DATA) zur Laufzeit präzise darzustellen und mit einer einzigen Codezeile erstaunliche Berichte zu erstellen.
Hier ist eine Beispieldarstellung von TSecurityLogonSessionData
von Ntapi.NtSecApi unter Verwendung von NtUiLib.Reflection.Types:
Hier die Übersicht über den Zweck verschiedener Module.
Unterstützungseinheit | Beschreibung |
---|---|
DelphiUtils.AutoObjects | Automatische Verwaltung der Ressourcenlebensdauer |
DelphiUtils.AutoEvents | Anonyme Ereignisse für mehrere Abonnenten |
DelphiUtils.Arrays | TArray-Helfer |
DelphiUtils.Lists | Ein genetisches, doppelt verknüpftes Listenprimitiv |
DelphiUtils.Async | Async-I/O-Unterstützungsdefinitionen |
DelphiUtils.ExternalImport | IAT-Helfer für externe Delphi-Schlüsselwörter |
DelphiUtils.RangeChecks | Helfer zur Reichweitenprüfung |
NtUtils | Gängige Bibliothekstypen |
NtUtils.SysUtils | String-Manipulation |
NtUtils.Errors | Fehlercode-Konvertierung |
NtUiLib.Errors | Suche nach Fehlercodenamen |
NtUiLib.Exceptions | SysUtils-Ausnahmeintegration |
DelphiUiLib.Strings | String-Verschönerung |
DelphiUiLib.Reflection | Basis-RTTI-Unterstützung |
DelphiUiLib.Reflection.Numeric | RTTI-Darstellung numerischer Typen |
DelphiUiLib.Reflection.Records | RTTI-Darstellung von Datensatztypen |
DelphiUiLib.Reflection.Strings | RTTI-Verschönerung von Strings |
NtUiLib.Reflection.Types | RTTI-Darstellung für gängige Typen |
NtUiLib.Console | Konsolen-I/O-Helfer |
NtUiLib.TaskDialog | TaskDialog-basierte GUI |
NtUiLib.Errors.Dialog | GUI-Fehlerdialog |
NtUiLib.Exceptions.Dialog | GUI-Ausnahmedialog |
Systemeinheit | Beschreibung |
---|---|
NtUtils.ActCtx | Aktivierungskontexte |
NtUtils.AntiHooking | Aushängen und direkter Systemaufruf |
NtUtils.Com | COM, IDispatch, WinRT |
NtUtils.Csr | CSRSS/SxS-Registrierung |
NtUtils.DbgHelp | DbgHelp- und Debug-Symbole |
NtUtils.Debug | Objekte debuggen |
NtUtils.Dism | DISM-API |
NtUtils.Environment | Umgebungsvariablen |
NtUtils.Environment.User | Benutzerumgebungsvariablen |
NtUtils.Environment.Remote | Umgebungsvariablen anderer Prozesse |
NtUtils.Files | Win32/NT-Dateinamen |
NtUtils.Files.Open | Datei und Pipe öffnen/erstellen |
NtUtils.Files.Operations | Dateioperationen |
NtUtils.Files.Directories | Aufzählung des Dateiverzeichnisses |
NtUtils.Files.FltMgr | Filtermanager-API |
NtUtils.Files.Mup | Mehrere UNC-Anbieter |
NtUtils.Files.Volumes | Volumenoperationen |
NtUtils.Files.Control | FSCTL-Operationen |
NtUtils.ImageHlp | PE-Analyse |
NtUtils.ImageHlp.Syscalls | Abrufen der Systemrufnummer |
NtUtils.ImageHlp.DbgHelp | Öffentliche Symbole ohne DbgHelp |
NtUtils.Jobs | Jobobjekte und Silos |
NtUtils.Jobs.Remote | Prozessübergreifende Jobobjektabfragen |
NtUtils.Ldr | LDR-Routinen und Parsing |
NtUtils.Lsa | LSA-Richtlinie |
NtUtils.Lsa.Audit | Audit-Richtlinie |
NtUtils.Lsa.Sid | SID-Suche |
NtUtils.Lsa.Logon | Anmeldesitzungen |
NtUtils.Manifests | Fusion/SxS-Manifest-Builder |
NtUtils.Memory | Speicheroperationen |
NtUtils.MiniDumps | Parsen des Minidump-Formats |
NtUtils.Objects | Kernel-Objekte und Handles |
NtUtils.Objects.Snapshots | Behandeln Sie Snapshots |
NtUtils.Objects.Namespace | NT-Objekt-Namespace |
NtUtils.Objects.Remote | Prozessübergreifende Handle-Operationen |
NtUtils.Objects.Compare | Griffvergleich |
NtUtils.Packages | App-Pakete und Paketfamilien |
NtUtils.Packages.SRCache | Status-Repository-Cache |
NtUtils.Packages.WinRT | WinRT-basierte Paketinformationen |
NtUtils.Power | Leistungsbezogene Funktionen |
NtUtils.Processes | Objekte verarbeiten |
NtUtils.Processes.Info | Informationen zur Prozessabfrage/Einstellung |
NtUtils.Processes.Info.Remote | Prozessabfrage/-einstellung über Code-Injection |
NtUtils.Processes.Modules | Prozessübergreifende LDR-Aufzählung |
NtUtils.Processes.Snapshots | Prozessaufzählung |
NtUtils.Processes.Create | Allgemeine Definitionen für die Prozesserstellung |
NtUtils.Processes.Create.Win32 | Methoden zur Win32-Prozesserstellung |
NtUtils.Processes.Create.Shell | Methoden zur Shell-Prozesserstellung |
NtUtils.Processes.Create.Native | NtCreateUserProcess und Co. |
NtUtils.Processes.Create.Manual | NtCreateProcessEx |
NtUtils.Processes.Create.Com | COM-basierte Prozesserstellung |
NtUtils.Processes.Create.Csr | Prozesserstellung über SbApiPort |
NtUtils.Processes.Create.Package | Appx-Aktivierung |
NtUtils.Processes.Create.Remote | Prozesserstellung per Code-Injection |
NtUtils.Processes.Create.Clone | Klonen von Prozessen |
NtUtils.Profiles | Benutzer- und AppContainer-Profile |
NtUtils.Registry | Registrierungsschlüssel |
NtUtils.Registry.Offline | Offline-Manipulation von Bienenstöcken |
NtUtils.Registry.VReg | Silobasierte Registrierungsvirtualisierung |
NtUtils.Sam | SAM-Datenbank |
NtUtils.Sections | Abschnitts-/Speicherprojektionsobjekte |
NtUtils.Security | Sicherheitsbeschreibungen |
NtUtils.Security.Acl | ACLs und ACEs |
NtUtils.Security.Sid | SIDs |
NtUtils.Security.AppContainer | AppContainer- und Funktions-SIDs |
NtUtils.Shellcode | Code-Injektion |
NtUtils.Shellcode.Dll | DLL-Injektion |
NtUtils.Shellcode.Exe | EXE-Injektion |
NtUtils.Svc | SCM-Dienste |
NtUtils.Svc.SingleTaskSvc | Service-Implementierung |
NtUtils.Synchronization | Synchronisationsprimitive |
NtUtils.System | Systeminformationen |
NtUtils.TaskScheduler | Aufgabenplaner |
NtUtils.Threads | Thread-Objekte |
NtUtils.Tokens.Info | Thread-Abfrage/Set-Informationen |
NtUtils.Threads.Worker | Thread-Worker (Thread-Pools) |
NtUtils.Tokens | Token-Objekte |
NtUtils.Tokens.Imitieren | Token-Identitätswechsel |
NtUtils.Tokens.Logon | Benutzer- und S4U-Anmeldung |
NtUtils.Tokens.AppModel | Token-AppModel-Richtlinie |
NtUtils.Transactions | Transaktionsobjekte (TmTx). |
NtUtils.Transactions.Remote | Prozesse in Transaktionen zwingen |
NtUtils.UserManager | Benutzermanagerdienst-API (Umgr). |
NtUtils.Wim | Windows-Imaging-API (*.wim). |
NtUtils.WinSafer | Sicherere API |
NtUtils.WinStation | Terminalserver-API |
NtUtils.WinUser | User32/GUI-API |
NtUtils.WinUser.WindowAffinity | Änderung der Fensteraffinität |
NtUtils.WinUser.WinstaLock | Fensterstationen verriegeln und entriegeln |
NtUtils.XmlLite | XML-Analyse und -Erstellung über XmlLite |
NtUiLib.AutoCompletion | Automatische Vervollständigung für Bearbeitungssteuerelemente |
NtUiLib.AutoCompletion.Namespace | Automatische Vervollständigung des NT-Objekt-Namespace |
NtUiLib.AutoCompletion.Sid | Automatische SID-Vervollständigung |
NtUiLib.AutoCompletion.Sid.Common | Einfache SID-Namensanbieter/-Erkenner |
NtUiLib.AutoCompletion.Sid.AppContainer | AppContainer- und Paket-SID-Anbieter/Erkenner |
NtUiLib.AutoCompletion.Sid.Capabilities | Leistungsfähige SID-Anbieter/Erkenner |
NtUiLib.WinCred | Dialogfeld „Anmeldeinformationen“. |