Пролог - часть 3

Материал из DOM

Перейти к: навигация, поиск

[править] Взаимодействие программ на прологе и других языках

Первым шагом для создания dll для пролога будет создание нужного типа проекта. До этого мы обходились созданием просто файла с исходным кодом *.pro. Создавайте проект через меню “Project/New Project” в свойствах проекта обязательно поставьте, что его тип “dll” и предназначен он для работы под платформу win32.

Далее мы начнем как и ранее описывать предикаты, но при этом следует придерживаться определенного правила: как известно ключевыми механизмами пролога являются унификация и backtracking, по этому на любой вопрос который мы задаем, например:

father (X,Y)

мы можем получить произвольное количество результатов, от нуля до бесконечности (или чего-то очень большого) . для пролога это вполне нормально, а вот программы на c/Delphi и прочих алгоритмических языках такой неопределенности не поймут. Поэтому нам необходимо при создании предикатов в секции “predicates” выполнять их декларацию как “procedure” – если обратиться к справке пролога, раздел “Модели детерминизма”, то можно узнать, что данное ключевое слово означает что предикат находит одно и только одно решение и при этом не может потерпеть неудачу. Например:

predicates
 procedure foo (integer, integer)(i,i) (i,o)

При разработке тела для данного правила уже придется следить за кодом, чтобы действительно было возможно только одно решение. Например, нижеприведенный фрагмент кода будет замечательно работать и находить целых три решения:

predicates
  nondeterm find_solution (integer , string)
 
clauses
  find_solution (X , "Число положительное или равно нулю"):-
   X >=0.
  find_solution (X , "Число равно нулю"):-
   X =0.
  find_solution (X , "Число отрицательное или равно нулю"):-
   X <=0.
 
goal
  find_solution (0 , КакоеЧисло).

КакоеЧисло=Число положительное или равно нулю КакоеЧисло=Число равно нулю КакоеЧисло=Число отрицательное или равно нулю 3 Solutions

Изображение:plusPrologplus12jpg.jpg


А если мы изменим тип детерминизма на procedure то получим ошибку компиляции.

E;Test_Goal, pos: 407, 590 Nondeterministic clause: find_solution

Для избежания данной проблемы мы вынуждены пользоваться отсечением (cut, !).

predicates
  procedure find_solution (integer , string)
clauses
  find_solution (X , "Число положительное или равно нулю"):-
   X >=0,!.
  find_solution (X , "Число равно нулю"):-
   X =0,!.
  find_solution (X , "Число отрицательное или равно нулю"):-
   X <=0,!.
goal
  find_solution (0 , КакоеЧисло).

Вот результат запуска программы: КакоеЧисло=Число равно нулю 1 Solution

Следующим шагом на пути создания библиотеки dll будет создание специального файла определений для экспорта. Ведь согласитесь, что глупо все объявленные предикаты делать общедоступными для пользователей. Если у вы начинали программировать под windows и разрабатывали библиотеки в конце win3.11 и вначале win95, то до появления директив, вроде:

__declspec(dllexport)

использовался специальный файл *.DEF, в котором описывались характеристики библиотеки и указывались имена экспортируемых функций.

Когда мы создали проект типа “dll”, среда Vip5 автоматически сгенерировала данный файл, но к сожалению не добавила его к проекту. Придется нам самим для удобства дальнейшей его правки добавить к проекту. Используйте кнопку “New” в менеджере проекта, и выберите на диске данный файл.

Изображение:plusPrologplus13jpg.jpg


Мы начнем с самого просто, с того, что создадим в прологе предикат, единственной задачей которого будет вывод на экран сообщения.

predicates
 procedure say_hello ()
clauses
 say_hello:-
   write ("Hello From Prolog"),nl.

В файле *.DEF мы добавим строчку в секции EXPORTS А вот и еще одна ошибка:

Error 2525: 'tdll1.def' - undefined name ‘say_hello'

Нам необходимо все имена на которые мы ссылаемся в *.DEF файле объявлять как глобальные. Для этого заменим директиву “predicates” на “global predicates”. И укажем в объявлении предиката директиву влияющую на правила именования функций: “language stdcall”, окончательно наш код будет выглядеть так:

global predicates
 procedure say_hello () – language stdcall
clauses
 say_hello:-
   write ("Hello From Prolog"),nl.

Если выбрать в меню “Project/Build All” то в результате мы, наконец, получим в каталоге “ТамГдеВыСоздалиВашПроект/Exe”, следующие файлы:

C tdll1.lib
C tdll1.dll

Далее мы рассмотрим специфические моменты того, как генерируется код компилятором и общие правила, которые следует учитывать при создании программ, которые содержат фрагменты на разных языках: нам следует разобраться в том, как передаются параметры функциям, как возвращаются результаты из этих функций и как даются имена функциям.

Обычно если функция получает на вход некоторые параметры, размер которых не превышает 32 бита то они передаются через регистры, если передаваемая переменная больше чем 32 бита, то либо она передается в паре eax/edx либо передается указатель на данную переменную. Так как количество регистров ограничено, то передаваемые параметры помещаются в стековую память в определенном порядке, в том же стеке сохраняется адрес точки, куда необходимо вернуть управление после завершения вызова функции. Разные компиляторы по-разному генерируют код в каком порядке передавать параметры через стек и кто должен заботиться об очистке стека после вызова. Компилятор visualC++ различает следующие способы:

Модель Описание
__cdecl
стек должен очищать тот кто вызвал функцию, 
параметры помещаются в стек в обратном порядке, справа на лево
__stdcall
стек очищает сама вызванная функция, параметры передаются 
через стек и тоже в обратном порядке.
__fastcall
любители cbuilder сразу вспомнили данную директиву, 
стек очищается вызванной функцией, параметры по возможности 
размещаются в регистрах и если только они там не помещаются, 
то остатки размещаются в стеке – данный способ оправдывает свое название 
быстрого вызова, работа с регистрами заведомо быстрее чем с памятью.
thiscall
это не ключевое слово (просто модель вызова) используется при 
описании членов класса, параметры помещаются в стек, указатель 
на текущий экземпляр класса (this) хранится в регистре ECX. 
Стек очищает вызванная функция.

Раньше существовали еще несколько ключевых слов: __pascal, PASCAL.. В настоящее время они в заголовочных файлах “windef.h” переопределены как __stdcall и использовать их не стоит.

Следующее что нам следует разобрать – понятие декорации имен. Как известно в c/c++ мы можем описать функции имеющие одно и тоже имя но различающиеся списком параметров. Следовательно компилятор не может их различать по именам, которые им дает программист. Выполняется так называемая декорация имен.

Модификатор PASCAL просто преобразовывал имя функции в верхний регистр. Для модификатора __stdcall принято, что имя функции дополняется спереди символом “_” затем после имени ставится знак “@” и после этого символа пишется число байт занимаемых списком параметров, сколько байт нужно отвести в стеке для передачи данных аргументов. name decoration как пример, если мы объявим функцию как:

int  __stdcall func (int a, double b)

то ее новое имя будет:

_func@12

Модификатор cdecl не меняет имя функций в верхний регистр как PASCAL а просто добавляет перед именем символ подчеркивания “_”.

Для того чтобы упростить понимание правил декорации имен попробуйте в visual c++ создать проект (обычный win32console) объявить в нем множество функций с различными модификаторами, а затем посмотреть в “*.MAP” файле новые имена. Для генерации MAP файла надо в опциях линкера (настройки проекта) поставить галку “generate mapfile”

Изображение:plusPrologplus14jpg.jpg

Так если скомпилировать проект, например, с таким кодом:

bool Predicate (char info) {
     return islower(info);
}
 
void Op (char & info){
if (islower(info))
  info = toupper (info);
else
  info = tolower (info);
}
 
void main()
{}


То содержимое файла *.MAP будет выглядеть примерно так:


Много всего разного ----

0001:000010e0  ?Predicate@@YA_ND@Z 004020e0 f dlq.obj 0001:00001130  ?Op@@YAXAAD@Z 00402130 f dlq.obj 0001:000011b0 _main 004021b0 f dlq.obj


Много всего разного ----

Так что мы возвращаемся к visual c++ и создаем новый проект, в котором объявляем некоторую внешнюю функцию, соответствующую тому предикату, который мы экспортировали из пролога.

Для того чтобы избежать декорации имен используйте директиву extern “C”.

extern "C" void __stdcall  say_hello (void);
int main(int argc, char* argv[])
{
   say_hello ();
   return 0;
}

Следующим шагом будет добавление к проекту visualc++ *.LIB файла, который был создан компилятором пролога. После сборки проекта его можно запускать, не забудьте только скопировать *.DLL туда где она будет видна exe-файлу. Лучше всего не засорять системную директорию X:\windows или X:\windows\system32 а положить DLL в папку Debug рядом с исполнимым файлом на visualc++. Запустим проект и получим:

Изображение:plusPrologplus15jpg.jpg

Следующим шагом будет передача в программу на прологе строку текста для некоторой ее обработке, здесь мы просто выведем на экран строку текста:

extern "C" void __stdcall  say_it (char *);
...
say_it ("its string passed from c++ to prolog");
say_it (TheString) :-
   write (TheString), nl .

сейчас нам необходимо установить соответствия между стандартными типами данных пролога и c/c++. То что строки в стиле пролога string – это char * мы уже знаем, а для проверки числовых типов попробуем написать нечто вроде калькулятора: напишем предикаты:

add_numbers – для сложения чисел, (здесь мы поработаем с вещественными числами)
mod_divide_number – для вычисления остатка от деления одного числа на другое – а здесь с целочисленными переменными.

Вот код на прологе (надеюсь в *.DEF файл описания вы сможете сами добавить):

global predicates
  procedure add_numbers (real , real , real)  -(i , i , o) language stdcall
  procedure mod_divide_number (integer , integer , integer)  -(i , i , o) language stdcall
 clauses
  add_numbers (X , Y, Rez):-
   Rez = X + Y.
  mod_divide_number (X , Y , Rez) :-
   Rez = X mod Y.

На стороне visualc++ будет следующий код:

extern "C" void __stdcall add_numbers (double , double , double *);
int main(int argc, char* argv[])
{
  double z;
  add_numbers (1 , 2 , &z);
  printf ("Z =  %lf", z);
  return 0;
}

По аналогии добавим код, использующий и новую версию функции получения остатка от деления двух чисел. Не забывайте после каждого изменения проекта пролога копировать новый “*.dll” файл в каталог Debug. Если же вы забудете сделать это, то при запуске рискуете увидеть сообщение вроде такого:

Изображение:plusPrologplus16jpg.jpg

Приятно, что возможность написания имен переменных на русском языке сохранилась, в принципе это ведь всего лишь фрагмент памяти, выделенный в стеке и его имя лишь абстракция, так что иного и не ожидалось, но все равно это приятно, а вот то, что можно именовать по-русски имена предикатов это пожалуй удивительно. Если такого не увидишь то, пожалуй, и не поверишь сразу, вот пример того, как будет выглядеть в depends список экспортируемых символов.

Замечание: depends – просто замечательная утилита, из джентльменского набора любого программиста, позволяет посмотреть список функций, которые экспортируются из некоторой dll, а также ее зависимости от других библиотек. Найти ее можно в стандартной поставке visual studio, примерно в каталоге “C:\Program Files\Microsoft Visual Studio\Common\Tools\DEPENDS.EXE".

Изображение:plusPrologplus17jpg.jpg

Единственное замечание: для того чтобы данный пример с русскими буквами скомпилировался, я использовал в качестве линкера link.exe из поставки visual studio. Если хотите поэкспериментировать, то установите в свойствах проекта “Main program = MSVC++ 32bit”. Кроме этого надо указать путь к каталогу где находится линкер. Делаем это на закладке “Other Options”.

Изображение:plusPrologplus18jpg.jpg Изображение:plusPrologplus19jpg.jpg

Остается только написать клиентскую часть которая будет вызывать предикаты нашей dll. Здесь нам необходимо уходить от visualc++ ибо попытка объявить функцию которая имеет русское имя никогда не удастся. А вот если сделать прыжок к VBA где и функции и переменные могут иметь русские имена, то может и получится.


Declare Sub AddNumbersVBA Lib "tdll1" Alias "add_numbers" (ByVal A As Double, ByVal B As Double, ByRef C As Double)
Declare Sub Сложи_Два_Числа Lib "tdll1" Alias "сложи_два_числа" (ByVal A As Double, ByVal B As Double, ByRef C As Double)
Declare Sub сложи_два_числа Lib "tdll1" (ByVal A As Double, ByVal B As Double, ByRef C As Double)
 
 
 
Sub zed()
 Dim C As Double
 Call AddNumbersVBA(1, 2, C)
 MsgBox "C = A+B = " & CStr(C)
 Call Сложи_Два_Числа(1, 2, C)
 MsgBox "Русская версия имени функции работает: C = A+B = " & CStr(C)
 Call сложи_два_числа(1, 2, C)
 MsgBox "И даже так русская версия имени функции работает: C = A+B = " & CStr(C)
End Sub

Для работы с символами (char) мы напишем пример предиката, выполняющего кодирование символа с помощью шифра Цезаря, напомню, что в данном шифре каждый символа заменяется на отстоящий от него на X позиций. Например, строка “abc” с ключом 3 будет равна “def”.

cipher (Символ, Смещение, РезультирующийСимвол) :-
  char_int (Символ, КодСимвола),
  РезультирующийКод = КодСимвола + Смещение,
  char_int (РезультирующийСимвол, РезультирующийКод).

Важно, что когда мы создаем глобальные предикаты, то их параметры должны быть либо стандартных типов данных, например integer, real или же если необходим пользовательский тип данных, то его определяют в секции “global domains”, например:

global domains
 Символ = char
 Ключ = integer
global predicates
  procedure cipher (Символ, Символ, Ключ) - (i , i , o) language stdcall

Код на visualc++ будет выглядеть примерно так:

extern "C" void __stdcall add_numbers (double , double , double *);
extern "C" void __stdcall cipher (char , int , char *);
 
int main(int argc, char* argv[])
{
  double z;
  add_numbers (1 , 2 , &z);
  char code;
  cipher ('a' , 3 , &code);
  printf ("Z =  %lf", z);
  printf ("cipher =  %c", code);
  return 0;
}

Подведем итог: для простых типов данных пролога существуют следующие соответствия:

char – char real – double integer – int symbol, string = char*

в том случае если некоторый параметр возвращается из предиката пролога, то его объявляют как указатель. Далее я привожу пример, как можно получить из пролога строку текста: наша программа, предикат, если быть точным, будет спрашивать пользователя как его зовут, а затем введенное значение будет возвращаться программе на visualc++ которая просто выведет его на экран.

global predicates
  procedure get_user_name (string) - (o) language stdcall
clauses
 get_user_name (UserName) :-
  write ("What is your name ?"),
  readln (UserName).


И, соответственно, код на c/c++.

extern "C" void __stdcall get_user_name (char **);
int main(int argc, char* argv[])
{
 char * name ;
 get_user_name (&name);
 printf ("C++: your name is %s" , name);
 return 0;
}

Хотя данный пример замечательно заработал, но натренированный глаз заметит слабое место – это управление памятью, не совсем ясно откуда взялся тот буфер куда была помещена строка и когда она была освобождена. После детального анализа примеров выяснилось, что они в аналогичной ситуации перед получением из пролога строк вызывали специальный предикат: “dll_mark_gstack”, а после того как полученная строка уже не нужна, например мы ее скопировали в какой-нибудь внутренний для кода на c/c++ буфер, то вызывался предикат: “dll_release_gstack”. Ниже приводится пример кода для этих предикатов:

dll_mark_gstack(STACKMARK):- STACKMARK=mem_MarkGStack().
  dll_release_gstack(STACKMARK):-mem_ReleaseGStack(STACKMARK).

Давайте разберемся с тем, как dll на прологе управляет памятью. Когда загружается библиотека то выделяется блок памяти необходимый для работы машины вывода пролога. Данная память называется Gstack – Global Stack. Общее правило при работе с этим стеком (как в принципе и с любыми динамически выделяемыми ресурсами) – закончил пользоваться ресурсами памяти, и освободи их. В справке (воистину кладезь знаний) говорится, что общепринятым способом освобождения памяти является вызов предиката fail.

somecallback( String, 0 ):-
dlg_Note("In callback", String),
fail. %<---- free GStack
somecallback( _, 0).

Однако более легко работать со специальными предикатами управления памятью:

mem_MarkGStack - пометка текущей позиции указателя на вершину GStack, mem_ReleaseGStack – освобождение памяти, проще говоря позиционирование указателя на сохраненное с помощью предыдущего предиката состояние, mem_SystemFreeGStack - память которая занимается Gstack и не используется машиной вывода освобождается и передается операционной системе.

Вызывать данные предикаты можно как внутри dll (внутри вызванного извне предиката), например, так:

dll1_custom_Create(Parent,Rct, Id, Win):-          % (i,i,i,o)
  %"Win" is output parameter, but memory for it is not allocated on GStack
  Stackmark = mem_MarkGStack(),
   Win = custom_Create(Parent,RCT, Id),
   mem_ReleaseGStack(Stackmark).


И самое важное то, что вызывать эти предикаты можно и из внешнего кода. Именно таким приемом пользуются разработчики примеров для vip5, например:

unsigned long stackmark;
 char *Out;
 dll_mark_gstack(&stackmark);
 getString(&Out);
 SetDlgItemText( hDlg, idc_s_ret, Out );
 dll_release_gstack(stackmark);

Здесь, getString предикат, который возвращает строку, т.е. объявлен как:

GLOBAL PREDICATES
  procedure getString(string Out) - (o) language stdcall
DATABASE - dll_database
  single s(string)
CLAUSES
  s("Empty").
  dll_mark_gstack(STACKMARK):- STACKMARK=mem_MarkGStack().
  dll_release_gstack(STACKMARK):-mem_ReleaseGStack(STACKMARK).
  setString(In):-
    assert(s(In)).
 
  getString(OutStr):- % OutStr is output parameter allocated on Prolog GStack
    s(PrologStr),                 
    ASCIIZ_2_VB_String(PrologStr, OutStr). %Convert to BSTR string type. Needed for Visual Basic.

Теперь остается разобраться кое с какими техническими деталями, если просто набрать в пролог-коде код тела для предикатов: “dll_mark_gstack” и “dll_release_gstack”, то получится ошибка времени компиляции, нам необходимо подключить специальные библиотеки, например, так:

include "types.dom"
IFDEF use_runtime
  include "pdcrunt.dom"
  include "pdcrunt.pre"
ENDDEF

Теперь все отлично компилируется, и мы можем переписать предыдущий код на visualc++.

extern "C" void __stdcall get_user_name (char **);
extern "C" void __stdcall  dll_mark_gstack(unsigned long *out);
extern "C" void __stdcall  dll_release_gstack(unsigned long in);
 
int main(int argc, char* argv[])
{
 char * name ;
 unsigned long pointee;
 dll_mark_gstack(&pointee);
 get_user_name (&name); // wrapped
 dll_release_gstack(pointee);
 printf ("\nC++: your name is %s" , name);
 return 0;
}

А вот демонстрация того, что все работает просто замечательно:

Изображение:plusPrologplus110jpg.jpg

Что же, передавать и получать простые параметры мы уже научились, остается только научиться работать, например, со списками. Как вы знаете списки в прологе одни из наиболее часто используемых типов данных. Я сразу набросал предикат, который выполняет преобразование элементов списка целых чисел, меняя им знак на противоположный.

global domains
 ИСписок = integer*
predicates
  procedure change_list (ИСписок , ИСписок) - (i , o) language stdcall 
clauses
  change_list ([] , []):-!.
  change_list ([X|Y] , [X1|Y1]):-
   X1 = -X,
   change_list (Y , Y1).

Хотя данный предикат прекрасно работает в среде пролога:

GOAL
  change_list ([1 , 2, -3 , 4, -5], X).
X=[-1,-2,3,-4,5]
1 Solution

но увы, попытка его вызова из c приводит к ошибке времени выполнения.

extern "C" void __stdcall  get_list (int *, int *);
void main (){
 double x;
 int ix;
 char after;
 add_number (1 , 2 , &x);
 mod_divide_number (10 , 3 , &ix);
 cipher ('a' , 3 , &after);
 printf("add_number %lf\n" , x);
 printf("mod_divide_number %d\n" , ix);
 printf("cipher %c\n" , after);
 int arr []  = {1 , 2,  -3 , 4 , -5};     
 int arr2 [5];
 get_list (arr , arr2);
 for (int i = 0; i < 5; i++)
 printf ("%d, " , arr2 [i]);
}


Изображение:plusPrologplus111jpg.jpg


Что же не все так, просто может быть кто-то верил что все наши примеры будут с первого раза работать, увы нет. Придется засесть за документацию, благо с прологом идет вполне неплохая справка. Самая большая сложность состоит в том, каков будет заголовок функции на C/C++ соответствующей определенному предикату. И если с именами проблем нет, то вот с входными и выходными параметрами, они уже появляются. В справке идет ссылка на программку с именем pro2c (что-то вроде prolog to c converter), которая и является лекарством, ибо умеет по входному файлу *.pro создать выходной заголовочный файл для c/c++. Правда по закону подлости данного файла в моем дистрибутиве не нашлось (по ходу дела, правда, выяснилось, что данная утилита не распространяется бесплатно вместе с некоммерческой версией пролога (и если скачать дистрибутив, то ее там не будет) а идет вместе с коммерческой, за деньги, разумеется). Так что придется разбираться со справкой без наглядного примера. В ходе данного разбирательства оказалось, что списки принимаются и передаются с помощью ... разумеется тоже списков. Придется вспомнить все, что мы знаем из курса структур данных и алгоритмов. В общем случае под списком понимаем множество узлов хранящих информацию между данными узлами установлены связи в виде указателей для каждого узла на узел предшествующий или последующий за текущим, а вполне возможно и обе ссылки сразу. для того чтобы показать, что следующего элемента нет использовался указатель на NULL. Списки в стиле пролога имеют несколько иное устройство, хотя аналогии очевидны:

вот пример объявления списка целых чисел:

typedef struct i_wrapper {
 unsigned char is_eolist;
 int  data;
 struct i_wrapper *next;
} iwrapper;

Здесь поле data - (очевидно), это информация, само значение числа хранящегося в списке. поле next - ссылка на последующий элемент списка. и поле is_eolist - признак того что текущий элемент последний и последующего нет. так для того чтобы обозначить что список закончился, используется значение 2. В противной ситуации - 1.

Для проверки данного утверждения попробуем написать простенький предикат, который подсчитывает количество элементов списка:

get_length_of_list ([] , 0):-!.
  get_length_of_list ([H|T] , N):-!,
    get_length_of_list (T , N1),
    N = 1 + N1.
extern "C" void __stdcall  get_length_of_list (i_wrapper *, int *);
void main (void){
 iwrapper * pointee_in = NULL;
 int number;
 do{
 printf ("enter number? ");
 scanf ("%d" , &number);
 iwrapper *iw = new iwrapper;
 iw->data = number;
 iw->is_eolist = 1;
 iw->next = pointee_in;
 pointee_in = iw;
 }while (number != 0);
 
 iwrapper *iw = pointee_in;
 while (iw->next)
 iw = iw->next;
 iw->is_eolist = 2;
 int size;
 get_length_of_list (pointee_in , &size);
 printf ("size = %d\n" , size);
}

Изображение:plusPrologplus112jpg.jpg

Теперь можно записать фрагмент кода который получает из пролога список целых чисел и выводит его на экран, вернемся снова к задаче с изменением знака всех элементов списка.

На стороне пролога мы пишем:

global domains
 ilist = integer*
global predicates
  procedure get_const_list (ilist) - (o) language stdcall
clauses
 get_const_list ([1,2,3,4,5]).

На стороне c мы пишем такой код:

typedef struct i_wrapper {
 unsigned char is_eolist;
 int  data;
 struct i_wrapper *next;
} iwrapper;
 
 
extern "C" void __stdcall  get_const_list (i_wrapper ** );
int main(int argc, char* argv[])
{
 iwrapper * pointee , arr_i [10];
 pointee = arr_i;
 get_const_list (&pointee);
 while (pointee->is_eolist == 1){
  printf ("%d, " , pointee->data);
  pointee = pointee->next;  
 };
 return 0;
}

Изображение:plusPrologplus113jpg.jpg

И еще пожалуй для демонстрации я приведу пример копии экрана с отладчиком где показывается в watch структура списка.

Изображение:plusPrologplus114jpg.jpg

extern "C" void __stdcall  change_list (i_wrapper *, i_wrapper **);
int main(int argc, char* argv[])
{
 iwrapper * pointee;
 iwrapper * pointee_in = NULL;
 int number;
 do{
 printf ("enter number? ");
 scanf ("%d" , &number);
 iwrapper *iw = new iwrapper;
 iw->data = number;
 iw->is_eolist = 1;
 iw->next = pointee_in;
 pointee_in = iw;
}while (number != 0);
 
iwrapper *iw = pointee_in;
while (iw->next)
iw = iw->next;
iw->is_eolist = 2;
 
change_list (pointee_in, &pointee);
while (pointee->is_eolist == 1){
 printf ("%d, " , pointee->data);
 pointee = pointee->next;  
};
 
iw = pointee_in;
while (iw->next){
 iwrapper * iwnext = iw->next;
 delete iw;
 iw = iwnext;
}
 
return 0;
}

Изображение:plusPrologplus115jpg.jpg

А вот и проблема проявилась: точнее не проблема, а ошибка, приведенный мною выше код по формированию списка ошибочен. я не заметил, что элементы помещаются в список в порядке обратном требуемому, что же давайте исправим, а может сами сделаете, не глядя на дальнейшее решение.

int main(int argc, char* argv[])
{
 iwrapper * pointee;
 iwrapper * pre_end = new iwrapper;
 iwrapper * the_end = pre_end;
 pre_end->is_eolist = 2;
 pre_end->data = 0; 
// не имеет значение какое число присвоить информационному полю, ведь оно все равно
// будет проигнорировано
 int number;
 do{
  printf ("enter number? ");
  scanf ("%d" , &number);
  if (number == 0) break;
  iwrapper *iw = new iwrapper;
  iw->data = number;
  iw->is_eolist = 1;
  iw->next = the_end;
  pre_end->next = iw;
  pre_end = iw;
}while (true);
 
change_list (the_end->next, &pointee);
while (pointee->is_eolist == 1){
 printf ("%d, " , pointee->data);
 pointee = pointee->next;  
};
 
iwrapper * iw = the_end->next;
unsigned char is_eolist;
do{
 is_eolist = iw->is_eolist; 
 iwrapper * iwnext = iw->next;
 delete iw;
 iw = iwnext;
}while (is_eolist == 1);
 return 0;
}

Следующим шагом для обмена данными между программами на c/c++ и прологе, будет передача функторов. Примеры функтора и обработка его приводится ниже. Приведенная программа считывает с клавиатуры список функторов и записывает их в файл, например, для дальнейшей обработки.

global database - phonebook 
 phone (string UserName , string Phone)
clauses
  read_phone_book :-
     write ("Enter UserName ?"),
     readln (UserName),
     write ("Enter UserPhone ?"),
     readln (UserPhone),
     asserta (phone (UserName, UserPhone)),
     write ("continue (y/n) ?"),
     readchar (Continue),
     Continue = 'n',!,
     save ("file.base" , phonebook).
  read_phone_book:-
     nl,
     read_phone_book.
GOAL
 read_phone_book.

Изображение:plusPrologplus116jpg.jpg


В результате выполнения мы получим текстовой файл со следующим содержимым:

phone("Tatiana","345-67-89")
phone("Petro","234-67-89")
phone("Vasyan","123-45-67")

Теперь давайте попробуем вернуть в получившийся в результате выполнения данного предиката список телефонов людей в программу на c/c++.

Возникает вполне резонный вопрос: а как вернуть не одно значение а множество. очевидно что только в списке. значит нам необходимо найти способ поместить все эти накопленные факты в список. делается это примерно так:

global database - phonebook 
dphone (string UserName , string Phone)
global domains
the_phone = phone (string UserName , string Phone)
 iphone  = the_phone*
predicates
 procedure form_phone_list (iphone ListOfPhones)  - (o)
 nondeterm get_any_phone (the_phone)  - (o)
 nondeterm read_phone_book ()
clauses
 read_phone_book :-
     write ("Enter UserName ?"),
     readln (UserName),
     write ("Enter UserPhone ?"),
     readln (UserPhone),
     asserta (dphone (UserName, UserPhone)),
     write ("continue (y/n) ?"),
     readchar (Continue),
     Continue = 'n',!,
     save ("file.base" , phonebook).
  read_phone_book:-
     nl,
     read_phone_book.
  get_any_phone  (phone(UserName, UserPhone)):-
   dphone (UserName , UserPhone).
 
  form_phone_list (ListOfPhones):-
    findall ( PhoneEntry ,get_any_phone (PhoneEntry) , ListOfPhones).
GOAL
  read_phone_book, form_phone_list (BigList).

Изображение:plusPrologplus117jpg.jpg

Здесь мы воспользовались стандартным предикатом для Vip5 findall.

Следующим шагом будет передача списка содержащего записи из телефонной книги в программу на C/C++.

global database - phonebook 
 dphone (string UserName , string Phone)
global domains
 the_phone = phone (string UserName , string Phone)
 iphone  = the_phone*
global predicates
 procedure form_phone_list (iphone ListOfPhones, string FileName) - (o, i) language stdcall
 nondeterm get_any_phone (the_phone)  - (o)
 get_length_of_phones_list (iphone, integer) - (i , o)
clauses
 get_length_of_phones_list ([] , 0):-!.
 get_length_of_phones_list ([H|T] , N):-!,
    get_length_of_phones_list (T , N1),
    N = 1 + N1.
  procedure form_phone_list (iphone ListOfPhones, string FileName) - (o, i) language stdcall
  nondeterm get_any_phone (the_phone)  - (o)
   get_any_phone  (phone(UserName, UserPhone)):-
    dphone (UserName , UserPhone).
   form_phone_list (ListOfPhones, FileName):-
     write ("load phones from file " , FileName),
     consult (FileName , phonebook),
     findall ( PhoneEntry ,get_any_phone (PhoneEntry) , ListOfPhones),
     get_length_of_phones_list (ListOfPhones, Size),
     nl, write ("Formed List Size = ", Size).

На стороне c/c++ мы пишем следующий код:

typedef struct phone_tag {
 unsigned char fno;
 char * UserName;
 char * UserPhone;
} phone;
 
typedef struct phone_wrapper {
 unsigned char is_eolist;
 phone  * data;
 struct phone_wrapper *next;
} phonewrapper;
 
extern "C" void __stdcall  form_phone_list  (phonewrapper **, char * fname);
void main (void){
 phonewrapper * pwr;
 printf ("C++ code\n");
 form_phone_list (&pwr , "E:\\Programs_Files_2\\prolog_family\\prolog_documents\\vp5\\test_dll1\\Obj\\file.base");
 printf ("\nagain C++ code\n");
 while (pwr->is_eolist == 1){
 printf ("Phone (%s, %s)\n" , pwr->data->UserName , pwr->data->UserPhone);
 pwr = pwr->next; 
}
}

Обратите внимание на то, что структура phone_tag имеет дополнительное поле "unsigned char fno" - данное поле представляет, играет собой признак того что структура - функтор действительна и должна быть равна единице.

Изображение:plusPrologplus118jpg.jpg

Subscribe Now!

 

ObMachine projects & articles (java, flash, flex, php, ...)  -- black-zorro.com