¿Qué es un controlador de Windows?

153

¿Qué es un "Handle" cuando se discuten recursos en Windows? ¿Cómo trabajan?

Al C
fuente

Respuestas:

167

Es un valor de referencia abstracto a un recurso, a menudo memoria o un archivo abierto, o una tubería.

Correctamente , en Windows (y en general en informática) un identificador es una abstracción que oculta una dirección de memoria real del usuario de la API, lo que permite que el sistema reorganice la memoria física de forma transparente para el programa. Resolver un controlador en un puntero bloquea la memoria, y liberar el controlador invalida el puntero. En este caso, considérelo como un índice en una tabla de punteros ... usa el índice para las llamadas a la API del sistema, y ​​el sistema puede cambiar el puntero en la tabla a voluntad.

Alternativamente, se puede dar un puntero real como identificador cuando el escritor de la API tiene la intención de aislar al usuario de la API de los detalles de los puntos de la dirección devuelta; en este caso, debe considerarse que lo que apunta el identificador puede cambiar en cualquier momento (de una versión de API a otra o incluso de una llamada a otra de la API que devuelve el identificador); por lo tanto, el identificador debe tratarse simplemente como un valor opaco significativo solo para la API.

Debo agregar que en cualquier sistema operativo moderno, incluso los llamados "punteros reales" siguen siendo mangos opacos en el espacio de memoria virtual del proceso, lo que permite que el O / S administre y reorganice la memoria sin invalidar los punteros dentro del proceso. .

Lawrence Dol
fuente
44
Realmente aprecio la rápida respuesta. Desafortunadamente, creo que todavía soy demasiado novato para entenderlo completamente :-(
Al C
44
¿Mi respuesta expandida arroja alguna luz?
Lawrence Dol
100

A HANDLEes un identificador único específico del contexto. Por contexto específico, quiero decir que un identificador obtenido de un contexto no necesariamente se puede usar en ningún otro contexto de aribtrary que también funcione en HANDLEs.

Por ejemplo, GetModuleHandledevuelve un identificador único a un módulo cargado actualmente. El identificador devuelto se puede utilizar en otras funciones que aceptan identificadores de módulo. No se puede asignar a funciones que requieren otros tipos de identificadores. Por ejemplo, no se podía dar un mango de regresar de GetModuleHandlea HeapDestroyy esperar que se hace algo razonable.

El HANDLEsí mismo es solo un tipo integral. Por lo general, pero no necesariamente, es un puntero a algún tipo subyacente o ubicación de memoria. Por ejemplo, el HANDLEdevuelto por GetModuleHandlees en realidad un puntero a la dirección de memoria virtual base del módulo. Pero no hay una regla que indique que los identificadores deben ser punteros. Un identificador también podría ser un entero simple (que podría ser utilizado por alguna API de Win32 como un índice en una matriz).

HANDLEs son representaciones intencionalmente opacas que proporcionan encapsulación y abstracción de los recursos internos de Win32. De esta manera, las API Win32 podrían cambiar el tipo subyacente detrás de un MANGO, sin que afecte el código del usuario de ninguna manera (al menos esa es la idea).

Considere estas tres implementaciones internas diferentes de una API Win32 que acabo de inventar, y suponga que Widgetes 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<void *>(w);
}
typedef void * HANDLE;

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

    w = findWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

El primer ejemplo expone los detalles internos sobre la API: permite que el código de usuario sepa que GetWidgetdevuelve un puntero a a struct Widget. Esto tiene un par de consecuencias:

  • el código de usuario debe tener acceso al archivo de encabezado que define la Widgetestructura
  • el código de usuario podría modificar potencialmente partes internas de la Widgetestructura devuelta

Ambas consecuencias pueden ser indeseables.

El segundo ejemplo oculta este detalle interno del código de usuario, al devolver solo void *. El código de usuario no necesita acceso al encabezado que define la Widgetestructura.

El tercer ejemplo es exactamente el mismo que el segundo, pero en su lugar solo llamamos void *a HANDLE. Quizás esto desalienta el código de usuario de tratar de averiguar exactamente a qué void *apuntan.

¿Por qué pasar por este problema? Considere este cuarto ejemplo de una versión más nueva de esta misma API:

typedef void * HANDLE;

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

    w = findImprovedWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

Observe que la interfaz de la función es idéntica al tercer ejemplo anterior. Esto significa que el código de usuario puede continuar usando esta nueva versión de la API, sin ningún cambio, a pesar de que la implementación "detrás de escena" ha cambiado para usar la NewImprovedWidgetestructura en su lugar.

Los identificadores en este ejemplo son realmente solo un nombre nuevo, presumiblemente más amigable, void *que es exactamente lo que HANDLEes en la API Win32 (búsquelo en MSDN ). Proporciona una pared opaca entre el código de usuario y las representaciones internas de la biblioteca Win32 que aumenta la portabilidad, entre versiones de Windows, del código que utiliza la API Win32.

Dan Molding
fuente
55
Intencional o no, tienes razón: el concepto es ciertamente opaco (al menos para mí :-)
Al C
55
He ampliado mi respuesta original con algunos ejemplos concretos. Esperemos que esto haga que el concepto sea un poco más transparente.
Dan Molding
2
Expansión muy útil ... ¡Gracias!
Al C
44
Esta tiene que ser una de las respuestas más limpias, directas y mejor escritas a cualquier pregunta que haya visto en mucho tiempo. ¡Gracias sinceramente por tomarse el tiempo de escribirlo!
Andrew
@DanMoulding: Por lo tanto, la razón principal para usar en handlelugar de desalentarvoid * es que el código del usuario intente averiguar exactamente a qué apunta el vacío * . ¿Estoy en lo correcto?
Lion Lai
37

Un HANDLE en la programación Win32 es un token que representa un recurso que es administrado por el kernel de Windows. Un identificador puede ser una ventana, un archivo, etc.

Los controladores son simplemente una forma de identificar un recurso particulado con el que desea trabajar utilizando las API de Win32.

Entonces, por ejemplo, si desea crear una ventana y mostrarla en la pantalla, puede hacer lo siguiente:

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

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

En el ejemplo anterior, HWND significa "un identificador para una ventana".

Si está acostumbrado a un lenguaje orientado a objetos, puede pensar en un HANDLE como una instancia de una clase sin métodos cuyo estado solo es modificable por otras funciones. En este caso, la función ShowWindow modifica el estado del MANGO de la ventana.

Consulte Manijas y tipos de datos para obtener más información.

Nick Haddad
fuente
Los objetos a los que se hace referencia a través del HANDLEADT son administrados por el núcleo. Los otros tipos de identificadores que nombre ( HWND, etc.) por otro lado, son objetos USUARIO. Esos no son administrados por el kernel de Windows.
Inspeccionable el
1
@IInspectable ¿adivina que son gestionados por cosas User32.dll?
the_endian
8

Un identificador es un identificador único para un objeto administrado por Windows. Es como un puntero , pero no un puntero en el sentido de que no es una dirección que el código de usuario pueda desreferenciar para obtener acceso a algunos datos. En cambio, se debe pasar un identificador a un conjunto de funciones que pueden realizar acciones en el objeto que identifica el identificador.

diente filoso
fuente
5

Entonces, en el nivel más básico, un MANGO de cualquier tipo es un puntero a un puntero o

#define HANDLE void **

Ahora en cuanto a por qué querrías usarlo

Tomemos una configuración:

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

Entonces, debido a que obj se pasó por valor (haga una copia y dele eso a la función) a foo, printf imprimirá el valor original de 1.

Ahora si actualizamos foo a:

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

Existe la posibilidad de que printf imprima el valor actualizado de 2. Pero también existe la posibilidad de que foo cause algún tipo de corrupción de memoria o excepción.

La razón es que mientras usa un puntero para pasar obj a la función, también está asignando 2 Megas de memoria, esto podría hacer que el sistema operativo mueva la memoria actualizando la ubicación de obj. Como ha pasado el puntero por valor, si obj se mueve, el sistema operativo actualiza el puntero pero no la copia en la función y puede causar problemas.

Una actualización final para foo de:

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

Esto siempre imprimirá el valor actualizado.

Vea, cuando el compilador asigna memoria para punteros, los marca como inamovibles, por lo que cualquier reorganización de la memoria causada por el objeto grande al que se le asigna el valor pasado a la función apuntará a la dirección correcta para encontrar la ubicación final en la memoria para actualizar.

Cualquier tipo particular de MANILLAS (hWnd, FILE, etc.) son específicas del dominio y apuntan a un cierto tipo de estructura para proteger contra la corrupción de la memoria.


fuente
1
Este es un razonamiento defectuoso; el subsistema de asignación de memoria C no puede simplemente invalidar punteros a voluntad. De lo contrario, ningún programa C o C ++ podría ser demostrablemente correcto; peor aún, cualquier programa de suficiente complejidad sería demostrablemente incorrecto por definición. Además, la doble indirección no ayuda si la memoria apuntada directamente se mueve debajo del programa a menos que el puntero sea en sí mismo una abstracción de la memoria real, lo que lo convertiría en un controlador .
Lawrence Dol
1
El sistema operativo Macintosh (en versiones de hasta 9 u 8) hizo exactamente lo anterior. Si asignó algún objeto del sistema, a menudo lo manejaría, dejando al sistema operativo libre para mover el objeto. Con el tamaño de memoria limitado de los primeros Mac que era bastante importante.
Rhialto apoya a Mónica
5

Un identificador es como un valor de clave principal de un registro en una base de datos.

edición 1: bueno, por qué el voto negativo, una clave primaria identifica de forma exclusiva un registro de base de datos, y un identificador en el sistema de Windows identifica de manera única una ventana, un archivo abierto, etc., eso es lo que estoy diciendo.

Edwin Yip
fuente
1
No me imagino que pueda afirmar que el identificador es único. Puede ser único según la Windows Station de un usuario, pero no se garantiza que sea único si hay varios usuarios que acceden al mismo sistema al mismo tiempo. Es decir, varios usuarios podrían recuperar un valor de identificador numéricamente idéntico, pero en el contexto de la Windows Station del usuario, asignan diferentes cosas ...
Nick
2
@nick Es único en un contexto dado. Una clave principal tampoco será única entre las diferentes tablas ...
Benny Mackney
2

Piense en la ventana de Windows como una estructura que la describe. Esta estructura es una parte interna de Windows y no necesita conocer los detalles de la misma. En cambio, Windows proporciona un typedef para que el puntero se estructura para esa estructura. Esa es la "manija" por la cual puedes agarrar la ventana.

Charlie Martin
fuente
Es cierto, pero siempre vale la pena recordar que el identificador generalmente no es una dirección de memoria y un código de usuario no debe desreferenciarlo.
sharptooth