Che cos’è un handle di Windows?

Che cos’è un “handle” quando si discutono le risorse in Windows? Come funzionano?

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

Correttamente , in Windows, (e generalmente nel calcolo) un handle è un’astrazione che nasconde un vero indirizzo di memoria dall’utente API, consentendo al sistema di riorganizzare la memoria fisica in modo trasparente al programma. La risoluzione di un handle in un puntatore blocca la memoria e il rilascio dell’handle invalida il puntatore. In questo caso, consideralo come un indice in una tabella di puntatori … si utilizza l’indice per le chiamate API di sistema e il sistema può modificare a piacimento il puntatore nella tabella.

In alternativa, può essere fornito un puntatore reale come handle quando l’autore dell’API intende che l’utente dell’API sia isolato dalle specifiche di ciò a cui punta l’indirizzo restituito; in questo caso si deve considerare che ciò che l’handle punta a può cambiare in qualsiasi momento (dalla versione dell’API alla versione o persino da una chiamata all’API che restituisce l’handle) – l’handle dovrebbe quindi essere considerato semplicemente come un valore opaco significativo solo per l’API.

Dovrei 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 agli O / S di gestire e riorganizzare la memoria senza invalidare i puntatori all’interno del processo .

Un HANDLE è un identificativo univoco specifico del contesto. Per contesto specifico, intendo che un handle ottenuto da un contesto non può essere necessariamente utilizzato in qualsiasi altro contesto aribrary che funzioni anche su HANDLE .

Ad esempio, GetModuleHandle restituisce un identificativo 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 è ansible fornire un handle restituito da GetModuleHandle a HeapDestroy e si aspetta che faccia qualcosa di sensato.

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

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

Considera queste tre diverse implementazioni interne di un’API Win32 appena creata e presumo che il Widget sia una 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(w); } 
 typedef void * HANDLE; HANDLE GetWidget (std::string name) { Widget *w; w = findWidget(name); return reinterpret_cast(w); } 

Il primo esempio espone i dettagli interni dell’API: consente al codice utente di sapere che GetWidget restituisce un puntatore a un struct Widget . Questo ha un paio di conseguenze:

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

Entrambe queste conseguenze potrebbero essere indesiderabili.

Il secondo esempio nasconde questo dettaglio interno dal codice utente, restituendo solo void * . Il codice utente non ha bisogno di accedere all’intestazione che definisce la struttura del Widget .

Il terzo esempio è esattamente uguale al secondo, ma chiamiamo invece il void * a HANDLE . Forse questo scoraggia il codice utente dal cercare di capire esattamente a cosa punta il void * .

Perché passare attraverso 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(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” è stata modificata per utilizzare invece la struttura NewImprovedWidget .

Le maniglie in questo esempio sono in realtà solo un nuovo, presumibilmente più amichevole, nome per void * , che è esattamente ciò che un HANDLE è nell’API Win32 (cercalo su 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.

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

I manici sono semplicemente un modo per identificare una risorsa particellare con cui si desidera lavorare utilizzando le API Win32.

Ad esempio, se vuoi creare una finestra e mostrarla sullo schermo, puoi fare quanto segue:

 // 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 un HANDLE come un’istanza di una class senza metodi il cui stato è solo modificabile da altre funzioni. In questo caso la funzione ShowWindow modifica lo stato di Window HANDLE.

Vedere Maniglie e tipi di dati per ulteriori informazioni.

Un handle è un identificativo univoco per un object gestito da Windows. È come un puntatore , ma non un puntatore nel senso che non è un indirizzo che potrebbe essere dereferenziato dal codice utente per accedere ad alcuni dati. Invece un handle deve essere passato a un insieme di funzioni che possono eseguire azioni sull’object identificato dal manico.

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

 #define HANDLE void ** 

Ora sul perché vorresti usarlo

Prendiamo 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 foo, printf stamperà il valore originale di 1.

Ora se aggiorniamo foo a:

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

C’è una possibilità che printf stampi il valore aggiornato di 2. Ma c’è anche la possibilità che foo causi qualche forma di corruzione della memoria o eccezione.

La ragione è questa 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 sulla memoria 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 causando problemi.

Un ultimo aggiornamento a foo di:

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

Questo stamperà sempre il valore aggiornato.

Vedete, quando il compilatore alloca la memoria per i puntatori li contrassegna come immobili, quindi ogni re-shuffling della memoria causato dall’object grande che viene assegnato al valore passato alla funzione punterà all’indirizzo corretto per trovare la posizione finale in memoria a aggiornare.

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

Un handle è come un valore di chiave primaria 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. Questo è quello che sto dicendo.

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 entrare in contatto con la finestra.

Un object è una struttura di dati che rappresenta una risorsa di sistema, ad esempio un file, un thread o un’immagine grafica. Un’applicazione non può accedere direttamente ai dati dell’object o alla risorsa di sistema rappresentata da un object. Invece, un’applicazione deve ottenere un handle di object, che può utilizzare per esaminare o modificare la risorsa di sistema. Ogni handle ha una voce in una tabella internamente mantenuta. Queste voci contengono gli indirizzi delle risorse e i mezzi per identificare il tipo di risorsa.