Qu'est-ce qu'un handle Windows?

153

Qu'est-ce qu'une «poignée» lors de la discussion des ressources dans Windows? Comment travaillent-ils?

Al C
la source

Réponses:

167

C'est une valeur de référence abstraite à une ressource, souvent de la mémoire ou un fichier ouvert, ou un tube.

Correctement , sous Windows (et généralement en informatique), un handle est une abstraction qui cache une adresse mémoire réelle à l'utilisateur de l'API, permettant au système de réorganiser la mémoire physique de manière transparente au programme. La résolution d'une poignée en un pointeur verrouille la mémoire et la libération de la poignée invalide le pointeur. Dans ce cas, considérez-le comme un index dans une table de pointeurs ... vous utilisez l'index pour les appels d'API système, et le système peut changer le pointeur dans la table à volonté.

En variante, un pointeur réel peut être donné comme descripteur lorsque l'auteur de l'API a l'intention que l'utilisateur de l'API soit isolé des spécificités de ce vers quoi l'adresse renvoyée pointe; dans ce cas il faut considérer que ce vers quoi pointe le handle peut changer à tout moment (de version d'API à version ou même d'appel à appel de l'API qui renvoie le handle) - le handle doit donc être traité comme une simple valeur opaque significatif uniquement pour l'API.

Je dois ajouter que dans n'importe quel système d'exploitation moderne, même les soi-disant "vrais pointeurs" sont toujours des poignées opaques dans l'espace mémoire virtuelle du processus, ce qui permet à l'O / S de gérer et de réorganiser la mémoire sans invalider les pointeurs dans le processus .

Lawrence Dol
la source
4
J'apprécie vraiment la réponse rapide. Malheureusement, je pense que je suis encore trop débutant pour le comprendre pleinement :-(
Al C
4
Ma réponse élargie éclaire-t-elle quelque chose?
Lawrence Dol
100

A HANDLEest un identifiant unique spécifique au contexte. Par contexte spécifique, je veux dire qu'un descripteur obtenu à partir d'un contexte ne peut pas nécessairement être utilisé dans un autre contexte aribtraire qui fonctionne également sur l' HANDLEart.

Par exemple, GetModuleHandlerenvoie un identifiant unique à un module actuellement chargé. Le handle retourné peut être utilisé dans d'autres fonctions qui acceptent les handles de module. Il ne peut pas être attribué aux fonctions qui nécessitent d'autres types de poignées. Par exemple, vous ne pouvez pas donner un handle retourné de GetModuleHandleà HeapDestroyet vous attendre à ce qu'il fasse quelque chose de sensé.

Le HANDLElui-même n'est qu'un type intégral. Il s'agit généralement, mais pas nécessairement, d'un pointeur vers un type ou un emplacement mémoire sous-jacent. Par exemple, le HANDLErenvoyé par GetModuleHandleest en fait un pointeur vers l'adresse de mémoire virtuelle de base du module. Mais il n'y a pas de règle stipulant que les poignées doivent être des pointeurs. Un handle peut également être simplement un simple entier (qui pourrait éventuellement être utilisé par une API Win32 comme index dans un tableau).

HANDLELes s sont des représentations intentionnellement opaques qui fournissent l'encapsulation et l'abstraction des ressources Win32 internes. De cette façon, les API Win32 pourraient potentiellement changer le type sous-jacent derrière un HANDLE, sans que cela n'affecte le code utilisateur de quelque manière que ce soit (du moins c'est l'idée).

Considérez ces trois implémentations internes différentes d'une API Win32 que je viens de créer et supposez que Widgetc'est un fichier 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);
}

Le premier exemple expose les détails internes de l'API: il permet au code utilisateur de savoir qui GetWidgetrenvoie un pointeur vers un struct Widget. Cela a quelques conséquences:

  • le code utilisateur doit avoir accès au fichier d'en-tête qui définit la Widgetstructure
  • le code utilisateur pourrait potentiellement modifier des parties internes de la Widgetstructure retournée

Ces deux conséquences peuvent être indésirables.

Le deuxième exemple masque ce détail interne du code utilisateur, en renvoyant juste void *. Le code utilisateur n'a pas besoin d'accéder à l'en-tête qui définit la Widgetstructure.

Le troisième exemple est exactement le même que le deuxième, mais nous appelons simplement le void *a à la HANDLEplace. Cela décourage peut-être le code utilisateur d'essayer de comprendre exactement à quoi il void *pointe.

Pourquoi traverser ce problème? Considérez ce quatrième exemple d'une version plus récente de cette même API:

typedef void * HANDLE;

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

    w = findImprovedWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

Notez que l'interface de la fonction est identique au troisième exemple ci-dessus. Cela signifie que le code utilisateur peut continuer à utiliser cette nouvelle version de l'API, sans aucune modification, même si l'implémentation "en coulisses" a changé pour utiliser la NewImprovedWidgetstructure à la place.

Les poignées de cet exemple ne sont en réalité qu'un nouveau nom, vraisemblablement plus convivial, void *qui est exactement ce que a HANDLEdans l'API Win32 (recherchez-le sur MSDN ). Il fournit un mur opaque entre le code utilisateur et les représentations internes de la bibliothèque Win32 qui augmente la portabilité, entre les versions de Windows, du code qui utilise l'API Win32.

Moulage Dan
la source
5
Intentionnel ou non, vous avez raison - le concept est certainement opaque (du moins pour moi :-)
Al C
5
J'ai développé ma réponse originale avec quelques exemples concrets. Espérons que cela rendra le concept un peu plus transparent.
Dan Moulding
2
Extension très utile ... Merci!
Al C
4
Cela doit être l'une des réponses les plus claires, directes et les mieux écrites à une question que j'ai vue depuis un moment. Merci sincèrement d'avoir pris le temps de l'écrire!
Andrew
@DanMoulding: Donc, la principale raison d'utiliser handleau lieu de void *est décourage le code utilisateur d'essayer de comprendre exactement ce que le void * pointe . Ai-je raison?
Lion Lai
37

Un HANDLE dans la programmation Win32 est un jeton qui représente une ressource gérée par le noyau Windows. Un handle peut être vers une fenêtre, un fichier, etc.

Les poignées sont simplement un moyen d'identifier une ressource particulaire avec laquelle vous souhaitez travailler à l'aide des API Win32.

Ainsi, par exemple, si vous souhaitez créer une fenêtre et l'afficher à l'écran, vous pouvez effectuer les opérations suivantes:

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

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

Dans l'exemple ci-dessus, HWND signifie "un handle vers une fenêtre".

Si vous êtes habitué à un langage orienté objet, vous pouvez considérer un HANDLE comme une instance d'une classe sans méthode dont l'état n'est modifiable que par d'autres fonctions. Dans ce cas, la fonction ShowWindow modifie l'état de la poignée de fenêtre.

Voir Poignées et types de données pour plus d'informations.

Nick Haddad
la source
Les objets référencés via l' HANDLEADT sont gérés par le noyau. Les autres types de descripteurs que vous nommez ( HWND, etc.) sont en revanche des objets USER. Ceux-ci ne sont pas gérés par le noyau Windows.
IInspectable le
1
@IInspectable devinant que ceux-ci sont gérés par des trucs User32.dll?
the_endian
8

Un handle est un identifiant unique pour un objet géré par Windows. C'est comme un pointeur , mais pas un pointeur dans le sens que ce n'est pas une adresse qui pourrait être déréférencée par le code utilisateur pour accéder à certaines données. Au lieu de cela, un handle doit être passé à un ensemble de fonctions qui peuvent effectuer des actions sur l'objet identifié par le handle.

dents acérées
la source
5

Ainsi, au niveau le plus élémentaire, un HANDLE de toute sorte est un pointeur vers un pointeur ou

#define HANDLE void **

Maintenant, pourquoi vous voudriez l'utiliser

Prenons une configuration:

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

Donc, comme obj a été passé par valeur (faites une copie et donnez-la à la fonction) à foo, le printf affichera la valeur d'origine de 1.

Maintenant, si nous mettons à jour foo vers:

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

Il y a une chance que le printf affiche la valeur mise à jour de 2. Mais il y a aussi la possibilité que foo provoque une forme de corruption de mémoire ou d'exception.

La raison est la suivante, alors que vous utilisez maintenant un pointeur pour passer obj à la fonction que vous allouez également 2 Mo de mémoire, cela pourrait amener le système d'exploitation à déplacer la mémoire en mettant à jour l'emplacement de obj. Puisque vous avez passé le pointeur par valeur, si obj est déplacé, le système d'exploitation met à jour le pointeur mais pas la copie dans la fonction et peut causer des problèmes.

Une dernière mise à jour de toto de:

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

Cela imprimera toujours la valeur mise à jour.

Voir, lorsque le compilateur alloue de la mémoire pour les pointeurs, il les marque comme immobiles, de sorte que tout remaniement de la mémoire causé par le grand objet alloué à la valeur transmise à la fonction pointera vers l'adresse correcte pour trouver l'emplacement final en mémoire vers mettre à jour.

Tous les types particuliers de HANDLE (hWnd, FILE, etc.) sont spécifiques au domaine et pointent vers un certain type de structure pour se protéger contre la corruption de la mémoire.


la source
1
C'est un raisonnement défectueux; le sous-système d'allocation de mémoire C ne peut pas simplement invalider les pointeurs à volonté. Sinon, aucun programme C ou C ++ ne pourrait être prouvé correctement; pire tout programme d'une complexité suffisante serait manifestement incorrect par définition. De plus, la double indirection n'aide pas si la mémoire directement pointée est déplacée sous le programme, à moins que le pointeur ne soit lui-même une abstraction de la mémoire réelle - ce qui en ferait un handle .
Lawrence Dol
1
Le système d'exploitation Macintosh (dans les versions jusqu'à 9 ou 8) a fait exactement ce qui précède. Si vous allouiez un objet système, vous obtiendrez souvent une poignée dessus, laissant le système d'exploitation libre de déplacer l'objet. Avec la taille de mémoire limitée des premiers Mac, c'était plutôt important.
Rhialto soutient Monica
5

Un handle est comme une valeur de clé primaire d'un enregistrement dans une base de données.

edit 1: eh bien, pourquoi le downvote, une clé primaire identifie de manière unique un enregistrement de base de données, et une poignée dans le système Windows identifie de manière unique une fenêtre, un fichier ouvert, etc., c'est ce que je dis.

Edwin Yip
la source
1
Je n'imagine pas que vous puissiez affirmer que la poignée est unique. Il peut être unique pour Windows Station d'un utilisateur, mais il n'est pas garanti qu'il soit unique si plusieurs utilisateurs accèdent au même système en même temps. Autrement dit, plusieurs utilisateurs peuvent récupérer une valeur de handle qui est numériquement identique, mais dans le contexte de la station Windows de l'utilisateur, ils mappent à des choses différentes ...
Nick
2
@nick C'est unique dans un contexte donné. Une clé primaire ne sera pas non plus unique entre les différentes tables ...
Benny Mackney
2

Considérez la fenêtre sous Windows comme une structure qui la décrit. Cette structure est une partie interne de Windows et vous n'avez pas besoin d'en connaître les détails. Au lieu de cela, Windows fournit un typedef pour le pointeur vers struct pour cette structure. C'est la "poignée" par laquelle vous pouvez saisir la fenêtre.,

Charlie Martin
la source
C'est vrai, mais il vaut toujours la peine de se rappeler que le handle n'est généralement pas une adresse mémoire et qu'un code utilisateur ne doit pas le déréférencer.
Sharptooth