Was ist ein Windows-Handle?

153

Was ist ein "Handle", wenn Ressourcen in Windows besprochen werden? Wie arbeiten Sie?

Al C.
quelle

Antworten:

167

Es ist ein abstrakter Referenzwert für eine Ressource, häufig Speicher oder eine geöffnete Datei oder eine Pipe.

Richtig , in Windows, (und in der Regel bei der Berechnung) ein Handgriff ist eine Abstraktion , die eine reale Speicheradresse von dem API Benutzer verbirgt, das System transparent , um das Programm zu reorganisieren physische Speicher ermöglicht. Das Auflösen eines Handles in einen Zeiger sperrt den Speicher, und das Loslassen des Handles macht den Zeiger ungültig. Stellen Sie sich in diesem Fall einen Index für eine Zeigertabelle vor. Sie verwenden den Index für die System-API-Aufrufe, und das System kann den Zeiger in der Tabelle nach Belieben ändern.

Alternativ kann ein realer Zeiger als Handle angegeben werden, wenn der API-Writer beabsichtigt, dass der Benutzer der API von den Besonderheiten isoliert wird, auf die die zurückgegebene Adresse verweist. In diesem Fall muss berücksichtigt werden, dass sich das, worauf das Handle zeigt, jederzeit ändern kann (von API-Version zu Version oder sogar von Aufruf zu Aufruf der API, die das Handle zurückgibt). Das Handle sollte daher einfach als undurchsichtiger Wert behandelt werden sinnvoll nur an die API.

Ich sollte hinzufügen, dass in jedem modernen Betriebssystem sogar die sogenannten "realen Zeiger" undurchsichtige Handles im virtuellen Speicherbereich des Prozesses sind, wodurch das Betriebssystem den Speicher verwalten und neu anordnen kann, ohne die Zeiger innerhalb des Prozesses ungültig zu machen .

Lawrence Dol
quelle
4
Ich schätze die schnelle Antwort sehr. Leider denke ich, dass ich immer noch zu ein Neuling bin, um es vollständig zu verstehen :-(
Al C
4
Bringt meine erweiterte Antwort Licht ins Dunkel?
Lawrence Dol
100

A HANDLEist eine kontextspezifische eindeutige Kennung. Mit kontextspezifisch meine ich, dass ein aus einem Kontext erhaltenes Handle nicht unbedingt in einem anderen aribträren Kontext verwendet werden kann, der auch auf HANDLEs funktioniert .

Gibt beispielsweise GetModuleHandleeine eindeutige Kennung an ein aktuell geladenes Modul zurück. Das zurückgegebene Handle kann in anderen Funktionen verwendet werden, die Modulhandles akzeptieren. Es kann nicht für Funktionen vergeben werden, die andere Arten von Handles erfordern. Zum Beispiel könnten Sie einem Handle, das von GetModuleHandleto zurückgegeben wird, nicht geben HeapDestroyund erwarten, dass es etwas Sinnvolles tut.

Das HANDLEselbst ist nur ein integraler Typ. Normalerweise, aber nicht unbedingt, ist es ein Zeiger auf einen zugrunde liegenden Typ oder Speicherort. Beispielsweise ist das HANDLEzurückgegebene von GetModuleHandletatsächlich ein Zeiger auf die virtuelle Basisspeicheradresse des Moduls. Es gibt jedoch keine Regel, die besagt, dass Handles Zeiger sein müssen. Ein Handle kann auch nur eine einfache Ganzzahl sein (die möglicherweise von einer Win32-API als Index für ein Array verwendet werden kann).

HANDLEs sind absichtlich undurchsichtige Darstellungen, die die Kapselung und Abstraktion von internen Win32-Ressourcen ermöglichen. Auf diese Weise können die Win32-APIs möglicherweise den zugrunde liegenden Typ hinter einem HANDLE ändern, ohne dass dies Auswirkungen auf den Benutzercode hat (zumindest ist dies die Idee).

Betrachten Sie diese drei verschiedenen internen Implementierungen einer Win32-API, die ich gerade erstellt habe, und nehmen Sie an, dass dies Widgeteine ist struct.

Widget * GetWidget (std::string name)
{
    Widget *w;

    w = findWidget(name);

    return w;
}
void * GetWidget (std::string name)
{
    Widget *w;

    w = findWidget(name);

    return reinterpret_cast<void *>(w);
}
typedef void * HANDLE;

HANDLE GetWidget (std::string name)
{
    Widget *w;

    w = findWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

Das erste Beispiel zeigt die internen Details der API: Der Benutzercode erkennt, dass GetWidgetein Zeiger auf a zurückgegeben wird struct Widget. Dies hat einige Konsequenzen:

  • Der Benutzercode muss Zugriff auf die Header-Datei haben, die die WidgetStruktur definiert
  • Der Benutzercode kann möglicherweise interne Teile der zurückgegebenen WidgetStruktur ändern

Diese beiden Konsequenzen können unerwünscht sein.

Das zweite Beispiel verbirgt dieses interne Detail vor dem Benutzercode, indem nur zurückgegeben wird void *. Der Benutzercode benötigt keinen Zugriff auf den Header, der die WidgetStruktur definiert .

Das dritte Beispiel ist genau das gleiche wie das zweite, aber wir nennen stattdessen einfach das void *a HANDLE. Vielleicht hält dies den Benutzercode davon ab, genau herauszufinden, worauf die void *Punkte zeigen.

Warum diese Schwierigkeiten durchmachen? Betrachten Sie dieses vierte Beispiel einer neueren Version derselben API:

typedef void * HANDLE;

HANDLE GetWidget (std::string name)
{
    NewImprovedWidget *w;

    w = findImprovedWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

Beachten Sie, dass die Benutzeroberfläche der Funktion mit dem dritten Beispiel oben identisch ist. Dies bedeutet, dass Benutzercode diese neue Version der API ohne Änderungen weiterhin verwenden kann, obwohl sich die Implementierung "hinter den Kulissen" geändert hat, um NewImprovedWidgetstattdessen die Struktur zu verwenden.

Die Handles in diesem Beispiel sind eigentlich nur ein neuer, vermutlich freundlicherer Name für void *, und genau das HANDLEist a in der Win32-API (siehe MSDN ). Es bietet eine undurchsichtige Wand zwischen dem Benutzercode und den internen Darstellungen der Win32-Bibliothek, die die Portabilität von Code, der die Win32-API verwendet, zwischen Windows-Versionen erhöht.

Dan Moulding
quelle
5
Absichtlich oder nicht, Sie haben Recht - das Konzept ist sicherlich undurchsichtig (zumindest für mich :-)
Al C
5
Ich habe meine ursprüngliche Antwort um einige konkrete Beispiele erweitert. Hoffentlich wird das Konzept dadurch etwas transparenter.
Dan Moulding
2
Sehr hilfreiche Erweiterung ... Danke!
Al C
4
Dies muss eine der saubersten, direktesten und am besten geschriebenen Antworten auf alle Fragen sein, die ich seit einiger Zeit gesehen habe. Vielen Dank, dass Sie sich die Zeit genommen haben, es zu schreiben!
Andrew
@DanMoulding: Der Hauptgrund für die Verwendung handleanstelle von void *ist, Benutzercode davon abzuhalten , genau herauszufinden, worauf die Leere * hinweist . Hab ich recht?
Lion Lai
37

Ein HANDLE in der Win32-Programmierung ist ein Token, das eine Ressource darstellt, die vom Windows-Kernel verwaltet wird. Ein Handle kann sich auf ein Fenster, eine Datei usw. beziehen.

Handles sind lediglich eine Möglichkeit, eine Partikelressource zu identifizieren, mit der Sie mithilfe der Win32-APIs arbeiten möchten.

Wenn Sie beispielsweise ein Fenster erstellen und auf dem Bildschirm anzeigen möchten, können Sie Folgendes tun:

// Create the window
HWND hwnd = CreateWindow(...); 
if (!hwnd)
   return; // hwnd not created

// Show the window.
ShowWindow(hwnd, SW_SHOW);

Im obigen Beispiel bedeutet HWND "ein Handle für ein Fenster".

Wenn Sie an eine objektorientierte Sprache gewöhnt sind, können Sie sich einen HANDLE als eine Instanz einer Klasse ohne Methoden vorstellen, deren Status nur durch andere Funktionen geändert werden kann. In diesem Fall ändert die ShowWindow- Funktion den Status des Fenstergriffs.

Weitere Informationen finden Sie unter Handles und Datentypen .

Nick Haddad
quelle
Objekte, auf die über HANDLEADT verwiesen wird, werden vom Kernel verwaltet. Die anderen von Ihnen benannten Handle-Typen ( HWNDusw.) sind USER-Objekte. Diese werden nicht vom Windows-Kernel verwaltet.
Unsichtbarer
1
@ IInspectable Vermutung, dass diese von User32.dll Sachen verwaltet werden?
The_endian
8

Ein Handle ist eine eindeutige Kennung für ein von Windows verwaltetes Objekt. Es ist wie ein Zeiger , aber kein Zeiger in dem Sinne, dass es keine Adresse ist, die durch Benutzercode dereferenziert werden könnte, um Zugriff auf einige Daten zu erhalten. Stattdessen soll ein Handle an eine Reihe von Funktionen übergeben werden, die Aktionen für das vom Handle identifizierte Objekt ausführen können.

scharfer Zahn
quelle
5

Auf der einfachsten Ebene ist ein GRIFF jeder Art ein Zeiger auf einen Zeiger oder

#define HANDLE void **

Nun, warum Sie es verwenden möchten

Nehmen wir ein Setup:

class Object{
   int Value;
}

class LargeObj{

   char * val;
   LargeObj()
   {
      val = malloc(2048 * 1000);
   }

}

void foo(Object bar){
    LargeObj lo = new LargeObj();
    bar.Value++;
}

void main()
{
   Object obj = new Object();
   obj.val = 1;
   foo(obj);
   printf("%d", obj.val);
}

Da obj als Wert (Kopie erstellen und der Funktion übergeben) an foo übergeben wurde, gibt printf den ursprünglichen Wert 1 aus.

Nun, wenn wir foo aktualisieren auf:

void foo(Object * bar)
{
    LargeObj lo = new LargeObj();
    bar->val++;
}

Es besteht die Möglichkeit, dass printf den aktualisierten Wert 2 ausgibt. Es besteht jedoch auch die Möglichkeit, dass foo eine Form von Speicherbeschädigung oder -ausnahme verursacht.

Der Grund dafür ist, dass Sie, während Sie jetzt einen Zeiger verwenden, um obj an die Funktion zu übergeben, der Sie auch 2 Megabyte Speicher zuweisen, das Betriebssystem möglicherweise den Speicher verschieben, um den Speicherort von obj zu aktualisieren. Da Sie den Zeiger als Wert übergeben haben, aktualisiert das Betriebssystem beim Verschieben von obj den Zeiger, jedoch nicht die Kopie in der Funktion, was möglicherweise zu Problemen führt.

Ein letztes Update zu foo von:

void foo(Object **bar){
    LargeObj lo = LargeObj();
    Object * b = &bar;
    b->val++;
}

Dadurch wird immer der aktualisierte Wert gedruckt.

Wenn der Compiler Speicher für Zeiger zuweist, markiert er diese als unbeweglich, sodass jedes erneute Mischen des Speichers, das durch das Zuweisen des an die Funktion übergebenen Werts an das große Objekt verursacht wird, auf die richtige Adresse verweist, um die endgültige Position im Speicher zu ermitteln aktualisieren.

Alle bestimmten Arten von HANDLEs (hWnd, FILE usw.) sind domänenspezifisch und verweisen auf eine bestimmte Art von Struktur, um vor Speicherbeschädigung zu schützen.


quelle
1
Dies ist eine fehlerhafte Argumentation; Das C-Speicherzuweisungssubsystem kann Zeiger nicht einfach nach Belieben ungültig machen. Andernfalls könnte kein C- oder C ++ - Programm jemals nachweislich korrekt sein. Schlimmer noch, ein Programm mit ausreichender Komplexität wäre per Definition nachweislich falsch. Außerdem hilft die doppelte Indirektion nicht, wenn der Speicher, auf den direkt verwiesen wird, unter dem Programm verschoben wird, es sei denn, der Zeiger selbst ist eine Abstraktion vom realen Speicher - was ihn zu einem Handle machen würde .
Lawrence Dol
1
Das Macintosh-Betriebssystem (in Versionen bis 9 oder 8) hat genau das oben Genannte getan. Wenn Sie ein Systemobjekt zugewiesen haben, erhalten Sie häufig ein Handle dafür, sodass das Betriebssystem das Objekt frei bewegen kann. Bei der begrenzten Speichergröße der ersten Macs war das ziemlich wichtig.
Rhialto unterstützt Monica
5

Ein Handle ist wie ein Primärschlüsselwert eines Datensatzes in einer Datenbank.

edit 1: Nun, warum das Downvote, ein Primärschlüssel einen Datenbankdatensatz eindeutig identifiziert und ein Handle im Windows-System ein Fenster, eine geöffnete Datei usw. eindeutig identifiziert. Das sage ich.

Edwin Yip
quelle
1
Ich kann mir nicht vorstellen, dass Sie behaupten können, dass der Griff einzigartig ist. Es kann für die Windows-Station eines Benutzers eindeutig sein, es kann jedoch nicht garantiert werden, dass es eindeutig ist, wenn mehrere Benutzer gleichzeitig auf dasselbe System zugreifen. Das heißt, mehrere Benutzer könnten einen numerisch identischen Handle-Wert zurückerhalten, aber im Kontext der Windows Station des Benutzers werden sie verschiedenen Dingen zugeordnet ...
Nick
2
@nick Es ist einzigartig in einem bestimmten Kontext. Ein Primärschlüssel wird auch nicht zwischen verschiedenen Tabellen eindeutig sein ...
Benny Mackney
2

Stellen Sie sich das Fenster in Windows als eine Struktur vor, die es beschreibt. Diese Struktur ist ein interner Teil von Windows und Sie müssen die Details nicht kennen. Stattdessen stellt Windows ein typedef für den Zeiger auf die Struktur für diese Struktur bereit. Das ist der "Griff", mit dem Sie das Fenster festhalten können.

Charlie Martin
quelle
Es stimmt, aber es ist immer zu beachten, dass das Handle normalerweise keine Speicheradresse ist und ein Benutzercode sie nicht dereferenzieren sollte.
Scharfzahn