Co to jest uchwyt systemu Windows?

153

Co to jest „uchwyt” podczas omawiania zasobów w systemie Windows? Jak oni pracują?

Al C
źródło

Odpowiedzi:

167

Jest to abstrakcyjna wartość odniesienia do zasobu, często pamięci, otwartego pliku lub potoku.

Prawidłowo w systemie Windows (i ogólnie w komputerach) uchwyt jest abstrakcją, która ukrywa rzeczywisty adres pamięci przed użytkownikiem API, umożliwiając systemowi przeorganizowanie pamięci fizycznej w sposób niewidoczny dla programu. Zamiana uchwytu na wskaźnik blokuje pamięć, a zwolnienie uchwytu unieważnia wskaźnik. W tym przypadku pomyśl o tym jako o indeksie tabeli wskaźników ... używasz indeksu do wywołań systemowych funkcji API, a system może dowolnie zmieniać wskaźnik w tabeli.

Alternatywnie, jako uchwyt można podać rzeczywisty wskaźnik, gdy autor API zamierza odizolować użytkownika API od specyfiki tego, na co wskazuje zwracany adres; w tym przypadku należy wziąć pod uwagę, że to, na co wskazuje uchwyt, może się zmienić w dowolnym momencie (z wersji API do wersji lub nawet z wywołania do wywołania API, które zwraca uchwyt) - uchwyt należy zatem traktować jako po prostu nieprzezroczystą wartość ma znaczenie tylko dla API.

Powinienem dodać, że w każdym nowoczesnym systemie operacyjnym nawet tak zwane „rzeczywiste wskaźniki” są nadal nieprzezroczystymi uchwytami do wirtualnej przestrzeni pamięci procesu, co umożliwia operatorowi operacyjnemu zarządzanie pamięcią i zmianę jej układu bez unieważniania wskaźników w procesie .

Lawrence Dol
źródło
4
Naprawdę doceniam szybką odpowiedź. Niestety, myślę, że nadal jestem zbyt nowicjuszem, aby w pełni to zrozumieć :-(
Al C
4
Czy moja rozszerzona odpowiedź rzuca jakieś światło?
Lawrence Dol
100

A HANDLEto unikalny identyfikator specyficzny dla kontekstu. Przez specyficzne dla kontekstu rozumiem, że uchwyt uzyskany z jednego kontekstu nie musi koniecznie być używany w żadnym innym arbitralnym kontekście, który działa również na HANDLEs.

Na przykład GetModuleHandlezwraca unikalny identyfikator do aktualnie załadowanego modułu. Zwrócony uchwyt może być używany w innych funkcjach, które akceptują uchwyty modułów. Nie można go nadać funkcjom, które wymagają innych typów uchwytów. Na przykład nie można było dać rączki zwróconej GetModuleHandledo HeapDestroyi oczekiwać, że zrobi coś rozsądnego.

Sam w HANDLEsobie jest typem integralnym. Zwykle, ale niekoniecznie, jest to wskaźnik do jakiegoś podstawowego typu lub lokalizacji w pamięci. Na przykład HANDLEzwracany przez GetModuleHandlejest w rzeczywistości wskaźnikiem do podstawowego adresu pamięci wirtualnej modułu. Ale nie ma reguły mówiącej, że uchwyty muszą być wskaźnikami. Uchwyt może być również prostą liczbą całkowitą (która może być używana przez niektóre API Win32 jako indeks tablicy).

HANDLEsą celowo nieprzezroczystymi reprezentacjami, które zapewniają hermetyzację i abstrakcję z wewnętrznych zasobów Win32. W ten sposób interfejsy API Win32 mogą potencjalnie zmienić typ bazowy za uchwytem, ​​bez wpływu na kod użytkownika w jakikolwiek sposób (przynajmniej taka jest idea).

Rozważ te trzy różne wewnętrzne implementacje interfejsu API Win32, które właśnie stworzyłem, i załóżmy, że Widgetjest to plik 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);
}

Pierwszy przykład ujawnia wewnętrzne szczegóły dotyczące interfejsu API: pozwala kodowi użytkownika wiedzieć, że GetWidgetzwraca wskaźnik do pliku struct Widget. Ma to kilka konsekwencji:

  • kod użytkownika musi mieć dostęp do pliku nagłówkowego, który definiuje Widgetstrukturę
  • kod użytkownika mógłby potencjalnie zmodyfikować wewnętrzne części zwracanej Widgetstruktury

Obie te konsekwencje mogą być niepożądane.

Drugi przykład ukrywa ten wewnętrzny szczegół przed kodem użytkownika, zwracając po prostu void *. Kod użytkownika nie potrzebuje dostępu do nagłówka, który definiuje Widgetstrukturę.

Trzecim przykładem jest dokładnie taki sam jak drugi, ale po prostu zadzwonić do void *A HANDLEzamiast. Być może zniechęca to kod użytkownika do próby dokładnego ustalenia, na co void *wskazuje.

Dlaczego przechodzisz przez ten problem? Rozważ czwarty przykład nowszej wersji tego samego interfejsu API:

typedef void * HANDLE;

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

    w = findImprovedWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

Zwróć uwagę, że interfejs funkcji jest identyczny z trzecim przykładem powyżej. Oznacza to, że kod użytkownika może nadal korzystać z nowej wersji interfejsu API bez żadnych zmian, mimo że implementacja „za kulisami” została zmieniona, aby NewImprovedWidgetzamiast tego używać struktury.

Uchwyty w tym przykładzie to tak naprawdę nowa, prawdopodobnie bardziej przyjazna nazwa void *, która jest dokładnie tym, co HANDLEjest w Win32 API (sprawdź to w MSDN ). Zapewnia nieprzezroczystą ścianę między kodem użytkownika a wewnętrznymi reprezentacjami biblioteki Win32, co zwiększa przenośność między wersjami systemu Windows kodu korzystającego z interfejsu API Win32.

Dan Moulding
źródło
5
Celowo czy nie, masz rację - koncepcja jest z pewnością nieprzejrzysta (przynajmniej dla mnie :-)
Al C
5
Rozszerzyłem moją pierwotną odpowiedź kilkoma konkretnymi przykładami. Miejmy nadzieję, że dzięki temu koncepcja stanie się bardziej przejrzysta.
Dan Molding
2
Bardzo pomocne rozszerzenie ... Dzięki!
Al C
4
To musi być jedna z najczystszych, bezpośrednich i najlepiej napisanych odpowiedzi na jakiekolwiek pytanie, jakie widziałem od jakiegoś czasu. Serdecznie dziękujemy za poświęcenie czasu na napisanie tego!
Andrew,
@DanMoulding: Więc głównym powodem używania handlezamiast of void *jest zniechęcenie użytkownika do próby ustalenia dokładnie, na co wskazuje void * . Mam rację?
Lion Lai
37

UCHWYT w programowaniu Win32 to token reprezentujący zasób zarządzany przez jądro systemu Windows. Uchwyt może znajdować się przy oknie, pliku itp.

Uchwyty to po prostu sposób na zidentyfikowanie konkretnego zasobu, z którym chcesz pracować przy użyciu interfejsów API Win32.

Na przykład, jeśli chcesz utworzyć okno i pokazać je na ekranie, możesz wykonać następujące czynności:

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

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

W powyższym przykładzie HWND oznacza „klamkę do okna”.

Jeśli jesteś przyzwyczajony do języka zorientowanego obiektowo, możesz myśleć o HANDLE jako o wystąpieniu klasy bez metod, której stan można modyfikować tylko przez inne funkcje. W tym przypadku funkcja ShowWindow modyfikuje stan UCHWYTU okna.

Aby uzyskać więcej informacji, zobacz Uchwyty i typy danych.

Nick Haddad
źródło
Obiekty, do których odwołuje się HANDLEADT, są zarządzane przez jądro. Z drugiej strony, inne typy uchwytów, które nazwiesz ( HWNDitp.), To obiekty USER. Nie są one obsługiwane przez jądro systemu Windows.
Niespodziewane
1
@IInspectable zgadujesz, że te są zarządzane przez rzeczy User32.dll?
the_endian
8

Dojście to unikatowy identyfikator obiektu zarządzanego przez system Windows. Jest jak wskaźnik , ale nie jest wskaźnikiem w tym sensie, że nie jest to adres, do którego można wyłuskać odwołanie za pomocą kodu użytkownika w celu uzyskania dostępu do niektórych danych. Zamiast tego uchwyt należy przekazać do zestawu funkcji, które mogą wykonywać akcje na obiekcie identyfikowanym przez uchwyt.

ostry
źródło
5

Więc na najbardziej podstawowym poziomie HANDLE dowolnego rodzaju jest wskaźnikiem do wskaźnika lub

#define HANDLE void **

Teraz, dlaczego chcesz go używać

Zróbmy konfigurację:

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

Więc ponieważ obiekt został przekazany przez wartość (wykonaj kopię i przekaż to funkcji) do foo, printf wydrukuje oryginalną wartość 1.

Teraz, jeśli zaktualizujemy foo do:

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

Jest szansa, że ​​printf wydrukuje zaktualizowaną wartość 2. Ale istnieje również możliwość, że foo spowoduje jakąś formę uszkodzenia pamięci lub wyjątek.

Powodem jest to, że podczas gdy używasz teraz wskaźnika do przekazania obj do funkcji, którą również przydzielasz 2 MB pamięci, może to spowodować, że system operacyjny przeniesie pamięć wokół aktualizacji lokalizacji obj. Ponieważ przekazałeś wskaźnik według wartości, jeśli obj zostanie przeniesiony, system operacyjny zaktualizuje wskaźnik, ale nie kopię w funkcji, co może powodować problemy.

Ostatnia aktualizacja do foo:

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

Spowoduje to zawsze wydrukowanie zaktualizowanej wartości.

Widzisz, kiedy kompilator przydziela pamięć dla wskaźników, oznacza je jako nieruchome, więc każde ponowne tasowanie pamięci spowodowane przez przydzielenie dużego obiektu wartości przekazanej do funkcji wskaże poprawny adres, aby znaleźć ostateczną lokalizację w pamięci aktualizacja.

Dowolne typy UCHWYTÓW (hWnd, PLIK itp.) Są specyficzne dla domeny i wskazują na określony typ struktury chroniącej przed uszkodzeniem pamięci.


źródło
1
To jest błędne rozumowanie; podsystem alokacji pamięci C nie może po prostu unieważniać wskaźniki na życzenie. W przeciwnym razie żaden program w C lub C ++ nigdy nie byłby w sposób udowodniony poprawny; gorzej każdy program o wystarczającej złożoności byłby z definicji ewidentnie nieprawidłowy. Poza tym podwójne pośrednie nie pomaga, jeśli pamięć wskazywana bezpośrednio jest przenoszona pod program, chyba że wskaźnik sam w sobie jest abstrakcją z prawdziwej pamięci - co uczyniłoby z niego uchwyt .
Lawrence Dol
1
System operacyjny Macintosh (w wersjach do 9 lub 8) zrobił dokładnie to samo. Jeśli przydzieliłeś jakiś obiekt systemowy, często otrzymywałeś do niego uchwyt, pozostawiając systemowi operacyjnemu swobodę przemieszczania obiektu. Przy ograniczonym rozmiarze pamięci pierwszych komputerów Mac było to dość ważne.
Rhialto obsługuje Monikę
5

Uchwyt jest jak wartość klucza podstawowego rekordu w bazie danych.

edycja 1: cóż, dlaczego głos przeciw, klucz podstawowy jednoznacznie identyfikuje rekord bazy danych, a uchwyt w systemie Windows jednoznacznie identyfikuje okno, otwarty plik itp., To właśnie mówię.

Edwin Yip
źródło
1
Nie wyobrażam sobie, że możesz twierdzić, że uchwyt jest wyjątkowy. Może być unikalny dla stacji Windows Station użytkownika, ale nie gwarantuje się, że będzie unikalny, jeśli wielu użytkowników uzyskuje dostęp do tego samego systemu w tym samym czasie. Oznacza to, że wielu użytkowników może odzyskać wartość uchwytu, która jest numerycznie identyczna, ale w kontekście aplikacji Windows Station użytkownika mapują na różne rzeczy ...
Nick
2
@nick Jest wyjątkowy w danym kontekście. Klucz główny nie będzie unikatowy między różnymi stołami ...
Benny Mackney,
2

Pomyśl o oknie w systemie Windows jako o strukturze, która je opisuje. Ta struktura jest wewnętrzną częścią systemu Windows i nie musisz znać jej szczegółów. Zamiast tego system Windows udostępnia typedef dla wskaźnika do struktury dla tej struktury. To jest „uchwyt”, za pomocą którego można złapać okno.,

Charlie Martin
źródło
To prawda, ale zawsze warto pamiętać, że uchwyt zwykle nie jest adresem pamięci i jeden kod użytkownika nie powinien go wyłuskiwać.
ostry ząb