Раздел 2. TClass Atom
В модуле System.pas TClass определяется следующим образом:
TClass = класс TObject;
Это означает, что TClass — это класс TObject. Поскольку TObject сам по себе является классом, TClass — это так называемый класс классов.
Концептуально TClass — это тип класса, то есть класс. Однако мы знаем, что класс DELPHI представляет собой часть данных VMT. Таким образом, класс можно рассматривать как тип, определенный для элемента данных VMT. Фактически, это тип указателя, указывающий на данные VMT!
В предыдущем традиционном языке C++ тип класса не мог быть определен. После компиляции объекта он фиксируется, структурная информация класса преобразуется в абсолютный машинный код, и полная информация о классе не будет существовать в памяти. Некоторые объектно-ориентированные языки более высокого уровня могут поддерживать динамический доступ и вызов информации о классе, но они часто требуют сложного внутреннего механизма интерпретации и большего количества системных ресурсов. Язык Object Pascal DELPHI вобрал в себя некоторые замечательные особенности объектно-ориентированных языков высокого уровня, сохраняя при этом традиционное преимущество прямой компиляции программ в машинный код, что прекрасно решает проблемы расширенных функций и эффективности программ.
Именно потому, что DELPHI сохраняет в приложении полную информацию о классах, он может предоставлять расширенные объектно-ориентированные функции, такие как преобразование и идентификация классов во время выполнения, в которых ключевую роль играют данные VMT класса. Заинтересованные друзья могут прочитать два процесса сборки AsClass и IsClass в модуле System. Это коды реализации операторов as и is, позволяющие глубже понять классы и данные VMT.
Учитывая тип класса, вы можете использовать его как переменную. Переменную класса можно понимать как специальный объект, и вы можете обращаться к методам переменной класса точно так же, как к объекту. Например: Давайте посмотрим на следующий фрагмент программы:
тип
TSampleClass = класс TSampleObject;
TSampleObject = класс (TObject)
общественный
конструктор Создать;
деструктор Уничтожить;
функция класса GetSampleObjectCount:Integer;
PROcedure GetObjectIndex:Integer;
конец;
вар
аSampleClass: TSampleClass;
аКласс: ТКласс;
В этом коде мы определяем класс TSampleObject и связанный с ним тип класса TSampleClass, а также две переменные класса aSampleClass и aClass. Кроме того, мы также определили конструктор, деструктор, метод класса GetSampleObjectCount и метод объекта GetObjectIndex для класса TSampleObject.
Во-первых, давайте поймем значение переменных класса aSampleClass и aClass.
Очевидно, что вы можете рассматривать TSampleObject и TObject как постоянные значения и присваивать их переменным aClass, точно так же, как присваиваете 123 константных значения целочисленной переменной i. Следовательно, связь между типами классов, классами и переменными класса — это связь между типами, константами и переменными, но на уровне класса, а не на уровне объекта. Конечно, недопустимо напрямую присваивать TObject aSampleClass, поскольку aSampleClass — это переменная класса TSampleObject, производного от TObject, а TObject не содержит всех определений, совместимых с типом TSampleClass. Напротив, присвоение TSampleObject переменной Class является законным, поскольку TSampleObject является производным классом TObject и совместим с типом TClass. Это в точности похоже на отношение присваивания и сопоставления типов объектных переменных.
Затем давайте посмотрим, что такое методы класса.
Так называемый метод класса относится к методу, вызываемому на уровне класса, например к методу GetSampleObjectCount, определенному выше, который представляет собой метод, объявленный с зарезервированным словом class. Методы класса отличаются от методов объекта, вызываемых на уровне объекта. Методы объекта уже знакомы нам, и методы класса всегда используются на уровне доступа и контроля общих характеристик всех объектов класса и централизованного управления объектами. В определении TObject мы можем найти большое количество методов класса, таких как ClassName, ClassInfo, NewInstance и т. д. Среди них NewInstance также определяется как виртуальный, то есть метод виртуального класса. Это означает, что вы можете переписать метод реализации NewInstance в производном подклассе, чтобы особым образом создавать экземпляры объектов этого класса.
Вы также можете использовать идентификатор self в методах класса, но его значение отличается от значения self в методах объекта. Self в методе класса представляет собственный класс, то есть указатель на VMT, тогда как self в методе объекта представляет сам объект, то есть указатель на пространство данных объекта. Хотя методы класса можно использовать только на уровне класса, вы все равно можете вызывать методы класса через объект. Например, метод класса ClassName объекта TObject можно вызвать с помощью оператора aObject.ClassName, поскольку первые 4 байта в пространстве данных объекта, на которое указывает указатель объекта, являются указателями на класс VMT. Напротив, вы не можете вызывать методы объекта на уровне класса, а такие операторы, как TObject.Free, должны быть незаконными.
Стоит отметить, что конструктор — это метод класса, а деструктор — метод объекта!
Что? Конструкторы — это методы класса, а деструкторы — это методы объекта! Была ли какая-то ошибка?
Видите ли, когда вы создаете объект, вы явно используете утверждение, подобное следующему:
aObject := TObject.Create;
Очевидно, что он вызывает метод Create класса TObject. При удалении объекта используйте следующий оператор:
aОбъект.Уничтожить;
Даже если вы используете метод Free для освобождения объекта, метод Destroy объекта вызывается косвенно.
Причина очень проста: до того, как объект создан, он еще не существует, существует только класс. Вы можете использовать методы класса только для создания объектов. Напротив, удаление объекта должно удалить существующий объект. Освобождается объект, а не класс.
Наконец, давайте обсудим вопрос о вымышленных конструкторах.
В традиционном языке C++ виртуальные деструкторы могут быть реализованы, но реализация виртуальных конструкторов представляет собой сложную проблему. Потому что в традиционном языке C++ нет типов классов. Экземпляры глобальных объектов существуют в глобальном пространстве данных во время компиляции, а локальные объекты функций также являются экземплярами, отображаемыми в пространстве стека во время компиляции. Даже динамически создаваемые объекты помещаются в фиксированную структуру классов с помощью нового оператора, выделенного в. пространство кучи, а конструктор — это просто метод объекта, который инициализирует сгенерированный экземпляр объекта. В традиционном языке C++ нет реальных методов класса. Даже если можно определить так называемые статические методы на основе классов, они в конечном итоге реализуются как специальная глобальная функция, не говоря уже о методах виртуального класса, которые могут быть нацелены только на конкретный объект. примеры. эффективный. Поэтому традиционный язык C++ считает, что до того, как будет сгенерирован конкретный экземпляр объекта, невозможно сконструировать сам объект на основе генерируемого объекта. Это действительно невозможно, потому что это создало бы противоречивый парадокс в логике!
Однако именно благодаря ключевым понятиям информации о типах динамических классов, истинно виртуальных методов классов и конструкторов, реализованных на основе классов в DELPHI, виртуальные конструкторы могут быть реализованы. Предметы производятся классами. Предмет подобен растущему младенцу, а класс – его матери. Малыш сам не знает, каким человеком он станет в будущем, но матери используют свои собственные методы воспитания для воспитания разных детей. Люди, принципы те же.
Именно в определении класса TComponent конструктор Create определен как виртуальный, чтобы разные типы элементов управления могли реализовывать свои собственные методы построения. В этом величие таких концепций, как классы, созданные TClass, а также величие DELPHI.
.................................................. ..
Глава 3. Представление времени и пространства в WIN32
Мой старый отец посмотрел на своего маленького внука, играющего с игрушками на земле, а потом сказал мне: «Этот ребенок такой же, как ты, когда ты был маленьким. Он любит разбирать вещи и останавливается, только досмотрев их до конца». Вспоминая то время, когда я был ребенком, я часто разбирал игрушечные машинки, маленькие будильники, музыкальные шкатулки и т. д., и мама часто меня ругала.
Впервые я понял основные принципы работы компьютеров, когда разобрал музыкальную шкатулку. Это было в комиксе, когда я учился в старшей школе. Старик с белой бородой объяснял теорию умных машин, а дядя с усами рассказывал о компьютерах и музыкальных шкатулках. Они сказали, что центральный процессор компьютера — это ряд музыкальных тростей, используемых для произношения в музыкальной шкатулке, а компьютерная программа — это плотно расположенные выступы на маленьком цилиндре в музыкальной шкатулке. Вращение маленького цилиндра эквивалентно. к вращению центрального процессора. Естественное движение указателя инструкций, в то время как выступы, изображающие музыку на маленьком цилиндре, управляют вибрацией музыкальной трости для создания инструкций, эквивалентных выполнению программы центральным процессором. Музыкальная шкатулка издает красивую мелодию, которая исполняется в соответствии с партитурой, выгравированной мастером на маленьком цилиндре. Компьютер выполняет сложную обработку на основе программы, заранее запрограммированной программистом. Поступив в колледж, я узнал, что старик с седой бородой был научным гигантом Тьюрингом. Его теория конечных автоматов способствовала развитию всей информационной революции, а дядя с усами был отцом компьютеров фон Нейманом. Компьютерная архитектура по-прежнему остается основной архитектурной структурой компьютеров. Музыкальную шкатулку разобрали не зря, мама может не сомневаться.
Только при простом и глубоком понимании мы можем создавать глубокие и лаконичные творения.
В этой главе мы обсудим основные концепции, связанные с нашим программированием в 32-битной операционной системе Windows, и установим правильное представление времени и пространства в WIN32. Я надеюсь, что после прочтения этой главы мы сможем глубже понять программы, процессы и потоки, понять принципы работы исполняемых файлов, динамически подключаемых библиотек и пакетов времени выполнения, а также ясно увидеть правду о глобальных данных, локальных данных и параметрах в памяти. .
Раздел 1. Понимание процесса
По историческим причинам Windows возникла из DOS. В эпоху DOS у нас всегда была только концепция программы, но не концепция процесса. В то время только обычные операционные системы, такие как UNIX и VMS, имели концепцию процессов, а мультипроцессы означали миникомпьютеры, терминалы и множество пользователей, что также означало деньги. Большую часть времени я мог использовать только относительно дешевые микрокомпьютеры и системы DOS. С процессами и миникомпьютерами я начал знакомиться только тогда, когда изучал операционные системы.
Это было только после Windows 3. Раньше в DOS одновременно могла выполняться только одна программа, а в Windows — несколько программ. Это многозадачность. При запуске программы под DOS одна и та же программа не может быть запущена одновременно, но под Windows одновременно может быть запущено более двух копий одной и той же программы, и каждая работающая копия программы является процессом. Точнее, каждый запуск любой программы порождает задачу, а каждая задача — это процесс.
Когда программы и процессы понимаются вместе, слово «программа» можно рассматривать как относящееся к статическим вещам. Типичная программа — это статический код и данные, состоящие из файла EXE или файла EXE плюс нескольких файлов DLL. Процесс — это запуск программы, которая представляет собой код и динамически изменяющиеся данные, которые динамически выполняются в памяти. Когда для запуска статической программы требуется выполнение, операционная система выделяет определенное пространство памяти для этой операции, переносит статический программный код и данные в эти области памяти, а также перемещает и отображает программный код и данные в этом пространстве. выполняется внутри, создавая таким образом динамический процесс.
Две копии одной и той же программы, работающие одновременно, означают, что в системной памяти имеются два пространства процессов, но их программные функции одинаковы, но они находятся в разных динамически изменяющихся состояниях.
Что касается времени выполнения процесса, каждый процесс выполняется одновременно. Профессиональный термин называется параллельным выполнением или параллельным выполнением. Но это в основном поверхностное ощущение, которое дает нам операционная система. На самом деле каждый процесс выполняется в режиме разделения времени, то есть каждый процесс по очереди занимает процессорное время для выполнения программных инструкций процесса. Для ЦП одновременно выполняются только инструкции одного процесса. Операционная система является манипулятором работы запланированного процесса. Она постоянно сохраняет и переключает текущее состояние каждого процесса, выполняемого в ЦП, так что каждый запланированный процесс считает, что он выполняется полностью и непрерывно. Поскольку планирование процессов с разделением времени происходит очень быстро, у нас создается впечатление, что все процессы выполняются одновременно. Фактически, настоящая одновременная работа возможна только в аппаратной среде с несколькими процессорами. Когда мы позже поговорим о потоках, мы обнаружим, что потоки — это то, что действительно управляет процессом, и, что более важно, они предоставляют пространство для процесса.
С точки зрения пространства, занимаемого процессом, каждое пространство процесса относительно независимо, и каждый процесс выполняется в своем собственном независимом пространстве. Программа включает в себя как пространство кода, так и пространство данных. И код, и данные занимают пространство процесса. Windows выделяет фактическую память для пространства данных, необходимого каждому процессу, и обычно использует методы совместного использования пространства кода, сопоставляя один код программы с несколькими процессами программы. Это означает, что если программа имеет 100 КБ кода и требует 100 КБ пространства данных, что означает, что в общей сложности требуется 200 КБ пространства процесса, операционная система выделит 200 КБ пространства процесса при первом запуске программы и 200 КБ пространства процесса. пространство будет выделено при втором запуске программы. При запуске процесса операционная система выделяет только 100 КБ пространства данных, в то время как пространство кода разделяет пространство предыдущего процесса.
Выше приведено базовое представление процесса во времени и пространстве в операционной системе Windows. Фактически, существует большая разница во времени и пространстве представления процесса между 16-битными и 32-битными операционными системами Windows.
С точки зрения времени управление процессами в 16-разрядных операционных системах Windows, таких как Windows 3.x, очень просто. На самом деле это просто многозадачная операционная система. Более того, планирование задач операционной системы является пассивным. Если задача не прекращает обработку сообщения, операционная система должна подождать. Из-за недостатков управления процессами 16-битной системы Windows, когда процесс запущен, он полностью занимает ресурсы ЦП. В те дни, чтобы у 16-битной Windows была возможность планировать другие задачи, Microsoft хвалила разработчиков приложений Windows за то, что они были программистами с широким кругозором, поэтому они были готовы написать еще несколько строк кода, чтобы подарить Операционная система. Напротив, операционные системы WIN32, такие как Windows 95 и NT, обладают реальными возможностями многопроцессной и многозадачной операционной системы. Процесс в WIN32 полностью запланирован операционной системой. Как только интервал времени выполнения процесса заканчивается, операционная система активно переключается на следующий процесс независимо от того, обрабатывает ли процесс данные. Строго говоря, 16-битную операционную систему Windows нельзя рассматривать как полноценную операционную систему, но настоящей операционной системой является 32-битная операционная система WIN32. Конечно, Microsoft не скажет, что WIN32 компенсирует недостатки 16-битной Windows, но утверждает, что в WIN32 реализована передовая технология под названием «вытесняющая многозадачность», которая является коммерческим методом.
С точки зрения пространства, хотя пространство процессов в 16-разрядной операционной системе Windows относительно независимо, процессы могут легко получать доступ к пространству данных друг друга. Поскольку эти процессы на самом деле представляют собой разные сегменты данных в одном физическом пространстве, и неправильные операции с адресами могут легко привести к неправильному чтению и записи пространства и привести к сбою операционной системы. Однако в операционной системе WIN32 каждое пространство процесса полностью независимо. WIN32 предоставляет каждому процессу виртуальное и непрерывное адресное пространство до 4G. Так называемое непрерывное адресное пространство означает, что каждый процесс имеет адресное пространство от $00000000 до $FFFFFFFF, а не сегментированное пространство 16-битной Windows. В WIN32 вам не нужно беспокоиться о том, что операции чтения и записи непреднамеренно влияют на данные в других пространствах процессов, и вам не нужно беспокоиться о том, что другие процессы могут помешать вашей работе. В то же время непрерывное виртуальное пространство 4G, предоставляемое WIN32 для вашего процесса, представляет собой физическую память, назначенную вам операционной системой при поддержке оборудования. Хотя у вас такое огромное виртуальное пространство, система никогда не потеряет ни байта. . физическая память.
Раздел 2. Пространство процессов
Когда мы используем DELPHI для написания приложений WIN32, мы редко заботимся о внутреннем мире процесса во время его работы. Поскольку WIN32 обеспечивает 4G непрерывного виртуального пространства для нашего процесса, возможно, самое большое приложение в мире в настоящее время использует только его часть. Кажется, что пространство процесса не ограничено, но пространство процесса 4G является виртуальным, и реальная память вашей машины может быть далека от этого. Хотя процесс занимает такое огромное пространство, некоторые программы со сложными алгоритмами все равно не смогут работать из-за переполнения стека, особенно программы, содержащие большое количество рекурсивных алгоритмов.
Таким образом, глубокое понимание структуры пространства процессов 4G, его связи с физической памятью и т. д. поможет нам более четко понять пространственно-временной мир WIN32, чтобы мы могли использовать правильные методы в реальных разработках. Мировоззрение и методология решения различных сложных задач.
Далее мы проведем простой эксперимент, чтобы понять внутренний мир пространства процессов WIN32. Для этого могут потребоваться некоторые знания регистров CUP и языка ассемблера, но я постарался объяснить это простым языком.
При запуске DELPHI автоматически будет сгенерирован проект Project1, с него мы и начнем. Установите точку останова в любом месте исходной программы Project1.dpr, например, установите точку останова в начале предложения. Затем запустите программу, и она автоматически остановится, когда достигнет точки останова. В это время мы можем открыть окно ЦП в инструменте отладки, чтобы наблюдать внутреннюю структуру пространства процесса.
Регистр указателя текущей инструкции Eip остановлен в $0043E4B8. Из двух старших шестнадцатеричных цифр адреса, в котором находится программная инструкция, являются нули, видно, что текущая программа находится в позиции адреса в нижней части 4G. пространство процесса, которое занимает от $00000000 до довольно небольшого адресного пространства для $FFFFFFFF.
В командном поле окна ЦП вы можете просмотреть содержимое пространства процесса. При просмотре содержимого пространства менее $00400000 вы обнаружите серию вопросительных знаков «????», появляющихся в содержимом менее $00400000. Это связано с тем, что адресное пространство не сопоставлено с реальным физическим пространством. Если вы посмотрите на шестнадцатеричное значение глобальной переменной HInstance в это время, вы обнаружите, что оно также равно $00400000. Хотя HInstance отражает дескриптор экземпляра процесса, на самом деле это значение начального адреса при загрузке программы в память, в том числе и в 16-битной Windows. Следовательно, можно считать, что программа процесса загружается начиная с $00400000, то есть пространство, начинающееся с 4M в виртуальном пространстве 4G, является пространством, в котором загружается программа.
Начиная с $00400000 и до $0044D000, это в основном адресное пространство программного кода и глобальных данных. В поле стека в окне ЦП вы можете просмотреть адрес текущего стека. Аналогичным образом вы обнаружите, что текущее адресное пространство стека составляет от $0067B000 до $00680000 и имеет длину $5000. Фактически минимальный размер стекового пространства процесса составляет 5000 долларов США, который получается на основе значения минимального размера стека, установленного на странице компоновщика ProjectOptions при компиляции программы DELPHI, плюс 1000 долларов США. Стек увеличивается от старшего адреса к нижнему. Если во время работы программы стека недостаточно, система автоматически увеличивает размер стекового пространства по направлению к нижнему адресу. Этот процесс отобразит больше фактической памяти в нижний адрес. пространство процесса. При компиляции программы DELPHI вы можете контролировать максимальное пространство стека, которое можно увеличить, задав значение максимального размера стека на странице компоновщика в ProjectOptions. Значение максимального размера стека должно быть установлено разумно, особенно в программах, которые содержат глубокие отношения вызовов подпрограмм или используют рекурсивные алгоритмы. Поскольку для вызова подпрограммы требуется место в стеке, и после того, как стек будет исчерпан, система выдаст ошибку «Переполнение стека».
Кажется, что пространство процесса после пространства стека должно быть свободным пространством. На самом деле это не так. В соответствующей информации WIN32 говорится, что пространство 2G после 80 000 000 долларов — это пространство, используемое системой. Похоже, что процесс действительно может владеть только пространством 2G. На самом деле пространство, которым на самом деле может владеть процесс, даже не 2G, потому что пространство в 4M от $00000000 до $00400000 также является ограниченной областью.
Но несмотря ни на что, адреса, которые может использовать наш процесс, по-прежнему очень широки. Особенно после пространства стека и суммы в 80 000 000 долларов, это главное поле битвы в пространстве процессов. Пространство памяти, выделенное процессу из системы, будет сопоставлено с этим пространством, в это пространство будет сопоставлена динамически подключаемая библиотека, загруженная процессом, пространство стека потоков нового потока также будет сопоставлено с этим пространством, почти все операции, связанные с выделением памяти. Все будут отображены в это пространство. Обратите внимание, что упомянутое здесь сопоставление означает соответствие между фактической памятью и этим виртуальным пространством. Пространство процесса, которое не сопоставлено с фактической памятью, не может использоваться, как и строка «» в командном поле окна ЦП во время отладки. ???».
............
Спасибо за чтение!