По основам программирования в П-коде см. соответствующую инструкцию. В данной инструкции приводятся особенности П-кодирования зеркальных компонентов MCK, т.е. порядок их адаптации с тем, чтобы для форм, на которых эти компоненты расположены, мог быть автоматически сгенерирован П-код.
Совместимость компонента, унаследованного от TKOLObj или TKOLCustomControl, c П-кодом, проверяется вызовом виртуальной функции Pcode_Generate. По завершении адаптации необходимо унаследовать этот метод и возвратить True: это будет сигнал для TKOLForm о том, что данный компонент совместим с П-кодом.
Но прежде необходимо определить набор процедур P_xxxxxxxx, соответствующих переопределенным в компоненте методам SetupFirst, SetupParams, GenerateTransparentInits, SetupLast, AssignEvents. Практически для всех этих методов соответствующий метод P_xxxxxxxx полностью совпадает по параметрам (исключение: метод P_SetupParams имеет дополнительный параметр nparams, см. ниже).
Порядок наполнения кода для P_xxxxxxxx процедуры примерно следующий: создается этот метод, в его тело переносится все содержимое исходного метода xxxxxxxx (например, SetupFirst => P_SetupFirst), затем весь код, добавляемый вызовами SL.Add( строка_на_языке_Паскаль ) должен быть заменен на SL.Add( строка_в_П-коде ). Естественно, я отсылаю к образцу кода в MCK (mckControls.pas, mckObjs.pas, mirror.pas), и предлагаю прочитать инструкцию по П-программированию. Напоминаю так же, что методы P_SetupXXXXX и прочие, так же как и исходные методы SetupXXXXX, всего лишь генерируют текст программы. Но если исходные методы SetupXXXXX генерировали код для компилятора с языка Паскаль, то P_SetupXXXXX должны генерировать код для П-компилятора. П-код в последующем компилируется при помощи программы PCompiler.exe в байт-код, фактически представленный кодом на встроенном в Delphi ассемблере, в виде последовательности директив DB, DW, DD. Основные особенности П-кода заключаются в том, что это код для двух-стековой машины, и исполняется этот П-код не процессором непосредственно, а эмулятором П-кода. Впрочем, это все написано в инструкции по П-кодированию.
То, что надо знать при адаптации конкретного компонента при написании определенного метода P_xxxxxxxx - это состояние вычислительного стека в момент вызова кода, который будет этим методом сгенерирован. А так же, основные способы "добраться" до нужных объектов, их полей и методов. Например, общий момент: внутренняя переменная П-машины SELF (регистр EBP) постоянно в процессе работы инициализатора формы New[имя_формы] содержит указатель объекта формы (например, Form1). Итак, по методам.
Метод P_SetupParams, так же как и исходный метод SetupParams, должен вернуть строку, в которой загружаются параметры функции New[тип_объекта](...). Только SetupParams возвращал список параметров в прямом порядке через запятую, а P_SetupParams должен возвратить набор операторов П-кода, загружающих параметры на вычислительный стек в обратном порядке. Кроме того, функция P_SetupParams имеет дополнительный параметр nparams, которому следует присвоить значение от 0 до 3 - это количество параметров, передаваемых функции NewXXXXX через регистры EAX, EDX, ECX. Для процедур с соглашением Паскаля о передаче параметра это просто min( число_параметров, 3 ). Если функция NewXXXXX объявлена с соглашением stdcall о передаче параметров, nparams в любом случае следует присвоить 0, независимо от числа параметров.
Для метода P_SetupParams важно знать следующее: П-код, который в нем генерируется, начинает работать в условиях, когда на вершине вычислительного стека находится указатель "родительского" объекта (и он не должен удаляться из стека), а по окончании работы сгенерированного в P_SetupParams кода "выше" (или все-таки "ниже", если учесть, что стек растет вниз) этого указателя на родителя должны быть положены входные параметры, причем - в обратном порядке. К коду, сгенерированному в P_SetupParams, далее "пристыковывается" вызов функции создания run-time объекта NewXXXXX<nparams> и команда RESULT для выкладывания на вершину стека указателя созданного объекта. (На самом деле, указатель на созданный объект еще и сохраняется в предназначенном для этого поле хранилища формы, но состояние стека эта операция уже не изменяет).
Если все параметры - обычные числа или указатели (хотя бы PChar), особых проблем с загрузкой на вычислительный стек их значений проблем быть не должно. В случае, если имеется параметр типа AnsiString (просто String, или он же - Huge String - т.е. заканчивающаяся нулем длинная строка, со своим счетчиком использования), то необходимо использовать функцию BaseLib.LoadAnsiStr, и при этом в стеке образуется два одинаковых значения. Это указатели на эту строку. Обычно второй указатель можно перемещать по вычислительному стеку, часто он немедленно используется (например, для операции Store), после чего со стека исчезает. Но по крайней мере один указатель нужно хранить до того момента, когда эта строка больше не понадобится, после чего следует вызвать DelAnsiStr, чтобы правильно уничтожить строку в динамической памяти, освободив ее без потерь. Заметьте себе, что если только последний параметр функции NewXXXXX - это строка, то имеет смысл начать загрузку параметров на стек именно с нее, но если же строкой является хотя бы один не последний параметр, то сначала следует вызвать все LoadAnsiStr, а уже затем в нужном порядке (т.е. с последнего параметра) создавать на стеке нужную последовательность (а когда нужна очередная строка, использовать команду Cn или L(n) COPY для копирования на вершину стека нужного значения).
Аналогичные подходы следует использовать, когда на вершину стека должно быть загружено любое значение, копия которого должна оставаться на время вызова метода NewXXXXX выше списка параметров - для выполнения каких-либо последующих операций освобождения, очистки памяти и т.п. Если такие операции очистки после вызова NewXXXXX требуются, код для такой зачистки может добавляться в одном из методов P_GenerateTransparentInits (только для контролов, унаследованных от TKOLCustomControl или TKOLControl, но не для зеркал объектов, унаследованных от TKOLObj), P_SetupFirst, P_SetupFirstFinalizy.
Метод P_GenerateTransparentInits вызывается во всех наследниках TKOLCustomControl, и начинает работать во время исполнения программы вслед за вызовом NewXXXXX. На стеке в этот момент находится указатель объекта, а выше - указатель родительского объекта (но если так работает P_SetupParams, между ними могут быть складированы еще какие-то временные данные. Лучше всего уничтожение этих данных отложить до того момента, когда с вершины стека будет удаляться за ненадобностью указатель созданного объекта, и создать соответствующий уничтожающий код в переопределенном методе P_SetupFirstFinalizy, тогда для удаления сразу нескольких значений с вершины стека можно будет использовать двух-байтную последовательность команд L(n) DELN ).
Код, который генерирует P_GenerateTransparentInits, в случае П-кода мало чем отличается от кода, который генерирует P_SetupFirst (вообще, его без проблем можно даже перенести в P_SetupFirst). Дело в том, что "прозрачные" функции, возвращающие в случае использования Паскаля в качестве результата (в регистре EAX), в случае П-кода в этом возвращаемом значении не нуждаются. Чаще всего указатель на Self должен быть передан в качестве первого параметра прозрачной функции, и его проще скопировать через голову уже загруженных прочих одного-двух параметров, а после вызова прозрачного метода стек опять оказывается в прежнем состоянии, т.е. на вершине стека опять находится указатель на объект.
Как и для метода P_GenerateTransparentInits, код, сгенерированный методом P_SetupFirst, вызвается, когда на вершине стека находится указатель на созданный объект, и он же там и должен оставаться по окончании работы этого кода. Немедленно вслед за кодом, сформированным в P_SetupFirst, будет вызвано создание всех дочерних контролов (в случае контролов), после чего будет добавлена команда DEL, удаляющая с вершины стека ненужный более указатель объекта, и сработает код, создаваемый в P_SetupFinalizy (если такой есть).
В отличие от прочих методов, этот метод генерирует код, при необходимости, и уже в условиях, когда на вершине стека нет указателя объекта. Если надо получить указатель объекта, следует использовать общую последовательность команд 'LoadSELF AddWord_LoadRef ##T' + ParentKOLForm.FormName + '.' + Name; эта последовательность загрузит указатель на объект заново, используя тот факт, что "переменная" SELF (регистр EBP) постоянно в пределах процедуры New[имя_формы] хранит указатель объекта-хранилища формы. Этот же метод можно использовать и в любом из вышеозначенных методов P_SetupXXXX, когда расчеты относительного местоположения объекта на стеке затруднены (например, был прямо в стеке создан динамический массив заранее неизвестной длины).
Этот специальный метод добавлен в предок всех зеркал
визуальных и невизуальных объектов с тем, чтобы возможно было добавить в генерируемый
inc-файл формы объявления вроде
property SomeProp: blablabla read
FSomeProp write SetSomeProp
или
write FSomeProp
).
Примечание: в целях сокращения длины идентификаторов (ассемблер обрезает идентификаторы до 32 символов) возможно использовать более короткие имена для Fake-типов, чем оригинальные. Главное, чтобы они оставались уникальными. Если для нескольких различных объектных типов Fake-имена совпадут, это вызовет конфликт в компиляторе, который потребуется разрешить.
Эти методы добавлены специально из-за (и для) RichEdit. В его же реализации можно посмотреть их реализацию.
Настоятельно рекомендую в процессе адаптации MCK-классов к П-коду подсматривать в реализацию аналогичных методов в коде самого MCK. Практически везде, где это было приемлемо, я сохранял прежний код, закомментаривая его, и дописывал новый код с префиксом {P} в строке. Так что всегда можно посмотреть, какие строки П-кода генерируются на месте бывших строк Паскаль-кода. Для отладки используйте П-отладчик. Для упрощения процесса отладки помещайте для начала свой компонент на пустую форму, так проще обнаружить возможные ошибки (тем более что в первой версии отладчика пока что реализовано только пошаговое прохождение П-кода по F7).
Владимир Кладов, 3.12.2005