Che cos'è un handle di Windows?


153

Che cos'è un "handle" quando si discute di risorse in Windows? Come funzionano?

Risposte:


167

È un valore di riferimento astratto per una risorsa, spesso memoria, un file aperto o una pipe.

Correttamente , in Windows, (e generalmente nell'informatica) un handle è un'astrazione che nasconde un indirizzo di memoria reale dall'utente API, consentendo al sistema di riorganizzare la memoria fisica in modo trasparente al programma. La risoluzione di una maniglia in un puntatore blocca la memoria e il rilascio della maniglia invalida il puntatore. In questo caso pensalo come un indice in una tabella di puntatori ... usi l'indice per le chiamate API di sistema e il sistema può cambiare il puntatore nella tabella a piacimento.

In alternativa un vero puntatore può essere dato come handle quando il writer API intende che l'utente dell'API sia isolato dalle specifiche di ciò a cui l'indirizzo restituito punta; in questo caso si deve considerare che ciò a cui punta l'handle può cambiare in qualsiasi momento (da versione API a versione o anche da chiamata a chiamata dell'API che restituisce l'handle) - l'handle deve quindi essere trattato semplicemente come un valore opaco significativo solo per l'API.

Vorrei aggiungere che in qualsiasi sistema operativo moderno, anche i cosiddetti "puntatori reali" sono ancora maniglie opache nello spazio di memoria virtuale del processo, che consente all'O / S di gestire e riorganizzare la memoria senza invalidare i puntatori all'interno del processo .


4
Apprezzo molto la risposta rapida. Sfortunatamente, penso di essere ancora troppo principiante per capirlo appieno :-(
Al C

4
La mia risposta espansa fa luce?
Lawrence Dol,

100

A HANDLEè un identificatore univoco specifico del contesto. Per specifico contesto, intendo che un handle ottenuto da un contesto non può necessariamente essere utilizzato in qualsiasi altro contesto aribtrare che funziona anche su HANDLEs.

Ad esempio, GetModuleHandlerestituisce un identificatore univoco a un modulo attualmente caricato. L'handle restituito può essere utilizzato in altre funzioni che accettano gli handle del modulo. Non può essere assegnato a funzioni che richiedono altri tipi di handle. Ad esempio, non si poteva dare un handle restituito da GetModuleHandlead HeapDestroye si aspettano di fare qualcosa di sensato.

Lo HANDLEstesso è solo un tipo integrale. Di solito, ma non necessariamente, è un puntatore a un tipo o posizione di memoria sottostante. Ad esempio, HANDLErestituito da GetModuleHandleè in realtà un puntatore all'indirizzo di memoria virtuale di base del modulo. Ma non esiste una regola che affermi che gli handle devono essere puntatori. Un handle potrebbe anche essere un semplice numero intero (che potrebbe essere utilizzato da alcune API Win32 come indice in un array).

HANDLEsono rappresentazioni volutamente opache che forniscono incapsulamento e astrazione dalle risorse interne di Win32. In questo modo, le API di Win32 potrebbero potenzialmente modificare il tipo sottostante dietro una MANIGLIA, senza che ciò influisca in alcun modo sul codice utente (almeno questa è l'idea).

Considera queste tre diverse implementazioni interne di un'API Win32 che ho appena creato e supponi che Widgetsia un 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);
}

Il primo esempio espone i dettagli interni sull'API: consente al codice utente di sapere che GetWidgetrestituisce un puntatore a struct Widget. Ciò ha un paio di conseguenze:

  • il codice utente deve avere accesso al file di intestazione che definisce la Widgetstruttura
  • il codice utente potrebbe potenzialmente modificare le parti interne della Widgetstruttura restituita

Entrambe queste conseguenze potrebbero essere indesiderabili.

Il secondo esempio nasconde questo dettaglio interno dal codice utente, restituendo just void *. Il codice utente non necessita dell'accesso all'intestazione che definisce la Widgetstruttura.

Il terzo esempio è esattamente lo stesso come il secondo, ma basta chiamare l' void *uno HANDLEinvece. Forse questo scoraggia il codice utente dal tentativo di capire esattamente a cosa void *punta.

Perché attraversare questo problema? Considera questo quarto esempio di una versione più recente di questa stessa API:

typedef void * HANDLE;

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

    w = findImprovedWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

Si noti che l'interfaccia della funzione è identica al terzo esempio sopra. Ciò significa che il codice utente può continuare a utilizzare questa nuova versione dell'API, senza alcuna modifica, anche se l'implementazione "dietro le quinte" è cambiata per utilizzare NewImprovedWidgetinvece la struttura.

Gli handle in questo esempio sono in realtà solo un nuovo, presumibilmente più amichevole, nome void *, che è esattamente ciò che HANDLEè a nell'API Win32 (cercare MSDN ). Fornisce un muro opaco tra il codice utente e le rappresentazioni interne della libreria Win32 che aumenta la portabilità, tra le versioni di Windows, del codice che utilizza l'API Win32.


5
Intenzionale o no, hai ragione - il concetto è certamente opaco (almeno per me :-)
Al C

5
Ho ampliato la mia risposta originale con alcuni esempi concreti. Speriamo che questo renda il concetto un po 'più trasparente.
Dan Molding,

2
Espansione molto utile ... Grazie!
Al C,

4
Questa deve essere una delle risposte più pulite, dirette e ben scritte a qualsiasi domanda che ho visto da un po '. Grazie sinceramente per aver dedicato del tempo a scriverlo!
Andrew,

@DanMoulding: Quindi il motivo principale da utilizzare al handleposto di void *è scoraggiare il codice utente dal tentativo di capire esattamente a cosa punta il vuoto * . Ho ragione?
Lion Lai,

37

Un HANDLE nella programmazione Win32 è un token che rappresenta una risorsa gestita dal kernel di Windows. Un handle può essere una finestra, un file, ecc.

Gli handle sono semplicemente un modo per identificare una risorsa particolato con cui si desidera lavorare utilizzando le API Win32.

Ad esempio, se si desidera creare una finestra e mostrarla sullo schermo, è possibile effettuare le seguenti operazioni:

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

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

Nell'esempio sopra HWND significa "un handle per una finestra".

Se sei abituato a un linguaggio orientato agli oggetti puoi pensare a una MANIGLIA come un'istanza di una classe senza metodi il cui stato è modificabile solo da altre funzioni. In questo caso la funzione ShowWindow modifica lo stato di Window HANDLE.

Vedere Maniglie e tipi di dati per ulteriori informazioni.


Gli oggetti a cui fa riferimento HANDLEADT sono gestiti dal kernel. Gli altri tipi di handle nominati ( HWND, ecc.) D'altra parte, sono oggetti USER. Quelli non sono gestiti dal kernel di Windows.
IIprevedibile

1
@IInspectable indovinando quelli sono gestiti da roba User32.dll?
the_endian il

8

Un handle è un identificatore univoco per un oggetto gestito da Windows. È come un puntatore , ma non un puntatore nel senso che non è un indirizzo che potrebbe essere referenziato dal codice utente per ottenere l'accesso ad alcuni dati. Invece, un handle deve essere passato a un set di funzioni in grado di eseguire azioni sull'oggetto identificato dall'handle.


5

Quindi al livello più elementare una MANIGLIA di qualsiasi tipo è un puntatore a un puntatore o

#define HANDLE void **

Ora sul perché vorresti usarlo

Diamo una configurazione:

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);
}

Quindi, poiché obj è stato passato per valore (crea una copia e assegnalo alla funzione) a pippo, printf stamperà il valore originale di 1.

Ora se aggiorniamo foo a:

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

È possibile che printf stia stampando il valore aggiornato di 2. Ma esiste anche la possibilità che foo provochi una qualche forma di corruzione o eccezione della memoria.

Il motivo è che mentre stai usando un puntatore per passare obj alla funzione che stai allocando anche 2 Meg di memoria, questo potrebbe far spostare il sistema operativo aggiornando la posizione di obj. Dato che hai passato il puntatore in base al valore, se obj viene spostato, il sistema operativo aggiorna il puntatore ma non la copia nella funzione e potenzialmente causa problemi.

Un aggiornamento finale a pippo di:

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

Questo stamperà sempre il valore aggiornato.

Vedi, quando il compilatore alloca memoria per i puntatori, li contrassegna come immobili, quindi qualsiasi rimescolamento della memoria causato dall'oggetto di grandi dimensioni che viene allocato, il valore passato alla funzione punterà all'indirizzo corretto per trovare la posizione finale in memoria a aggiornare.

Qualsiasi tipo particolare di HANDLE (hWnd, FILE, ecc.) È specifico del dominio e punta a un certo tipo di struttura per proteggere dalla corruzione della memoria.


1
Questo è un ragionamento errato; il sottosistema di allocazione della memoria C non può semplicemente invalidare i puntatori a piacimento. Altrimenti nessun programma C o C ++ potrebbe mai essere dimostrabile correttamente; peggio, qualsiasi programma di sufficiente complessità sarebbe per definizione dimostrabile errato. Inoltre, la doppia indiretta non aiuta se la memoria direttamente indicata viene spostata sotto il programma a meno che il puntatore non sia in realtà un'astrazione dalla memoria reale, il che lo renderebbe un handle .
Lawrence Dol,

1
Il sistema operativo Macintosh (nelle versioni fino a 9 o 8) ha fatto esattamente quanto sopra. Se si allocasse un oggetto di sistema, si otterrebbe spesso un handle ad esso, lasciando il sistema operativo libero di spostare l'oggetto. Con le limitate dimensioni della memoria dei primi Mac era piuttosto importante.
Rhialto sostiene Monica

5

Un handle è come un valore chiave principale di un record in un database.

modifica 1: beh, perché il downvote, una chiave primaria identifica in modo univoco un record del database e un handle nel sistema Windows identifica in modo univoco una finestra, un file aperto, ecc. Ecco cosa sto dicendo.


1
Non immagino che tu possa affermare che la maniglia è unica. Può essere univoco per Windows Station di un utente, ma non è garantito che sia unico se ci sono più utenti che accedono allo stesso sistema contemporaneamente. Cioè, più utenti potrebbero recuperare un valore di handle numericamente identico, ma nel contesto della Windows Station dell'utente si associano a cose diverse ...
Nick,

2
@nick È unico in un determinato contesto.
Neanche

2

Pensa alla finestra di Windows come a una struttura che la descrive. Questa struttura è una parte interna di Windows e non è necessario conoscerne i dettagli. Invece, Windows fornisce un typedef per il puntatore a struct per quella struttura. Questa è la "maniglia" con cui puoi aggrapparti alla finestra.,


È vero, ma vale sempre la pena ricordare che l'handle di solito non è un indirizzo di memoria e un codice utente non dovrebbe dereferenziarlo.
sharptooth,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.