Що таке ручка для Windows?


153

Що таке "Ручка" при обговоренні ресурсів у Windows? Як вони працюють?

Відповіді:


167

Це абстрактне довідкове значення для ресурсу, часто пам'яті, відкритого файлу або труби.

Власне , в Windows (і взагалі в обчислювальній машині) ручка - це абстракція, яка приховує реальну адресу пам'яті від користувача API, що дозволяє системі прозоро реорганізувати фізичну пам'ять у програму. Розв’язання ручки в покажчику блокує пам'ять, а звільнення ручки недійсно вказує. У цьому випадку подумайте про це як на індекс у таблиці покажчиків ... ви використовуєте індекс для системних викликів API, і система може змінити вказівник у таблиці за бажанням.

Альтернативно, в якості ручки може бути заданий реальний покажчик, коли автор програми планує, щоб користувач API був ізольований від специфіки того, на що вказується адреса; в цьому випадку слід враховувати, що те, що вказує на ручку, може змінитися в будь-який час (від версії API до версії або навіть від виклику до виклику API, який повертає ручку) - тому ручка повинна трактуватися як просто непрозоре значення має значення лише для API.

Додам, що в будь-якій сучасній операційній системі навіть так звані «реальні покажчики» все ще залишаються непрозорими ручками у просторі віртуальної пам’яті, що дозволяє O / S керувати і переставляти пам’ять, не виключаючи покажчики в процесі .


4
Я дуже ціную швидку відповідь. На жаль, я думаю, що я все ще надто багато новачків, щоб повністю зрозуміти це :-(
Al C

4
Чи проливає моя розгорнута відповідь?
Лоуренс Дол,

100

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

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

Сам HANDLEпо собі є лише інтегральним типом. Зазвичай, але не обов'язково, це вказівник на деякий базовий тип або місце пам'яті. Наприклад, що HANDLEповертається, GetModuleHandle- це фактично вказівник на базову адресу віртуальної пам'яті модуля. Але не існує жодного правила, яке зазначає, що ручки повинні бути покажчиками. Ручка може бути просто простим цілим числом (яке, можливо, може бути використане деяким API Win32 як індекс в масив).

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

Розглянемо ці три різні внутрішні реалізації API Win32, які я щойно створив, і припустимо, що Widgetце 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);
}

Перший приклад розкриває внутрішні відомості про API: він дозволяє користувачеві коду знати, що GetWidgetповертає вказівник на a struct Widget. Це має пару наслідків:

  • код користувача повинен мати доступ до файлу заголовка, який визначає Widgetструктуру
  • код користувача може потенційно змінювати внутрішні частини повернутої Widgetструктури

Обидва ці наслідки можуть бути небажаними.

Другий приклад приховує цю внутрішню деталь від коду користувача, повертаючи просто void *. Код користувача не потребує доступу до заголовка, який визначає Widgetструктуру.

Третій приклад точно так же , як і другий, але ми просто називаємо void *а , 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є a в API Win32 (подивіться це на MSDN ). Він забезпечує непрозору стіну між кодом користувача та внутрішніми представленнями бібліотеки Win32, що збільшує портативність між версіями Windows коду, що використовує API Win32.


5
Навмисне чи ні, ви праві - концепція, безумовно, непрозора (принаймні, для мене :-)
Al C

5
Я розширив свою оригінальну відповідь деякими конкретними прикладами. Сподіваємось, це зробить концепцію трохи прозорішою.
Дан Ліплення

2
Дуже корисне розширення ... Дякую!
Al C

4
Це має бути однією з найчистіших, прямих та найкраще письмових відповідей на будь-яке запитання, яке я бачив за деякий час. Щиро дякую, що знайшли час, щоб написати це!
Андрій

@DanMoulding: Отже, головна причина використання handleзамість цього void *- відштовхує код користувача від спроб з'ясувати, на що саме вказує пустота * . Я прав?
Лев Лай

37

HANDLE в програмуванні Win32 - це маркер, який представляє ресурс, яким керує ядро ​​Windows. Ручка може бути до вікна, файлу тощо.

Ручки - це просто спосіб визначення ресурсу часток, з яким потрібно працювати з використанням API Win32.

Наприклад, якщо ви хочете створити Вікно і показати його на екрані, ви можете зробити наступне:

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

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

У наведеному вище прикладі HWND означає "ручка до вікна".

Якщо ви звикли до об'єктно-орієнтованої мови, ви можете розглядати HANDLE як екземпляр класу без методів, стан яких можна змінювати лише за допомогою інших функцій. У цьому випадку функція ShowWindow змінює стан WAND HANDLE.

Додаткову інформацію див. У розділі Ручки та типи даних.


Об'єктами, на які посилається HANDLEADT, керує ядро. Інші типи ручки, які ви називаєте ( HWNDтощо), з іншого боку, є об'єктами USER. Вони не керуються ядром Windows.
ІІНеочікувана

1
@IInspectable здогадки, якими керує матеріал User32.dll?
the_endian

8

Ручка - це унікальний ідентифікатор об’єкта, яким керує Windows. Це як вказівник , але не вказівник в сенсі, що це не адреса, яку можна відкинути за допомогою коду користувача, щоб отримати доступ до деяких даних. Замість ручки слід передати набір функцій, які можуть виконувати дії над об'єктом, який ідентифікує ручка.


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 переміщується, ОС оновлює вказівник, але не його копію у функції та потенційно спричиняє проблеми.

Остаточне оновлення до:

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

Це завжди буде надрукувати оновлене значення.

Дивіться, коли компілятор виділяє пам'ять для покажчиків, вона позначає їх як нерухомі, тому будь-яке повторне переміщення пам’яті, спричинене великим об'єктом, що виділяється, значення, передане функції, вказуватиме на правильну адресу, щоб з’ясувати остаточне місце в пам'яті для оновлення.

Будь-які конкретні типи HANDLE (hWnd, FILE тощо) є специфічними для домену і вказують на певний тип структури для захисту від пошкодження пам'яті.


1
Це хибні міркування; підсистема розподілу пам’яті C не може просто зашкодити покажчики за власним бажанням. В іншому випадку жодна програма C або C ++ ніколи не може бути коректно правильною; гірше, що будь-яка програма достатньої складності була б очевидно неправильною за визначенням. Крім того, подвійне непряме не допомагає, якщо пам'ять, на яку безпосередньо вказували, переміщується під програмою, якщо покажчик насправді не є абстракцією від реальної пам’яті - що зробить її ручкою .
Лоуренс Дол

1
Операційна система Macintosh (у версіях до 9 або 8) зробила саме зазначене вище. Якщо ви виділили якийсь системний об’єкт, ви часто отримуєте ручку до нього, залишаючи ОС вільною для переміщення об'єкта. З обмеженим обсягом пам'яті першого Macs це було досить важливо.
Ріалто підтримує Моніку

5

Ручка - це як значення основного ключа запису в базі даних.

редагувати 1: добре, чому downvote, первинний ключ однозначно ідентифікує запис бази даних, а ручка в системі Windows однозначно ідентифікує вікно, відкритий файл тощо. Це те, що я говорю.


1
Я не уявляю, ви можете стверджувати, що ручка унікальна. Він може бути унікальним для користувачів станції Windows, але не гарантується, що він буде унікальним, якщо одночасно є кілька користувачів, які мають доступ до однієї системи. Тобто, декілька користувачів можуть отримати значення ручки, яке є цифровим ідентичним, але в контексті станції Windows користувача вони відображають різні речі ...
Nick

2
@nick Це унікально в даному контексті. Первинний ключ також не буде унікальним між різними таблицями ...
Бенні Макні

2

Подумайте про вікно в Windows як про структуру, яка описує його. Ця структура є внутрішньою частиною Windows, і вам не потрібно знати її деталі. Натомість Windows надає typedef для вказівника на stru для цієї структури. Це "ручка", за яку ви можете утриматись у вікні.,


Щоправда, але завжди варто пам’ятати, що ручка зазвичай не є адресою пам’яті, і один код користувача не повинен її зневажати.
гострий зуб
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.