Qu'est-ce qu'un "Handle" lors de la discussion de ressources dans Windows? Comment travaillent-ils?
C'est une valeur de référence abstraite d'une ressource, souvent de la mémoire, un fichier ouvert ou un canal.
Correctement, sous Windows (et généralement en informatique), un descripteur est une abstraction qui masque une adresse mémoire réelle à l'utilisateur de l'API, permettant ainsi au système de réorganiser la mémoire physique de manière transparente pour le programme. La résolution d'un handle dans un pointeur verrouille la mémoire et le relâchement du handle 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 modifier le pointeur de la table à volonté.
En guise d'alternative, un pointeur réel peut être utilisé comme identificateur lorsque l'auteur de l'API souhaite que l'utilisateur de l'API soit isolé des spécificités de l'adresse indiquée par l'adresse renvoyée; dans ce cas, il faut considérer que le pointeur peut être modifié à tout moment (d'une version d'API à l'autre ou même d'un appel à l'autre de l'API renvoyant le descripteur) - le descripteur doit donc être traité comme une simple valeur opaque. significatif niquement à l'API.
Je devrais ajouter que, dans tout système d'exploitation moderne, même les soi-disant "pointeurs réels" sont toujours des poignées opaques dans l'espace de mémoire virtuelle du processus, ce qui permet au système d'exploitation de gérer et de réorganiser la mémoire sans invalider les pointeurs du processus. .
Un HANDLE
est un identifiant unique spécifique au contexte. Par spécifique au contexte, j'entends qu'un descripteur obtenu à partir d'un contexte ne peut pas nécessairement être utilisé dans un autre contexte aribtrary qui fonctionne également sur HANDLE
s.
Par exemple, GetModuleHandle
renvoie un identifiant unique à un module actuellement chargé. Le descripteur renvoyé peut être utilisé dans d'autres fonctions acceptant les descripteurs de module. Il ne peut pas être attribué à des fonctions nécessitant d'autres types de descripteurs. Par exemple, vous ne pouvez pas donner un descripteur renvoyé de GetModuleHandle
à HeapDestroy
et vous attendre à ce qu'il fasse quelque chose de sensé.
Le HANDLE
lui-même n'est qu'un type intégral. Généralement, mais pas nécessairement, il s'agit d'un pointeur sur un type ou un emplacement de mémoire sous-jacent. Par exemple, le HANDLE
renvoyé par GetModuleHandle
est en réalité un pointeur sur 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 descripteur pourrait également être un simple entier (qui pourrait éventuellement être utilisé par certaines API Win32 en tant qu'index dans un tableau).
HANDLE
s sont des représentations volontairement opaques qui fournissent une encapsulation et une abstraction à partir de 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 (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 supposons que Widget
soit 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);
}
Le premier exemple expose les détails internes de l'API: il permet au code utilisateur de savoir que GetWidget
renvoie un pointeur sur un struct Widget
. Cela a quelques conséquences:
Widget
Widget
renvoyéeCes deux conséquences peuvent être indésirables.
Le deuxième exemple cache ce détail interne au code utilisateur, en retournant seulement void *
. Le code utilisateur n'a pas besoin d'accéder à l'en-tête qui définit la structure Widget
.
Le troisième exemple est exactement le même que le second, mais nous appelons simplement le void *
a HANDLE
à la place. Cela décourage peut-être le code utilisateur d'essayer de comprendre exactement ce que le void *
pointe vers.
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 "dans les coulisses" a été modifiée pour utiliser plutôt la structure NewImprovedWidget
.
Les poignées dans cet exemple ne sont en réalité qu'un nouveau nom, probablement plus convivial, pour void *
, qui correspond exactement à ce qu'un HANDLE
est dans l'API Win32 (recherchez-le sous 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.
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 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 procéder comme suit:
// 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 descripteur de 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 Window HANDLE.
Voir Poignées et types de données pour plus d'informations.
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 qu'il ne s'agit pas d'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 descripteur doit être transmis à un ensemble de fonctions pouvant effectuer des actions sur l'objet identifié par le descripteur.
Un descripteur est comme une valeur de clé primaire d'un enregistrement dans une base de données.
edit 1: pourquoi le vote négatif, une clé primaire identifie de manière unique un enregistrement de base de données et un descripteur de système Windows identifie de manière unique une fenêtre, un fichier ouvert, etc., c’est ce que je dis.
Donc, au niveau le plus élémentaire, un HANDLE est un pointeur sur un pointeur ou
#define HANDLE void **
Maintenant, pourquoi voudriez-vous 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, parce que obj a été passé par valeur (faire une copie et le donner à la fonction) à foo, printf affichera la valeur originale de 1.
Maintenant, si nous mettons à jour foo pour:
void foo(Object * bar)
{
LargeObj lo = new LargeObj();
bar->val++;
}
Il est possible que l’imprimante imprime la valeur mise à jour de 2. Il est également possible que foo provoque une forme de corruption de mémoire ou une exception.
La raison en est que, alors que vous utilisez maintenant un pointeur pour passer obj à la fonction que vous allouez, vous avez également 2 Mo de mémoire, cela pourrait amener le système d'exploitation à déplacer la mémoire pour mettre à 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, ce qui peut poser des problèmes.
Une dernière mise à jour de foo de:
void foo(Object **bar){
LargeObj lo = LargeObj();
Object * b = &bar;
b->val++;
}
Cela imprimera toujours la valeur mise à jour.
Vous voyez, lorsque le compilateur alloue de la mémoire pour des pointeurs, il les marque comme étant inamovibles. Ainsi, tout remaniement de la mémoire causé par le gros objet alloué à la valeur transmise à la fonction pointera vers la bonne adresse pour connaître l'emplacement final en mémoire. mise à jour.
Tous les types particuliers de HANDLE (hWnd, FILE, etc.) sont spécifiques à un domaine et pointent vers un certain type de structure pour la protection contre la corruption de la mémoire.
Pensez à la fenêtre dans Windows comme étant 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 à structurer pour cette struct. C'est la "poignée" par laquelle vous pouvez saisir la fenêtre.,