Контексты RTTI в Delphi 2010: как это работает и как их использовать Delphi 2010 включает в себя расширенную поддержку RTTI, также известную как информация о типах времени выполнения (run-time type info) или рефлексия. Множество подходов в проектированию раньше были доступны только в управляемых языках, таких как C# и Java, так как для них необходима аннотация кода (code annotation) и интроспекция (самоанализ). Теперь это возможно и в мире Delphi. Что интересно в работе RTTI, так это его подход к организации пулов объектов. Delphi - это язык, не использующий "сборку мусора", так что пользователям нужно быть внимательными и уничтожать объекты, когда они больше не нужны, делая это явно, или создавая или используя какие-нибудь схемы владения (объектами), такие как используются TComponent-ом, где владелец (Owner) берет на себя ответственность за уничтожение объектов. Порядок использования информации о типах не очень хорошо сочетается со схемой владения в стиле TComponent. Обычно, работая с RTTI, вам требуется выполнять поиск интересующих вас объектов, что-то с ними делать и продолжить работу дальше. Это означает, что множество объектов могут быть определены для проверки, но в действительности не использоваться. Управление временем существования этих объектов должно быть довольно утомительно, поэтому использован другой подход: единый глобальный RTTI пул объектов. Пока хотя бы один RTTI контекст активен в программе, пул объектов хранит все свои объекты в актуальном состоянии. Когда последний контекст выходит из области видимости - объекты освобождаются. Управление пулом для работы использует запись Delphi, которая содержит ссылку на интерфейс. Когда любой переданный RTTI контекст используется в первый раз, он помещается в эту ссылку на интерфейс. Он помещается туда только в первый раз - однократно, потому что записи Delphi не поддерживают конструкторов по умолчанию, которые к тому же имеют свои собственные проблемы. Например, как вы обрабатываете исключения в конструкторе по умолчанию, во всех точках, где они могут возникнуть? Создание массивов, локальных для потока переменных, глобальных переменных, глобальных переменных в модулях, временных объектов в выражениях, и т. д. Это может стать отвратительным, как иногда C++ это и делает. Таким образом, первое использование создает интерфейс, называющийся токен пула (pool token). Он действует как некий дескриптор со счетчиком ссылок, указывающий на глобальный пул объектов. До тех пор пока этот интерфейс актуален (существует), глобальный пул объектов будет оставаться актуальным. Даже если RTTI контекст куда-то скопирован, встроенная в Delphi логика управления интерфейсами, созданная на базе принципов COM, позволяет быть уверенными, что интерфейс не будет преждевременно удален, счетчик ссылок будет иметь верное значение. И когда RTTI контекст выходет из области видимости, или будучи локальной переменной в функции, которая завершилась, или полем в удаленном объекте, счетчик ссылок уменьшит свое значение. Когда счетчик сслылок достигнет нуля - пул опустошится. Наибольшее преимущество такого подхода состоит в том, что использование RTTI, в сущности, должно быть легким, интуитивно понятным. Нужно только в коде программы объявить переменную соответствующего типа и начать ее использовать: procedure Foo; var ctx: TRttiContext; t: TRttiType; begin t := ctx.GetType(TypeInfo(Integer)); Writeln(t.Name); end; Обратная сторона, тем не менее, состоит в том,что "ленивая" инициализация может вызвать ошибку. Представим такой сценарий: 1. Библиотека A объявляет RTTI контекст A.C 2. Пользовательский код B объявляет RTTI контекст B.C 3. Код B запрашивает некоторые RTTI объекты O из B.C, для того чтобы передать их библиотеке A 4. B.C выходит из области видимости 5. Библиотека A сейчас пытается работать с O, но обнаруживает к своему удивлению, что объекты были преждевременно удалены, даже если A уже имеет RTTI контекст A.C Проблема в том, что A никогда не использовала A.C, потому токен пула не был создан. Когда B.C использовал свой контекст, пул начал свое существование, и объекты O были назначены ему; но, после того как B.C вышел из области видимости, объекты были освобождены. Решение этой проблемы - для библиотеки A, знание того, что она имеет долгоживущий RTTI контекст и она рассчитывает взаимодействовать со сторонним кодом, который создает объекты из ее собственного RTTI контекста и передает их обратно, она должна быть уверена, что для этого долгоживущего контекста создан токен пула. Простой способ сделать это выглядит так: type TFooManager = class FCtx: TRttiContext; // ... constructor Create; // ... end; constructor TFooManager.Create; begin FCtx.GetType(TypeInfo(Integer)); // ... end; Это создаст только необходимый минимум RTTI объектов, которые нужны для представления типа System.Integer, но что более важно, даст уверенность в том, что FCtx имеет токен пула и оставит глобальный RTTI пул в актуальном состоянии. В будущих версиях Delphi, статический метод TRttiContext.Create будет обеспечивать, что возвращаемое им значение получило токен пула; пока это не так. TRttiContext.Create был первоначально определен для того, чтобы сделать запись TRttiContext более похожей на класс для людей незнакомых с идиомой использования интерфейсов для автоматического детерминированного управления временем существования объектов. Соответствующий метод TRttiContext.Free удаляет токен пула, и должен остаться прежним. ------------------------------------------------------------------------------- http://translated.by/you/delphi-2010-rtti-contexts-how-they-work-and-a-usage-note/into-ru/trans/ Original (English): Delphi 2010 RTTI Contexts: how they work, and a usage note (http://blog.barrkel.com/2010/01/delphi-2010-rtti-contexts-how-they-work.html) Translation: © DreamerSole, r3code, TDelphiBlog. translated.by crowd