  Как импортировать объекты из внешней программы, написанной на
                        Turbo Pascal 6.0

                          Кучеров С.Н.


   Итак,  после  долгих  сомнений  вы,  наконец  решили  написать
большую  программу,   это  может   быть,   например,   СУБД   или
интегрированная  система.  Перед  вами  сразу  встанет  множество
проблем, и  одна из  них  заключается  в  том,  чтобы  обеспечить
возможность расширения  функций вашей  системы без  переписывания
текстов программ.  Обычно эта проблема решается при помощи макро-
функций или макро-языка, позволяющего пользователю создавать свои
приложения.
   Примером  программы  с  развитым  макро-языком  может  служить
текстовый  редактор   Multi Edit,  в   котором  все  операции  по
управлению  текстом  могут  быть  выполнены  программой-макросом.
Единственный недостаток  этого метода заключается в сложности его
реализации.  Действительно,   если  вы  решите  включить  в  вашу
программу  макро-язык,   то  вам   придется  писать  компиллятор,
интерпретатор и отладчик, что не так просто.
   Чтобы  не   связываться  с   интерпретатором,  можно  написать
библиотеку функций, которую затем использовать в различных языках
программирования. В  этом  случае  программист  сможет  создавать
приложения к  системе, используя  готовый компиллятор  и создавая
выполняемые  EXE  модули.  Однако  в  этом  случае  каждая  такая
программа будет содержать в себе весь набор базовых функций вашей
системы (рис 1a).
   Другое решение  заключается в  том, что  эти приложения  будут
запускаться  из   главной  программы   и  вызывать   из  нее  все
необходимые  процедуры.   Другими  словами,  программы-приложения
будут использовать  коды главной  программы для доступа к базовым
операциям  (рис 1b).   Примером  такой   системы  может   служить
Microsoft  Windows.   Программы,  написанные   для  этой  системы
используют базовые  операции для того, чтобы в графике изображать
окна, меню и диалоговые панели.
   Хорошая идея,  но как  ее реализовать?  Первое, что приходит в
голову это  воспользоваться системой  прерываний. В  этом  случае
главная   программа   перехватывает   свободное   прерывание,   а
программа-приложение вызывает его для доступа к базовым функциям.
Но при  этом мы  попадаем в  странное  положение:  обе  программы
(главная  и  приложение)  пишутся  на  языке  высокого  уровня  с
использованием  объектно-ориентированного   программирования,   а
связь между  ними осуществляется примитивно, при помощи регистров
и прерываний.  Гораздо лучше,  если программа-приложение могла бы
обращаться к процедурам или объектам главной программы также, как
она обращается  к собственным процедурам и объектам. Для этого ей
необходимо сообщить  адреса всех  базовых процедур,  которые  она
будет использовать, и это можно легко сделать используя объектно-
ориентированный подход.

     ┌────────────────────┐640k    ┌────────────────────┐640k
     │░░░░░░░░░░░░░░░░░░░░│        │░░░░░░░░░░░░░░░░░░░░│
     │░░░░░░ Память ░░░░░░│        │░░░░░░ Много  ░░░░░░│
     │░░░░░░░░░░░░░░░░░░░░├─┐      │░░░░░░ памяти ░░░░░░├────┐
     │░░░░░░░░░░░░░░░░░░░░│ │      │░░░░░░░░░░░░░░░░░░░░│    │
     │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ │      │░░░░░░░░░░░░░░░░░░░░│    │
     │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│<┤      │░░░░░░░░░░░░░░░░░░░░│    │
     │████████████████████│ │      │░░░░░░░░░░░░░░░░░░░░│    │
     │████████████████████│ │      │░░░░░░░░░░░░░░░░░░░░│    │
     │█ Базовые функции ██│<┘      │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│<───┤
     │████████████████████│        │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│    │
     ╞════════════════════╡        ├────────────────────┤    │
     │░░░░░░░░░░░░░░░░░░░░│        │░░░░░░░░░░░░░░░░░░░░│    │
     │░░░░░░ Память ░░░░░░├─┐      │░░░░░░ Память ░░░░░░├─┐  │
     │░░░░░░░░░░░░░░░░░░░░│ │      │░░░░░░░░░░░░░░░░░░░░│ │  │
     │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ │      │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ │  │
     │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│<┤      │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│<┤  │
     │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ │      │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ │  │
     │████████████████████│ │      │████████████████████│ │  │
     │████████████████████│ │      │████████████████████│<┘  │
     │█ Базовые функции ██│<┘      │█ Базовые функции ██│<───┘
     │████████████████████│        │████████████████████│
     └────────────────────┘        └────────────────────┘

     a) Независимая программа-     b) Программа-приложение
     приложение дублирует все      вызывает базовые функции из
     базовые функции.              главной программы.

  Рис 1. Сравнение двух способов организации доступа к базовым
                функциям из программы-приложения.


   Давайте с вами напишем программу MAIN, которая будет содержать
некий базовый  объект (например,  базу данных  BASE), и попробуем
организовать  доступ   к  методам  этого  объекта  из  программы-
приложения, которую  назовем CHILD.  Для начала  нужно вспомнить,
что  при  компилляции  программы  на  Паскале,  все  обращения  к
невиртуальным методам  объектов заменяются  на вызов  процедуры с
явно указанным  адресом. Поэтому,  если мы  хотим  заменить  этот
адрес в  процессе работы программы, то должны объявить все методы
объекта BASE  виртуальными. Для  каждого типа  объекта,  имеющего
виртуальные методы,  Паскаль создает  таблицу виртуальных  правил
(VMT), которая содержит адреса всех виртуальных методов объекта.
   Теперь нам понадобится создать два модуля (библиотеки). Первый
модуль  (назовем  его  BASE1)  будет  содержать  полное  описание
объекта BASE  со всеми  методами.  Второй  модуль  (BASE0)  будет
содержать только  описание объекта  BASE, содержащееся  в разделе
interface, а  вместо  процедур  в  разделе  implementation  будут
стоять "пустые"  заголовки и  пустые операторы  begin end. Первый
модуль будет использован при компилляции программы MAIN, а второй
при компилляции  программы CHILD.  Таким образом, программа CHILD
сможет использовать  объект BASE,  который описан в модуле BASE0.
Для этого типа объекта будет создана таблица VMT, в которой будут
записаны  адреса  "пустых"  методов  этого  объекта.  Адрес  этой
таблицы можно  получить при  помощи стандартной  функции  TypeOf.
Если бы мы могли заменить этот адрес в объекте программы CHILD на
адрес аналогичной  таблицы из  программы MAIN,  то мы бы достигли
желаемого результата,  но, к  сожалению,  этого  сделать  нельзя,
потому  что   объект  хранит   только   смещение   таблицы   VMT.
Следовательно, единственный  способ  переадресовать  обращение  к
методам объекта  - полностью  скопировать содержимое  таблицы VMT
программы MAIN в аналогичную таблицу программы CHILD.

     ┌────────────────────┐
     │  Размер объекта    │  2 байта
     ├────────────────────┤
     │  Отриц. размер     │  2 байта
     ├────────────────────┤
     │  Адрес 1 метода    │  4 байта
     │  ...               │


                  Рис 2. Структура таблицы VMT.

   Но это  еще не  все. Посмотрите  на рисунок 1b, вы видите, что
программа CHILD  вызывает процедуры  из  программы  MAIN.  Теперь
представьте, что  эта процедура  пытается отвести память под свои
переменные путем  вызова стандартной  процедуры GetMem. Она будет
вызвана из  области программы MAIN и попытается выделить память в
области  зарезервированной  программой  MAIN,  которая  на  время
выполнения программы CHILD будет уменьшена до минимума. Для того,
чтобы процедуры из программы MAIN могли отводить память в области
зарезервированной   программой   CHILD,   мы   должны   присвоить
переменным   HeapPtr    и   HeapOrg   программы   MAIN   значения
соответствующих переменных программы CHILD.
   Для  того,   чтобы  программа   CHILD  могла   совершить   все
вышеперечисленные действия  и получить  доступ к  методам объекта
BASE, программа  MAIN должна  создать таблицу  настройки, которая
должна содержать следующие сведения:

       - адрес переменной HeapPtr;
       - адрес переменной HeapOrg;
       - длина таблицы VMT объекта BASE;
       - адрес таблицы VMT объекта BASE;

   Как вычислить  длину  таблицы  VMT?  Смотрите  на  рисунок  2:
таблица  VMT  содержит  размер  объекта  (он  занимает  16  бит),
отрицательный размер  объекта  для  контроля  инициализации  (еще
столько же)  и список  полных адресов  всех виртуальных  методов.
Следовательно, длина таблицы VMT вычисляется как:

       VMT_size := 4 + SizeOf(Pointer) * Meth_Num;
где Meth_Num - количество виртуальных методов объекта. Количество
методов не  удается  получить  стандартной  функцией,  поэтому  в
качестве этого параметра лучше использовать константу.


                            Память до настройки           Память после настройки

 << Программа CHILD >>   640│                    │     640│                    │
                            │░░░░░░░░░░░░░░░░░░░░│        │░░░░░░░░░░░░░░░░░░░░│
                            │░░░░░░░░░░░░░░░░░░░░│        │░░░░░░░░░░░░░░░░░░░░│
Экземпляр объекта Base      │░░DataBase───┐░░░░░░│        │░░DataBase───┐░░░░░░│
           Таблица VMT      │░░░░░░░░░░░░░│░░░░░░│        │░░░░░░░░░░░░░│░░░░░░│
     "пустого" объекта   ┌──┤░░Base.VMT <─┘░░^░░░│     ╔═>│░░Base.VMT <─┘░░^░░─┼──┐
     Указатель на Heap   │  │░░HeapPtr ──────┘░>═╪═╗к к║  │░░HeapPtr ──────┤░░░│  │у
                         │  │░░░░░░░░░░░░░░░░░░░░│ ║о о║  │░░░░░░░░░░░░░░░░│░░░│  │к
                         │  │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ ║п п║  │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│▓▓▓│  │а
         Код программы   └─>│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ ║и и║  │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│▓▓▓│  │з
    (объекта Base нет)      │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ ║р р║  │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│▓▓▓│  │ы
----------------------------├────────────────────┤ ║о о║  ├────────────────┼───┤  │в
  << Программа MAIN >>      │░░░░░░░░░░░░░░░░^░░░│ ║в в║  │░░░░░░░░░░░░░░░░│░░░│  │а
                            │░░░░░░░░░░░░░░░░│░░░│ ║а а║  │░░░░░░░░░░░░░░░░│░░░│  │е
     Указатель на Heap      │░░HeapPtr ──────┘░░░│ ║т т║  │░░░░░░░░░░░░░░░░│░░░│  │т
     Таблица настройки   ┌──┤░░TuneTable░░░░░░░░░│ ║ь ь║  │░░TuneTable░░░░░│░░░│  │
           Таблица VMT   │  │░░Base.VMT ░░░░░░░>═╪═╬═══╝  │░░Base.VMT░░░░░░│░░░│  │н
          объекта Base  а│  │░░░░░░░░░░░░░░░░░░░░│ ╚═════>│░░HeapPtr ──────┘░░░│  │а
                        д│  │░░░░░░░░░░░░░░░░░░░░│        │░░░░░░░░░░░░░░░░░░░░│  │
                        р│  │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│        │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│  │
        Код программы:  е│  │▓▓ Base.####; ▓▓▓▓▓▓│        │▓▓ Base.####; ▓▓▓▓▓▓│<─┘
   методы объекта Base  с│  │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│        │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
                         │  │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│        │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
-------------------------│--├────────────────────┤        ├────────────────────┤
                         │  │ BIOS, DOS          │        │ BIOS, DOS          │
                         │  ├────────────────────┤        ├────────────────────┤
                         └─>│ Вектор $61         │        │ Вектор $61         │
                           0└────────────────────┘       0└────────────────────┘

    Рис 3. Настройка программы-приложения на работу с главной
                           программой.


   Из этой  таблицы программа  CHILD узнает  все необходимое  для
того, чтобы  настроить себя на работу с программой MAIN. Осталось
сообщить ей  адрес этой  таблицы настройки.  Этот адрес программа
MAIN поместит  в один  из свободных векторов прерываний (например
$61). Программа  CHILD должна  прочитать этот  вектор и проверить
его правильность,  для  чего  в  таблице  настройки  предусмотрен
заголовок.
   Сразу после  запуска программа CHILD должна считать содержимое
вектора прерывания,  проверить, лежит  ли по этому адресу таблица
настройки  и,   если  это   так,  заменить   содержимое   таблицы
виртуальных  правил   (VMT)  "пустого"   объекта  на   содержимое
аналогичной таблицы  главной  программы.  После  этого  программа
CHILD должна  переслать содержимое переменных HeapPtr и HeapOrg в
программу MAIN  и запустить  конструктор экземпляра  объекта BASE
(при этом  адрес таблицы  VMT с  новыми  адресами  методов  будет
помещен в  экземпляр объекта).  На этом  заканчивается  настройка
программы CHILD (рис 3).
   После  того,  как  программа  CHILD  завершит  свою  работу  и
управление вернется  в программу  MAIN, нужно  не забыть  вернуть
прежние значения переменным HeapPtr и HeapOrg (их нужно сохранить
перед  вызовом  программы  CHILD)  и  освободить  занятый  вектор
прерывания.
   Осталось добавить,  что если  вы  хотите  организовать  импорт
нескольких  объектов,  то  вам  придется  реорганизовать  таблицу
настройки, чтобы  в ней  можно было передать параметры нескольких
объектов. Для  этого в  начале таблицы  можно указать  количество
импортируемых объектов.
   Остается открытым  вопрос об использовании этого метода вместе
с оверлейными  модулями. Для того, чтобы импортировать оверлейный
объект наверное  можно переслать  параметры буфера оверлеев также
как мы передали параметры Heap.
   Все, что  здесь  написано  я  проверил  на  практике,  написав
программы MAIN  и CHILD,  которые успешно  работают, демонстрируя
правильность  сделанных   выводов.  Ниже   приведен  текст  обеих
программ.  Для  удобства  использования  я  выделил  все  функции
настройки в  отдельный модуль  TUNE. Модуль BASE1 не приведен для
экономии места,  он отличается от модуля BASE0 тем, что в разделе
implementation описана конкретная реализация объекта BASE.

{--------------------------------------------------------}
{                  Модуль настройки TUNE                 }
{--------------------------------------------------------}
unit Tune;
interface
type
   PointPtr   = ^pointer;
   TunePtr  = ^Tune_Table;
   Tune_Table = record       { Таблица настройки объекта }
      Head     : integer;    { Заголовок всегда -1       }
      PHeapOrg : PointPtr;   { Указатель на Main.HeapOrg }
      PHeapPtr : PointPtr;   { Указатель на Main.HeapPtr }
      VMTPtr   : pointer;    { Указатель на Main.VMT     }
      VMTSize  : word;       { Размер таблицы Main.VMT   }
   end;

{ ****** Процедуры, вызываемые из Main ****** }
procedure SaveHeap(var T:Tune_Table);
{ Сохранить параметры Main.heap }

procedure RestoreHeap(var T:Tune_Table);
{ Восстановить параметры Main.heap }

{ ****** Процедуры, вызываемые из Child ****** }
procedure ChangeHeap(var T:Tune_Table);
{ Записать параметры Child.Heap в Main.Heap }

procedure CopyVMT(var T:Tune_Table; Vptr:pointer);
{ Записать Main.VMT в Child.VMT }

implementation
uses Dos;
var
   OldHeapPtr,
   OldHeapOrg : pointer;

procedure SaveHeap;
begin
   OldHeapPtr:=HeapPtr;
   OldHeapOrg:=HeapOrg;
   T.PHeapOrg:=@HeapOrg;
   T.PHeapPtr:=@HeapPtr;
end;

procedure RestoreHeap;
begin
   HeapPtr:=OldHeapPtr;
   HeapOrg:=OldHeapOrg;
   T.PHeapOrg:=nil;
   T.PHeapPtr:=nil;
end;

procedure ChangeHeap;
begin
   with T do begin
      PHeapOrg^:=HeapOrg;
      PHeapPtr^:=HeapPtr;
   end;
end;

procedure CopyVMT;
begin
   with T do Move(VMTPtr^,Vptr^,VMTSize);
end;
end.


{--------------------------------------------------------}
{                   Главная программа                    }
{--------------------------------------------------------}
uses Crt, Dos, Tune, Base1;
{$M $400,0,0}

var TT : Tune_Table;   { таблица настройки }
  S  : string;
  i  : byte;
const
  Methods=10;        { количество методов объекта }

procedure WriteError;
begin
  TextAttr:=$8C; gotoxy(2,4);
  case DosError of
    2 : write('Ошибка: не найден файл CHILD.EXE');
    3 : write('Ошибка: путь не найден');
    5 : write('Ошибка: файл недоступен');
    8 : write('Ошибка: не хватает памяти, чтобы запустить CHILD.EXE');
  end;
end;

begin
  { ---  Инициализация таблицы библиотеки --- }
  with TT do begin
    Head := -1;
    PHeapOrg := nil;
    PHeapPtr := nil;
    VMTPtr := TypeOf(Base);
    VMTSize := 4 + SizeOf(pointer) * Methods;
  end;

  { ---  Записать параметры главного модуля --- }
  SaveHeap(TT); SetIntVec($61,Addr(TT));

  { ---  Запустить программу Child --- }
  SwapVectors;
  Exec('CHILD.EXE','');
  SwapVectors;

  { ---  Восстановить параметры главного модуля --- }
  RestoreHeap(TT); SetIntVec($61,nil);
end.


{--------------------------------------------------------}
{                  Программа-приложение                  }
{--------------------------------------------------------}
uses Crt, Dos, Tune, Base0;
var
  TT    : TunePtr;  { Указатель на настроечную таблицу }
  B     : base;     { Экземпляр импортируемого объекта }

function From_Main : boolean;
var P : pointer;

begin
  GetIntVec($61,P); TT:=P;      { Получить адрес настроечной таблицы }
  From_Main:=true;
  if (TT=nil) or                { Если вектор пуст, }
    (TT^.Head<>-1)              { или не указывает на таблицу }
    then From_Main:=false       { то настройка невозможна }
  else begin
    ChangeHeap(TT^);            { переслать параметры Heap в программу MAIN }
    CopyVMT(TT^,TypeOf(Base));  { скопировать таблицу VMT из программы }
                                { MAIN в аналогичную таблицу CHILD }
  end;
end;

begin
  if not From_Main then begin
    writeln('Программа должна быть запущена из главного модуля!');
    Halt;
  end;
  B.Init;   { Запуск конструктора объекта }
  {* Обращение к экземпляру объекта BASE.*}
  {* Все методы этого объекта будут вызваны из программы MAIN.*}
end.
