Odpowiedzi:
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 .
A HANDLE
to 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 HANDLE
s.
Na przykład GetModuleHandle
zwraca 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 GetModuleHandle
do HeapDestroy
i oczekiwać, że zrobi coś rozsądnego.
Sam w HANDLE
sobie jest typem integralnym. Zwykle, ale niekoniecznie, jest to wskaźnik do jakiegoś podstawowego typu lub lokalizacji w pamięci. Na przykład HANDLE
zwracany przez GetModuleHandle
jest 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).
HANDLE
są 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 Widget
jest 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 GetWidget
zwraca wskaźnik do pliku struct Widget
. Ma to kilka konsekwencji:
Widget
strukturęWidget
strukturyObie 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 Widget
strukturę.
Trzecim przykładem jest dokładnie taki sam jak drugi, ale po prostu zadzwonić do void *
A HANDLE
zamiast. 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 NewImprovedWidget
zamiast 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 HANDLE
jest 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.
handle
zamiast of void *
jest zniechęcenie użytkownika do próby ustalenia dokładnie, na co wskazuje void * . Mam rację?
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.
HANDLE
ADT, są zarządzane przez jądro. Z drugiej strony, inne typy uchwytów, które nazwiesz ( HWND
itp.), To obiekty USER. Nie są one obsługiwane przez jądro systemu Windows.
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.
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.
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ę.
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.,