Что такое дескриптор Windows?

153

Что такое «дескриптор» при обсуждении ресурсов в Windows? Как они работают?

Al C
источник

Ответы:

167

Это абстрактное значение ссылки на ресурс, часто в память, открытый файл или канал.

Должным образом , в Windows (и вообще в вычислениях) дескриптор является абстракцией, которая скрывает реальный адрес памяти от пользователя API, позволяя системе прозрачно реорганизовать физическую память для программы. Преобразование дескриптора в указатель блокирует память, а освобождение дескриптора делает недействительным указатель. В этом случае воспринимайте его как индекс в таблице указателей ... вы используете индекс для системных вызовов API, и система может изменить указатель в таблице по своему усмотрению.

Альтернативно, реальный указатель может быть задан в качестве дескриптора, когда разработчик API намеревается изолировать пользователя API от особенностей того, на что указывает возвращаемый адрес; в этом случае следует учитывать, что то, на что указывает дескриптор, может измениться в любое время (от версии API к версии или даже от вызова к вызову API, возвращающего дескриптор) - поэтому дескриптор должен рассматриваться как просто непрозрачное значение смысл только для API.

Я должен добавить, что в любой современной операционной системе даже так называемые «реальные указатели» по-прежнему являются непрозрачными дескрипторами в пространстве виртуальной памяти процесса, что позволяет операционным системам управлять и переставлять память без аннулирования указателей в процессе. ,

Лоуренс Дол
источник
4
Я действительно ценю быстрый ответ. К сожалению, я думаю, что я все еще слишком новичок, чтобы полностью понять это :-(
Al C
4
Пролил ли мой расширенный ответ какой-либо свет?
Лоуренс Дол
100

A HANDLE- это специфический для контекста уникальный идентификатор. Под контекстно-зависимым я подразумеваю, что дескриптор, полученный из одного контекста, не обязательно может быть использован в любом другом контексте aribtrary, который также работает на HANDLEs.

Например, GetModuleHandleвозвращает уникальный идентификатор для загруженного в данный момент модуля. Возвращенный дескриптор может использоваться в других функциях, которые принимают дескрипторы модуля. Это не может быть дано функциям, которые требуют других типов ручек. Например, вы не можете дать дескриптор, возвращенный из GetModuleHandleв, HeapDestroyи ожидать, что он сделает что-то разумное.

Сам по HANDLEсебе просто интегральный тип. Обычно, но не обязательно, это указатель на некоторый базовый тип или область памяти. Например, HANDLEвозвращаемое значение на GetModuleHandleсамом деле является указателем на базовый адрес виртуальной памяти модуля. Но нет правила, утверждающего, что дескрипторы должны быть указателями. Дескриптор также может быть простым целым числом (которое может быть использовано некоторыми Win32 API в качестве индекса в массиве).

HANDLEЭто намеренно непрозрачные представления, которые обеспечивают инкапсуляцию и абстрагирование от внутренних ресурсов Win32. Таким образом, Win32 API могли бы потенциально изменить базовый тип за HANDLE, без какого-либо влияния на пользовательский код (по крайней мере, в этом идея).

Рассмотрим эти три разные внутренние реализации Win32 API, которые я только что составил, и предположим, что Widgetэто 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);
}

В первом примере раскрываются внутренние подробности об API: он позволяет пользовательскому коду знать, что GetWidgetвозвращает указатель на a struct Widget. Это имеет несколько последствий:

  • код пользователя должен иметь доступ к заголовочному файлу, который определяет Widgetструктуру
  • пользовательский код может потенциально изменить внутренние части возвращаемой Widgetструктуры

Оба эти последствия могут быть нежелательными.

Второй пример скрывает эту внутреннюю деталь от пользовательского кода, возвращая только void *. Код пользователя не нуждается в доступе к заголовку, который определяетWidget структуру.

Третий пример точно такой же, как и второй, но вместо этого мы просто называем void *a HANDLE. Возможно, это препятствует тому, чтобы пользовательский код пытался выяснить, на что именно void *указывает.

Зачем переживать эту проблему? Рассмотрим этот четвертый пример более новой версии этого же API:

typedef void * HANDLE;

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

    w = findImprovedWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

Обратите внимание, что интерфейс функции идентичен третьему примеру выше. Это означает, что пользовательский код может продолжать использовать эту новую версию API без каких-либо изменений, даже несмотря на то, что реализация «за кулисами» изменилась для использования NewImprovedWidgetструктуры вместо этого.

Дескрипторы в этом примере на самом деле просто новое, по-видимому, более дружелюбное название void * , которое в точности совпадает HANDLEс Win32 API (посмотрите на MSDN ). Он обеспечивает непрозрачную стену между пользовательским кодом и внутренними представлениями библиотеки Win32, что увеличивает переносимость между версиями Windows кода, использующего Win32 API.

Дэн Молдинг
источник
5
Преднамеренно или нет, вы правы - концепция, безусловно, непрозрачна (по крайней мере для меня :-)
Al C
5
Я расширил свой оригинальный ответ некоторыми конкретными примерами. Надеюсь, это сделает концепцию более прозрачной.
Дэн Молдинг
2
Очень полезное расширение ... Спасибо!
Al C
4
Это должен быть один из самых чистых, прямых и наиболее хорошо написанных ответов на любой вопрос, который я когда-либо видел. Спасибо вам за то, что нашли время написать это!
Андрей
@DanMoulding: Таким образом, основная причина использовать handleвместо этого void *- отговаривать пользовательский код от попыток точно определить, на что указывает пустота * . Я прав?
Лев Лай
37

РУЧКА в программировании Win32 - это токен, представляющий ресурс, управляемый ядром Windows. Дескриптор может быть окном, файлом и т. Д.

Дескрипторы - это просто способ идентифицировать частичный ресурс, с которым вы хотите работать, используя Win32 API.

Например, если вы хотите создать окно и показать его на экране, вы можете сделать следующее:

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

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

В приведенном выше примере HWND означает «дескриптор окна».

Если вы привыкли к объектно-ориентированному языку, вы можете думать о HANDLE как об экземпляре класса без методов, чье состояние может быть изменено только другими функциями. В этом случае ShowWindow функция изменяет состояние окна HANDLE.

Посмотрите Ручки и Типы данных для получения дополнительной информации.

Ник Хаддад
источник
Объекты, на которые ссылаются через HANDLEADT, управляются ядром. Другие типы дескрипторов, которые вы называете ( HWNDи т. Д.), С другой стороны, являются объектами USER. Они не управляются ядром Windows.
II
1
@IInspectable угадывать те, которые управляются вещи User32.dll?
the_endian
8

Дескриптор - это уникальный идентификатор объекта, управляемого Windows. Это как указатель , но не указатель в том смысле, что это не адрес, который может быть разыменован пользовательским кодом для получения доступа к некоторым данным. Вместо этого дескриптор должен быть передан в набор функций, которые могут выполнять действия над объектом, который идентифицирует дескриптор.

Sharptooth
источник
5

Таким образом, на самом базовом уровне РУЧКА любого рода - это указатель на указатель или

#define HANDLE void **

Теперь о том, почему вы хотели бы использовать его

Давайте сделаем настройку:

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

Так как obj был передан по значению (сделайте копию и передайте это функции) в foo, printf выведет исходное значение 1.

Теперь, если мы обновим foo до:

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

Существует вероятность, что printf напечатает обновленное значение 2. Но также существует вероятность того, что foo вызовет некоторую форму повреждения памяти или исключения.

Причина в том, что теперь, когда вы используете указатель для передачи obj функции, которой вы также выделяете 2 мегабайта памяти, это может привести к тому, что ОС будет перемещать память вокруг обновления местоположения obj. Поскольку вы передали указатель по значению, при перемещении объекта obj ОС обновляет указатель, но не копию в функции, что может вызвать проблемы.

Последнее обновление для foo:

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

Это всегда будет печатать обновленное значение.

Видите, когда компилятор выделяет память для указателей, он помечает их как неподвижные, поэтому любая перестановка памяти, вызванная выделением большого объекта, значению, переданному функции, будет указывать на правильный адрес, чтобы узнать окончательное местоположение в памяти для Обновить.

Любые конкретные типы HANDLE (hWnd, FILE и т. Д.) Зависят от домена и указывают на определенный тип структуры для защиты от повреждения памяти.


источник
1
Это ошибочное рассуждение; подсистема выделения памяти C не может просто аннулировать указатели по желанию. Иначе никакая программа на C или C ++ никогда не будет доказуемо правильной; хуже того, что любая программа достаточной сложности будет явно некорректной по определению. Кроме того, двойное косвенное обращение не помогает, если память, на которую непосредственно указана память, перемещается под программой, если указатель фактически не является абстракцией от реальной памяти - что сделало бы ее ручкой .
Лоуренс Дол
1
Операционная система Macintosh (в версиях до 9 или 8) делала в точности то же самое. Если вы выделяете какой-либо системный объект, вы часто получаете к нему дескриптор, оставляя ОС свободной для перемещения объекта. С ограниченным объемом памяти первых компьютеров Mac это было довольно важно.
Риальто поддерживает Монику
5

Дескриптор подобен значению первичного ключа записи в базе данных.

правка 1: хорошо, почему downvote, первичный ключ однозначно идентифицирует запись в базе данных, а дескриптор в системе Windows однозначно идентифицирует окно, открытый файл и т. д. Это то, что я говорю.

Эдвин Йип
источник
1
Я не думаю, что вы можете утверждать, что ручка уникальна. Он может быть уникальным для каждой пользовательской Windows Station, но он не гарантированно будет уникальным, если несколько пользователей одновременно получают доступ к одной и той же системе. То есть, несколько пользователей могут получить обратно значение дескриптора, которое численно идентично, но в контексте пользовательской Windows Station они отображаются на разные вещи ...
Ник
2
@nick Это уникально в данном контексте. Первичный ключ также не будет уникальным для разных таблиц ...
Бенни Макни
2

Думайте об окне в Windows как о структуре, которая его описывает. Эта структура является внутренней частью Windows, и вам не нужно знать ее детали. Вместо этого Windows предоставляет typedef для указателя на структуру для этой структуры. Это «ручка», с помощью которой вы можете удерживать окно.,

Чарли Мартин
источник
Это правда, но всегда стоит помнить, что дескриптор обычно не является адресом памяти, и один пользовательский код не должен разыменовывать его.
Sharptooth