O que é um identificador do Windows?

153

O que é um "identificador" ao discutir recursos no Windows? Como eles funcionam?

Al C
fonte

Respostas:

167

É um valor de referência abstrato para um recurso, geralmente memória ou arquivo aberto ou canal.

Adequadamente , no Windows (e geralmente na computação), um identificador é uma abstração que oculta um endereço de memória real do usuário da API, permitindo que o sistema reorganize a memória física de forma transparente para o programa. A resolução de um identificador em um ponteiro bloqueia a memória e a liberação do identificador invalida o ponteiro. Nesse caso, pense nele como um índice em uma tabela de ponteiros ... você usa o índice para as chamadas à API do sistema, e o sistema pode alterar o ponteiro na tabela à vontade.

Como alternativa, um ponteiro real pode ser fornecido como identificador quando o gravador da API pretende que o usuário da API seja isolado das especificidades para as quais o endereço retornado aponta; nesse caso, deve-se considerar que o que o identificador aponta pode mudar a qualquer momento (de versão da API para versão ou mesmo de chamada para chamada da API que retorna o identificador) - o identificador deve ser tratado simplesmente como um valor opaco significativo apenas para a API.

Devo acrescentar que, em qualquer sistema operacional moderno, mesmo os chamados "ponteiros reais" ainda são opacos no espaço de memória virtual do processo, o que permite que o sistema operacional gerencie e reorganize a memória sem invalidar os ponteiros no processo .

Lawrence Dol
fonte
4
Eu realmente aprecio a resposta rápida. Infelizmente, eu acho que ainda sou muito de um novato para compreendê-lo totalmente :-(
Al C
4
Minha resposta expandida lança alguma luz?
2426 Lawrence Dol
100

A HANDLEé um identificador exclusivo específico do contexto. Por contexto específico, quero dizer que um identificador obtido de um contexto não pode necessariamente ser usado em qualquer outro contexto arbitrário que também funcione em HANDLEs.

Por exemplo, GetModuleHandleretorna um identificador exclusivo para um módulo carregado no momento. O identificador retornado pode ser usado em outras funções que aceitam identificadores de módulo. Não pode ser atribuído a funções que requerem outros tipos de identificadores. Por exemplo, você não pode dar um identificador retornado de GetModuleHandlepara HeapDestroye esperar que ele faça algo sensato.

O HANDLEpróprio é apenas um tipo integral. Geralmente, mas não necessariamente, é um ponteiro para algum tipo ou local de memória subjacente. Por exemplo, o HANDLEretornado por GetModuleHandleé na verdade um ponteiro para o endereço de memória virtual base do módulo. Mas não há regra afirmando que os identificadores devem ser ponteiros. Um identificador também pode ser apenas um número inteiro simples (que pode ser usado por alguma API do Win32 como um índice em uma matriz).

HANDLEs são representações intencionalmente opacas que fornecem encapsulamento e abstração de recursos internos do Win32. Dessa forma, as APIs do Win32 podem potencialmente alterar o tipo subjacente por trás de um HANDLE, sem afetar o código do usuário de qualquer forma (pelo menos essa é a ideia).

Considere estas três implementações internas diferentes de uma API do Win32 que acabei de criar e suponha que Widgetseja a 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);
}

O primeiro exemplo expõe os detalhes internos sobre a API: permite que o código do usuário saiba que GetWidgetretorna um ponteiro para a struct Widget. Isso tem algumas consequências:

  • o código do usuário deve ter acesso ao arquivo de cabeçalho que define a Widgetestrutura
  • o código do usuário pode modificar partes internas da Widgetestrutura retornada

Ambas as consequências podem ser indesejáveis.

O segundo exemplo oculta esse detalhe interno do código do usuário, retornando apenas void *. O código do usuário não precisa de acesso ao cabeçalho que define a Widgetestrutura.

O terceiro exemplo é exatamente o mesmo que o segundo, mas apenas chamar a void *uma HANDLEvez. Talvez isso desencoraje o código do usuário de tentar descobrir exatamente para que void *serve.

Por que passar por esse problema? Considere este quarto exemplo de uma versão mais recente dessa mesma API:

typedef void * HANDLE;

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

    w = findImprovedWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

Observe que a interface da função é idêntica ao terceiro exemplo acima. Isso significa que o código do usuário pode continuar usando essa nova versão da API, sem nenhuma alteração, mesmo que a implementação "nos bastidores" tenha mudado para usar a NewImprovedWidgetestrutura.

Os identificadores neste exemplo são realmente apenas um novo nome, provavelmente mais amigável, void *que é exatamente o que HANDLEexiste na API do Win32 (consulte o MSDN ). Ele fornece uma parede opaca entre o código do usuário e as representações internas da biblioteca Win32 que aumentam a portabilidade, entre versões do Windows, do código que usa a API do Win32.

Dan Moulding
fonte
5
Intencional ou não, você está certo - o conceito é certamente opaco (pelo menos para mim :-)
Al C
5
Eu expandi minha resposta original com alguns exemplos concretos. Espero que isso torne o conceito um pouco mais transparente.
Dan Molding #
2
Expansão muito útil ... Obrigado!
Al C
4
Essa deve ser uma das respostas mais limpas, diretas e bem escritas a qualquer pergunta que eu já tenha visto há algum tempo. Sinceramente, obrigado por dedicar um tempo para escrevê-lo!
Andrew
@ DanMoulding: Portanto, o principal motivo para usar, em handlevez de, void *é desencorajar o código do usuário de tentar descobrir exatamente o que o vazio * aponta . Estou correcto?
Lion Lai
37

Um HANDLE na programação do Win32 é um token que representa um recurso gerenciado pelo kernel do Windows. Um identificador pode ser para uma janela, um arquivo etc.

Os identificadores são simplesmente uma maneira de identificar um recurso particulado com o qual você deseja trabalhar usando as APIs do Win32.

Por exemplo, se você deseja criar uma janela e mostrá-la na tela, você pode fazer o seguinte:

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

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

No exemplo acima, HWND significa "um identificador para uma janela".

Se você está acostumado a uma linguagem orientada a objetos, pode pensar em HANDLE como uma instância de uma classe sem métodos cujo estado é modificável apenas por outras funções. Nesse caso, a função ShowWindow modifica o estado do punho da janela.

Consulte Identificadores e tipos de dados para obter mais informações.

Nick Haddad
fonte
Objetos referenciados pelo HANDLEADT são gerenciados pelo kernel. Os outros tipos de identificador que você nomeia ( HWND, etc.), por outro lado, são objetos USER. Esses não são gerenciados pelo kernel do Windows.
11nspectable
1
@IInspectable adivinhar aqueles são gerenciados por coisas User32.dll?
the_endian
8

Um identificador é um identificador exclusivo para um objeto gerenciado pelo Windows. É como um ponteiro , mas não um ponteiro, na medida em que não é um endereço que possa ser desreferenciado pelo código do usuário para obter acesso a alguns dados. Em vez disso, um identificador deve ser passado para um conjunto de funções que podem executar ações no objeto que o identificador identifica.

dente afiado
fonte
5

Portanto, no nível mais básico, um HANDLE de qualquer tipo é um ponteiro para um ponteiro ou

#define HANDLE void **

Agora, por que você gostaria de usá-lo

Vamos fazer uma configuração:

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

Portanto, como obj foi passado por valor (faça uma cópia e dê isso à função) para foo, o printf imprimirá o valor original de 1.

Agora, se atualizarmos foo para:

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

Há uma chance de que o printf imprima o valor atualizado de 2. Mas também há a possibilidade de que o foo cause algum tipo de corrupção ou exceção na memória.

O motivo é que, enquanto você está usando um ponteiro para passar obj à função, você também está alocando 2 Megs de memória, isso pode fazer com que o sistema operacional mova a memória atualizando o local da obj. Como você passou o ponteiro por valor, se obj for movido, o SO atualizará o ponteiro, mas não a cópia na função e, potencialmente, causando problemas.

Uma atualização final para foo de:

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

Isso sempre imprimirá o valor atualizado.

Veja, quando o compilador aloca memória para ponteiros, ele os marca como imóveis, portanto, qualquer reorganização da memória causada pelo objeto grande que está sendo alocado, o valor passado para a função apontará para o endereço correto para descobrir o local final na memória para atualizar.

Quaisquer tipos específicos de HANDLEs (hWnd, FILE etc.) são específicos do domínio e apontam para um determinado tipo de estrutura para proteção contra corrupção de memória.


fonte
1
Esse é um raciocínio imperfeito; o subsistema de alocação de memória C não pode apenas invalidar os ponteiros à vontade. Caso contrário, nenhum programa C ou C ++ poderia estar comprovadamente correto; pior ainda, qualquer programa de complexidade suficiente estaria demonstradamente incorreto por definição. Além disso, a indireção dupla não ajuda se a memória apontada diretamente for movida por baixo do programa, a menos que o ponteiro seja realmente uma abstração da memória real - o que tornaria uma manipulação .
Lawrence Dol
1
O sistema operacional Macintosh (nas versões até 9 ou 8) fez exatamente o acima. Se você alocasse algum objeto do sistema, frequentemente conseguiria identificá-lo, deixando o sistema operacional livre para mover o objeto. Com o tamanho limitado da memória dos primeiros Macs, isso foi bastante importante.
Rhialto apoia Monica
5

Um identificador é como um valor de chave primária de um registro em um banco de dados.

edit 1: bem, por que o voto negativo, uma chave primária identifica exclusivamente um registro do banco de dados, e um identificador no sistema Windows identifica exclusivamente uma janela, um arquivo aberto etc., é o que estou dizendo.

Edwin Yip
fonte
1
Não imagino que você possa afirmar que o identificador é único. Pode ser exclusivo para o Windows Station de um usuário, mas não é garantido que ele seja exclusivo se houver vários usuários acessando o mesmo sistema ao mesmo tempo. Ou seja, vários usuários podem receber de volta um valor identificador que é numericamente idênticos, mas no contexto da Estação Windows do usuário que mapeiam para coisas diferentes ...
Nick
2
@ Nick É único em um determinado contexto. Uma chave primária não vai ser único entre diferentes tabelas ou ...
Benny Mackney
2

Pense na janela do Windows como sendo uma estrutura que a descreve. Essa estrutura é uma parte interna do Windows e você não precisa saber os detalhes. Em vez disso, o Windows fornece um typedef para o ponteiro estruturar para essa estrutura. Essa é a "alça" pela qual você pode se segurar na janela.,

Charlie Martin
fonte
É verdade, mas é sempre bom lembrar que o identificador geralmente não é um endereço de memória e um código de usuário não deve desreferê-lo.
Sharptooth 25/05/09