handle что это в программировании
Handle Класс
Определение
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Представляет тип, время существования которого управляется средой выполнения рабочего процесса.
Комментарии
Среда выполнения рабочего процесса управляет временем существования типов, производных от Handle. Для переменных рабочего процесса, использующих типы обработки, среда выполнения использует необходимый конструктор без параметров для создания экземпляра типа, присваивает его Variable и вызывает OnInitialize при инициализации среды. Когда переменная выходит за пределы области, вызывается метод OnUninitialize.
Конструкторы
Инициализирует новый экземпляр класса Handle.
Свойства
Возвращает имя, используемое этим дескриптором Handle при добавлении к свойствам выполнения действия.
Возвращает экземпляр ActivityInstance, содержащий переменную Variable, которая содержит дескриптор Handle.
Методы
Определяет, равен ли указанный объект текущему объекту.
Служит хэш-функцией по умолчанию.
Возвращает объект Type для текущего экземпляра.
Создает неполную копию текущего объекта Object.
Вызывается средой выполнения рабочего процесса, когда инициализируется среда, содержащая Handle.
Вызывается средой выполнения рабочего процесса, когда переменная Variable для Handle выходит за пределы области.
Вызывает исключение InvalidOperationException, если дескриптор Handle не инициализирован.
Возвращает строку, представляющую текущий объект.
Что такое дескриптор в C++?
Мне сказали, что дескриптор-это своего рода указатель, но нет, и что он позволяет вам сохранять ссылку на объект, а не сам объект. Что такое более подробное объяснение?
7 ответов:
дескриптор может быть любым от целочисленного индекса до указателя на ресурс в пространстве ядра. Идея заключается в том, что они обеспечивают абстракцию ресурса, поэтому вам не нужно много знать о самом ресурсе, чтобы использовать его.
например, HWND в Win32 API является дескриптором для окна. Само по себе это бесполезно: вы не можете извлечь из него никакой информации. Но передайте его в правильные функции API, и вы можете выполнять с ним множество различных трюков. Внутренне вы можете подумайте о HWND как просто индекс в таблице графического интерфейса windows (что может быть не обязательно так, как это реализовано, но это имеет смысл).
EDIT: не на 100% уверен, что конкретно вы задавали в своем вопросе. В основном речь идет о чистом C/C++.
дескриптор-это указатель или индекс, к которому не прикреплен видимый тип. Обычно вы видите что-то вроде:
поэтому в вашем коде вы просто передаете дескриптор как непрозрачное значение.
в коде, который использует объект, он приводит указатель к реальному типу структуры и использует его:
или он использует его в качестве индекса для массива / вектора:
дескриптор и своего рода указатель в том, что это обычно способ ссылки на некоторую сущность.
точнее было бы сказать, что указатель-это один тип дескриптора, но не все дескрипторы являются указателями.
например, дескриптор может также быть некоторым индексом в таблице в памяти, которая соответствует записи, которая сама содержит указатель на некоторый объект.
главное, что когда у вас есть «ручка», вы не знаете и не заботитесь о том, как эта ручка на самом деле заканчивается идентификацией того, что она идентифицирует, все, что вам нужно знать, это то, что она делает.
также должно быть очевидно, что нет единого ответа на вопрос «что именно является ручкой», потому что ручки для разных вещей, даже в одной и той же системе, могут быть реализованы по-разному «Под капотом». Но вам не нужно беспокоиться об этих различиях.
В C++/CLI дескриптор-это указатель на объект, расположенный в куче GC. Создание объекта в (неуправляемой) куче C++ достигается с помощью new и результат a new выражение является» нормальным » указателем. Управляемый объект выделяется в куче GC (managed) с помощью gcnew выражение. Результатом будет дескриптор. Вы не можете сделать арифметику указателя на дескрипторах. Вы не освобождаете ручки. О них позаботится ГК. Кроме того, GC может свободно перемещать объекты в управляемой куче и обновите маркеры, чтобы они указывали на новые местоположения во время работы программы.
Это появляется в контексте Handle-Body-Idiom, также называемые идиомы Pimpl. Это позволяет сохранить ABI (двоичный интерфейс) библиотеки тем же самым, сохраняя фактические данные в другой объект класса, на который просто ссылается указатель, удерживаемый в объекте «дескриптор», состоящий из функций, которые делегируют этому классу «тело».
также полезно включить постоянное время и исключение безопасной замены двух объектов. Для этого просто указатель, указывающий на объект тела должен быть заменен.
HANDLE-это тип, определенный в winnt.H файл в visual studio (windows).
Я.с: typedef void *HANDLE;
дескриптор будет таким, каким вы хотите его видеть.
дескриптор может быть целым числом без знака, используемым в некоторой таблице подстановки.
дескриптор может быть указателем на больший набор данных или в него.
Это зависит от того, как ведет себя код, который использует дескриптор. Это определяет тип дескриптора.
Почему термин ‘дескриптор ‘ используется то, что важно. Это указывает на них как на тип идентификации или доступа к объекту. В смысле, к программист, они представляют собой ‘ключ’ или доступ к чему-то.
Что такое дескриптор Windows?
Что такое «дескриптор» при обсуждении ресурсов в Windows? Как они работают?
Это абстрактное значение ссылки на ресурс, часто в память, открытый файл или канал.
Например, GetModuleHandle возвращает уникальный идентификатор для загруженного в данный момент модуля. Возвращенный дескриптор может использоваться в других функциях, которые принимают дескрипторы модуля. Это не может быть дано функциям, которые требуют других типов ручек. Например, вы не можете дать дескриптор, возвращенный из GetModuleHandle в, HeapDestroy и ожидать, что он сделает что-то разумное.
Сам по HANDLE себе просто интегральный тип. Обычно, но не обязательно, это указатель на некоторый базовый тип или область памяти. Например, HANDLE возвращаемое значение на GetModuleHandle самом деле является указателем на базовый адрес виртуальной памяти модуля. Но нет правила, утверждающего, что дескрипторы должны быть указателями. Дескриптор также может быть простым целым числом (которое может быть использовано некоторыми Win32 API в качестве индекса в массиве).
HANDLE Это намеренно непрозрачные представления, которые обеспечивают инкапсуляцию и абстрагирование от внутренних ресурсов Win32. Таким образом, Win32 API могли бы потенциально изменить базовый тип за HANDLE, без какого-либо влияния на пользовательский код (по крайней мере, в этом идея).
Оба эти последствия могут быть нежелательными.
Зачем переживать эту проблему? Рассмотрим этот четвертый пример более новой версии этого же API:
Обратите внимание, что интерфейс функции идентичен третьему примеру выше. Это означает, что пользовательский код может продолжать использовать эту новую версию API без каких-либо изменений, даже несмотря на то, что реализация «за кулисами» изменилась для использования NewImprovedWidget структуры вместо этого.
Использование handle и intrusive reference counter-ов в многопоточных средах в языке C
Доступ к одим и тем же данным в нескольких потоках считается плохой практикой, но во многих случаях это неизбежно, и это не тот вопрос, который обсуждается здесь. Вопрос который здесь обсуждается, это как организовать такой доступ наиболее безопасным способом. Также тут не обсуждаются атомарные операции, которые тут упоминаются: разные компиляторы предлагают различные средства для таких операций.
В многопоточной среде при использовании объекта или структуры данных, один из главных вопросов, помимо прочего, это гарантия того, что объект к которому производится доступ все еще жив и память, выделенная для структуры не освобождена.
Это может быть сделано несколькими способами, но мы будем говорить только о двух из них: хэндлы (handles) и встроенные счётчики ссылок (intrusive reference counters).
Хэндлы — это небольшие структуры, которые содержат указатель на объект данных и вспомогательные данные, чтобы гарантировать, что объект еще жив. Как правило для работы с хэндлами пишутся две функции: lock_handle и unlock_handle (имена выбраны произвольно, чтобы показать функциональность). Lock_handle проверяет «живость» объекта, увеличивает атомарный счетчик ссылок и возвращает указатель на объект данных, если он ещё доступен. Если нет, то функция возвращает NULL, или с помощью другого способа даёт знать, что объект больше не доступен. Соответственно своему названию, unlock_handle атомарно уменьшает счетчик ссылок и как только он достигает значения 0, удаляет объект.
Встроенные счетчики ссылок — это атомарные числовые переменные внутри объекта данных, которые считают количество ссылок в программе на указанный объект данных. Как только счетчик ссылок достигает значения 0, объект удаляется.
Теперь давайте взглянем на преимущества и распространенные ошибки обеих стратегий и определим, какую из них лучше использовать в конкретных случаях. Сперва давайте разберемся с встроенными счетчиками ссылок.
Встроенные счетчики ссылок
Встроенные счетчики ссылок, как понятно из названия, должны быть встроены в структуру данных: это атомарное целое число. В простой структуре, назовем ее data_packet_s, подобная модификация может выглядеть так:
Именно это является главным недостатком этого подхода: он нуждается в модификации структуры данных, поэтому использовать его можно только для таких структур, которые мы можем изменить. Мы не можем использовать его с произвольным типом данных или структурами, например, библиотечными.
Любопытно, но это же факт является и преимуществом. Преимущество состоит в том, что нам не нужны никакие дополнительные структуры и дополнительное выделение памяти для подобных структур.
Еще один недостаток, или, скорее, специфика этого подхода заключается в следующем: доступность объекта должна быть гарантирована, когда происходит приращение счетчика ссылок. Другими словами, мы не можем просто хранить указатель на объект и дожидаться момента, когда мы хотим получить к нему доступ, и затем просто увеличить счетчик ссылок и далее работать с объектом, поскольку между моментом когда мы сохранили указатель и моментом когда мы начинаем использовать объект он может быть уничтожен, при этом уничтожается и счётчик ссылок. Давайте продемонстрируем простой случай такого события, используя простой условный язык.
В указанном случае, чтобы обеспечить целостность объекта, придется увеличить количество ссылок в потоке 1 и передать полученную ссылку в собственность потоку 2 (уменьшать reference_count будет поток 2 после того как объект больше не нужен). Другими словами, эта схема может работать очень хорошо, если необходимо обработать объект в другом потоке, и забыть о нем. Это может быть продемонстрировано с помощью следующей иллюстрации:
Такая схема может быть использована любым количеством потоков при условии, что каждый поток, который увеличивает счетчик ссылок, уже владеет по крайней мере одним экземпляром-ссылкой (либо увеличил количество ссылок и не передал собственность, либо получил экземпляр-ссылку в собственность от другого потока). Таким образом, в последнем случае поток 1 может увеличить счетчик ссылок несколько раз (по одному для каждого потока) и лишь после этого он может запустить несколько потоков (столько, на сколько был увеличен счетчик ссылок.
Тут становится виден и второй недостаток: мы увеличивать и уменьшаем счетчик ссылок в разных потоках, что часто может приводить к различным ошибкам, начиная от утечек памяти когда ссылка не освобождается, заканчивая двойным освобождением ссылки и соответственно ложным освобождением объекта, что ведёт к его удалению во время того как он ещё используется в другом потоке или потоках.
Эта схема реализует так называемый «shared ownership», когда потоки совместно владеют объектом, и нить, уменьшающая счетчик ссылок до нуля, будет удалять объект.
И если вы хотите сохранить указатель на объект в одном из потоков, и использовать его при необходимости вы увидите, что память выделенная вашей программой будет расти с каждым хранящимся объектом, потому что поток никогда не освободит ссылку на объект, «живость» которого он хочет гарантировать.
Другая возможная ошибка в таком подходе это обращение к косвенно переданным объектам, то есть объектам, которые передаются по указателю, содержащемуся в одном из других, прямо или косвенно переданных объектов, которые ссылаются, но не владеют объектом. Все такие объекты должны быть известны, при этом должен быть обеспечен безопасный доступ ко всем подобным объектам, если они используются наряду с основным.
Итак, давайте просуммируем недостатки и преимущества встроенных счетчиков ссылок:
Хэндлы
Хэндлы — это легкие структуры, которые передаются по значению, они ссылаются на объект, управляют ссылками и обеспечивают целостность объекта.
Очевидно, что хэндлы, которые ссылаются на один и тот же объект, будут иметь указатель на один счетчик ссылок. Как правило, это означает, что счетчик ссылок должен быть выделен на куче динамически. Простейшая структура хэндла, которая первой приходит на ум, может выглядеть так:
Где reference_count выделяется при создании первого хэндла на данный объект.
Первый недостаток такого подхода уже очевиден. Мы должны управлять еще одной дополнительной структурой, выделять еще одну область памяти для счетчика ссылок. Но это окупается, если вы хотите использовать подсчет ссылок со структурами, к которым вы не имеете доступа.
Типичное применение такого хэндла будет следующим:
Давайте посмотрим, что происходит, когда последняя ссылка на объект удалена. Прежде всего, мы, очевидно, хотим, чтобы удалился управляемый объект. Если объект является простым куском выделенной памяти, мы просто хотим, чтобы память, используемая объектом была освобождена. Но в большинстве случаев это не так. Как и в вышеупомянутом примере data_packet_s, где мы хотели бы освободить также и память data_buffer. Если мы используем хэндл для только одного типа объектов, это не создаёт большой проблемы. Но если мы хотим, чтобы хэндл мог обрабатывать разыличные типы, это привносит ещё один вопрос: как правильно удалить управляемый объект?
Мы можем добавить в хэндл ещё одно поле: указатель на функцию, которая будет использоваться для уничтожения/освобождения управляемого объекта. Теперь хэндл выглядит следующим образом:
Теперь release_handle не обязательно знать специфику объекта, чтобы удалить его, он просто будет использовать функцию, которую мы сохранили в хэндле, так что давайте вернёмся к тому, что происходит, когда последняя ссылка будет освобождена.
После этого мы хотели бы освободить память, которая содержит reference_count. Но не тут то было: сделать это будет ужасной ошибкой. Если другие хэндлы на этот же объект до сих пор хранятся в других потоках, после освобождения памяти они будут ссылаться на reference_count, который уже удален. А на следующей попытке получить доступ к объекту, мы будем иметь попытку обращения к освобожденной памяти.
Есть ли решение, которое позволило бы не допустить утечки памяти но при этом избежать обращений к освобождённой памяти счетчиков ссылок? Такой способ есть. Это пул объектов, который будут управлять освобожденными счетчиками. И вот тут-то и появляется проблема, найти которую может быть довольно непросто, и которая известна как «проблема ABA». Представьте себе, ситуацию, когда у вас есть хэндл на объект. Один из потоков удаляет объект. Затем конструируется другой объект, и для него создается управляющих хэндл. Что при этом произойдет?
Когда объект уничтожен, reference_count связанный с данным объектом (назовем его object1) освобождается обратно в пул объектов со значением 0. Пока что всё идёт по плану. Но когда выделяется другой хэндл для нового объекта (назовем его object2), то reference_count, который будет связан с этим объектом берется из пула объектов, при этом данный reference_count устанавливается в 1. Теперь представьте, что поток хранящий хэндл на object1 пытается получить указатель. Это удастся, потому что reference_count на который указывает данный хэндл уже не 0, хотя он и принадлежит теперь object2. Функция блокировки вернет неверный указатель, программа (если повезёт) потерпит крушение, или (если не повезёт) повредит содержимое памяти обращением к освобожденному участку.
Решение, разумеется, существует, иначе я бы не писал всё это.
Мы хотим сделать структуру handle_s настолько легкой, насколько это возможно, чтобы иметь возможность передать ее по значению, а не по указателю, так что сделаем следующее: создадим две структуры, одна из которых будет «слабым» хэндлом, то есть не ограничивать и не проверять объект, которым она управляет, а другой будет «сильным» хэндлом, т.е. таким, который будет иметь жесткую связь с конкретным управляемым объектом и вернет NULL, если «слабый» хэндл связанный с ним больше не ссылается на тот же объект.
Давайте определим их так:
Итак, как вы видите, теперь обе ручки имеют поле «version», и strong_handle_s уже не имеет указателя на объект, так как он теперь хранится в общем для объекта weak_handle_s.
Давайте посмотрим, как он защищает нас от проблемы ABA, показанной выше.
В strong_handle_s и weak_handle_s, которые ссылаются на один и тот же объект имеются поля «version», которые равны друг другу.
Всякий раз, когда ручка освобождается и weak_handle_s помещается обратно в пул объектов, мы будем увеличивать номер версии в weak_handle_s.
В следующий раз, если освобожденный в пул weak_handle_s переиспользуется для обработки другого объекта, он будет иметь номер версии, отличный от номера версии который был у объекта, который был освобожден. Теперь в функции lock_handle, сравнивая поля версии в обоих, «слабом» и «сильном» хэндле, мы можем сказать, ссылается ли weak_handle_s по-прежнему на тот объект, указатель которого мы пытаемся получить из strong_handle_s, и вернуть NULL, если это не так.
Так что, как мы видим, хэндлы приносят некоторые довольно сложные проблемы, но он также имеет и свои плюсы: хэндл может быть сохранен и забыт, пока нам не понадобится управляемый им объект; хэндл не является встроенным, что означает, что мы можем использовать его практически с любыми типами данных.
Кроме того, передача хэндла не обязательно означает передачу права собственности на объект, так что он может быть использован вместо указателя в качестве безопасной ссылки на объекты везде, где они не являются собственностью ссылающегося объекта (в структурах данных и т.д.).
Таким образом хэндлами сложнее управлять. Но и возможности их гораздо мощнее.
Выводы
Встроенный счетчик ссылок реализует общие сильные ссылки (strong reference), которые должны удерживать счетчик ссылок, пока объект не будет гарантированно невостребован. Пока все ссылки не будут разлочены, объект не будет удален. Если ссылка разблокирована в потоке, который имел в собственности лишь одну ссылку, этот поток уже больше не сможет гарантированно безопасно получить еще одну ссылку.
Хэндл реализует общую слабую ссылку (weak reference). Если объект жив, функция lock_handle вернет указатель на запрашиваемый объект, и объект гарантированно не будет удален, пока хэндл не будет разлочен. Это безопасно для блокировки и разблокировки сколько угодно раз, так как хэндл является отдельным объектом, и гарантированно является валидным участком памяти. Соответствующие меры должны быть приняты, чтобы гарантировать, что разделяемая память счетчика ссылок не освобождается, когда объект достигает счетчика ссылок 0, и что повторно используемый счетчик ссылок не используется, чтобы проверить количество ссылок на уже освобожденные объекты.
Что такое Method Handles в Java
1. Вступление
В этом туториале мы рассмотрим важный API, представленный в Java 7 и расширенный в новых версиях, java.lang.invoke.MethodHandles.
Мы узнаем, что такое method handles, как их создавать и использовать.
2. Что такое Method Handles?
В документации API method handle имеет такое определение:
Method handle — это типизированная, исполняемая ссылка на базовый метод, конструктор, поле или другую низкоуровневую операцию с дополнительными трансформациями аргументов или возвращаемых значений.
Другими словами, method handles — это низкоуровневый механизм для поиска, адаптации и вызова методов. Объекты method handles неизменяемые и не имеют отображаемого состояния.
Для создания и использования MethodHandle нужно выполнить 4 действия:
2.1. Method Handles vs Reflection
Method handles были представлены для функционирования наряду с java.lang.reflect API, т.к. они созданы для разных целей и отличаются по своим характеристикам.
С точки зрения производительности, MethodHandles API может оказаться намного быстрее Reflection API, поскольку проверки доступа выполняются во время создания, а не исполнения. При наличии security manager’а это различие увеличивается, т.к. поиск классов и получение их элементов подвергаются дополнительным проверкам.
Однако, производительность — не единственный показатель оптимальности задачи, нужно учитывать, что MethodHandles API сложнее в использовании из-за недостатка таких механизмов, как получение методов класса, проверка маркеров доступа и др.
Несмотря на это, MethodHandles API дает возможность каррировать методы, менять тип и порядок параметров.
Теперь, зная определение и предназначение MethodHandles API, можем работать с ними. Начнем с поиска методов.
3. Создание Lookup
Первое, что нужно сделать, когда мы хотим создать method handle, — это получить lookup, объект-фабрику, отвечающий за создание method handles для методов, конструкторов и полей, видимых для класса lookup.
С помощью MethodHandles API можно создать lookup-объект с разными режимами доступа.
Создадим lookup, предоставляющий доступ к public-методам:
Однако, если нам нужен доступ к методам private и protected, вместо этого мы можем использовать метод lookup():
4. Создание MethodType
Для создания MethodHandle lookup-объекту необходимо задать тип, и это можно сделать с помощью класса MethodType.
В частности, MethodType представляет аргументы и тип возвращаемого значения, принимаемые и возвращаемые method handle, или передаваемые и ожидаемые вызывающим кодом.
Структура MethodType проста, она формируется возвращаемым типом вместе с соответствующим числом типов параметра, которые должны полностью соотноситься между method handle и вызывающим кодом.
Так же, как и MethodHandle, все экземпляры MethodType неизменяемы.
Посмотрим, как определить MethodType, задающий класс java.util.List в качестве типа возвращаемого значения и массив Object в качестве типа ввода данных:
Определим MethodType, который возвращает значение int и принимает Object:
Можно приступать к созданию MethodHandle.
5. Поиск MethodHandle
После того, как мы задали тип метода, для создания MethodHandle нужно найти его с помощью объекта lookup или publicLookup, который также выдает исходный класс и имя метода.
Lookup предоставляет набор методов, позволяющий находить method handle оптимальным способом с учетом области видимости метода. Рассмотрим основные подходы, начиная с простейших.
5.1. Method Handle для методов
С помощью метода findVirtual() можно создать MethodHandle для метода экземпляра. Создадим его на основе метода concat() класса String :
5.2. Method Handle для статических методов
Для получения доступа к статическому методу можно использовать метод findStatic() :
5.3. Method Handle для конструкторов
Создадим method handle с поведением, как у конструктора класса Integer с параметром String:
5.4. Method Handle для полей
С помощью method handle можно также получить доступ к полям.
Начнем с определения класса Book:
В качестве исходного условия мы имеем прямую видимость между method handle и объявленным свойством, таким образом, можно создать method handle с поведением как у get-метода:
Более подробную информацию об управлении переменными/полями ищите в статье Java 9 Variable Handles Demystified, где мы рассказываем о java.lang.invoke.VarHandle API, введенном в Java 9.
5.5. Method Handle для Private методов
Создать method handle для метода типа private можно с помощью java.lang.reflect API.
Начнем с того, что создадим private метод для класса Book:
Теперь мы можем создать method handle с поведением метода formatBook() :
6. Вызов Method Handle
6.1. Вызов Method Handle
При использовании метода invoke() количество аргументов (arity) фиксируется, но при этом возможно выполнение приведения типов и упаковка/распаковка аргументов и типов возвращаемого значения.
Теперь посмотрим, как можно использовать invoke() с упакованным аргументом:
6.2. Вызов с аргументами
Вызов method handle с помощью метода invokeWithArguments имеет меньше всего ограничений.
По сути, помимо проверки типов и упаковки/распаковки аргументов и возвращаемых значений, он позволяет делать вызовы с переменным числом параметров.
На практике мы можем создать список Integer, имея массив значений int неизвестной длины:
6.3. Вызов Exact
Фактически, он не предоставляет возможность приведения типов класса и требует фиксированного набора аргументов.
Посмотрим, как можно выполнить сложение двух значений int с помощью method handle:
7. Работа с массивами
MethodHandles могут работать не только с полями и объектами, но и с массивами. При помощи asSpreader() API можно создать method handle, поддерживающий массивы в качестве позиционных аргументов.
В этом случае method handle принимает массив, распределяя его элементы как позиционные аргументы, и опционально — длину массива.
Посмотрим, как получить method handle, чтобы проверить, являются ли аргументы массива одинаковыми строками:
8. Уточнение Method Handle
Как только method handle задан, можно уточнить его, привязав к аргументу, без вызова метода.
Например, в Java 9 этот трюк используется для оптимизации конкатенации строк.
Посмотрим, как можно выполнить конкатенацию, привязав суффикс к concatMH :
9. Обновления Java 9
В Java 9 было внесено несколько изменений в MethodHandles API, чтобы упростить их использование.
Обновления касаются 3 основных аспектов:
Эти изменения повлекли за собой другие полезные нововведения:
Более подробный список изменений доступен в MethodHandles API Javadoc.
10. Заключение
В этой статье мы познакомились с MethodHandles API, а также узнали, что из себя представляют Method Handles и как их использовать.
Мы также описали, как он связан с Reflection API. Так как вызов method handles это довольно низкоуровневая операция, их использование оправдано только в том случае, если они в точности подходят под ваши задачи.
Как обычно, весь исходный код для статьи доступен на Github.