Логическое программирование на Пролог. Часть 3

February 3, 2007

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

Далее мы начнем как и ранее описывать предикаты, но при этом следует придерживаться определенного правила: как известно ключевыми механизмами пролога являются унификация и backtracking, по этому на любой вопрос который мы задаем, например:
 father (X,Y)
мы можем получить произвольное количество результатов, от нуля до бесконечности (или чего-то очень большого) . для пролога это вполне нормально, а вот программы на c/Delphi и прочих алгоритмических языках такой неопределенности не поймут. Поэтому нам необходимо при создании предикатов в секции “predicates” выполнять их декларацию как “procedure” – если обратиться к справке пролога, раздел “Модели детерминизма”, то можно узнать, что данное ключевое слово означает что предикат находит одно и только одно решение и при этом не может потерпеть неудачу. Например:
  1. predicates
  2.  procedure foo (integer, integer)(i,i) (i,o)
При разработке тела для данного правила уже придется следить за кодом, чтобы действительно было возможно только одно решение. Например, нижеприведенный фрагмент кода будет замечательно работать и находить целых три решения:
  1. predicates
  2.   nondeterm find_solution (integer , string)
  3.  
  4. clauses
  5.   find_solution (X , "Число положительное или равно нулю"):-
  6.    X >=0.
  7.   find_solution (X , "Число равно нулю"):-
  8.    X =0.
  9.   find_solution (X , "Число отрицательное или равно нулю"):-
  10.    X <=0.
  11.  
  12. goal
  13.   find_solution (0 , КакоеЧисло).
КакоеЧисло=Число положительное или равно нулю

КакоеЧисло=Число равно нулю

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

3 Solutions



А если мы изменим тип детерминизма на procedure то получим ошибку компиляции.
 E;Test_Goal, pos: 407, 590 Nondeterministic clause: find_solution
Для избежания данной проблемы мы вынуждены пользоваться отсечением (cut, !).
  1. predicates
  2.   procedure find_solution (integer , string)
  3. clauses
  4.   find_solution (X , "Число положительное или равно нулю"):-
  5.    X >=0,!.
  6.   find_solution (X , "Число равно нулю"):-
  7.    X =0,!.
  8.   find_solution (X , "Число отрицательное или равно нулю"):-
  9.    X <=0,!.
  10. goal
  11.   find_solution (0 , КакоеЧисло).
Вот результат запуска программы:

КакоеЧисло=Число равно нулю

1 Solution

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

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



Мы начнем с самого просто, с того, что создадим в прологе предикат, единственной задачей которого будет вывод на экран сообщения.
  1. predicates
  2.  procedure say_hello ()
  3. clauses
  4.  say_hello:-
  5.    write ("Hello From Prolog"),nl.
В файле *.DEF мы добавим строчку в секции EXPORTS

А вот и еще одна ошибка:
 Error 2525: 'tdll1.def' - undefined name ‘say_hello'
Нам необходимо все имена на которые мы ссылаемся в *.DEF файле объявлять как глобальные. Для этого заменим директиву “predicates” на “global predicates”. И укажем в объявлении предиката директиву влияющую на правила именования функций: “language stdcall”, окончательно наш код будет выглядеть так:
  1. global predicates
  2.  procedure say_hello () – language stdcall
  3. clauses
  4.  say_hello:-
  5.    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 как пример, если мы объявим функцию как:
  1. int  __stdcall func (int a, double b)
то ее новое имя будет:
 _func@12
Модификатор cdecl не меняет имя функций в верхний регистр как PASCAL а просто добавляет перед именем символ подчеркивания “_”.

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



Так если скомпилировать проект, например, с таким кодом:
  1. bool Predicate (char info) {
  2.      return islower(info);
  3. }
  4.  
  5. void Op (char & info){
  6. if (islower(info))
  7.   info = toupper (info);
  8. else
  9.   info = tolower (info);
  10. }
  11.  
  12. void main()
  13. {}
То содержимое файла *.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”.
  1. extern "C" void __stdcall  say_hello (void);
  2. int main(int argc, char* argv[])
  3. {
  4.    say_hello ();
  5.    return 0;
  6. }
Следующим шагом будет добавление к проекту visualc++ *.LIB файла, который был создан компилятором пролога. После сборки проекта его можно запускать, не забудьте только скопировать *.DLL туда где она будет видна exe-файлу. Лучше всего не засорять системную директорию X:\windows или X:\windows\system32 а положить DLL в папку Debug рядом с исполнимым файлом на visualc++. Запустим проект и получим:



Следующим шагом будет передача в программу на прологе строку текста для некоторой ее обработке, здесь мы просто выведем на экран строку текста:
  1. extern "C" void __stdcall  say_it (char *);
  2. ...
  3. say_it ("its string passed from c++ to prolog");
  4. say_it (TheString) :-
  5.    write (TheString), nl .
сейчас нам необходимо установить соответствия между стандартными типами данных пролога и c/c++. То что строки в стиле пролога string – это char * мы уже знаем, а для проверки числовых типов попробуем написать нечто вроде калькулятора: напишем предикаты:
 add_numbers – для сложения чисел, (здесь мы поработаем с вещественными числами)
 mod_divide_number – для вычисления остатка от деления одного числа на другое – а здесь с целочисленными переменными.
Вот код на прологе (надеюсь в *.DEF файл описания вы сможете сами добавить):
  1. global predicates
  2.   procedure add_numbers (real , real , real)  -(i , i , o) language stdcall
  3.   procedure mod_divide_number (integer , integer , integer)  -(i , i , o) language stdcall
  4.  clauses
  5.   add_numbers (X , Y, Rez):-
  6.    Rez = X + Y.
  7.   mod_divide_number (X , Y , Rez) :-
  8.    Rez = X mod Y.
На стороне visualc++ будет следующий код:
  1. extern "C" void __stdcall add_numbers (double , double , double *);
  2. int main(int argc, char* argv[])
  3. {
  4.   double z;
  5.   add_numbers (1 , 2 , &z);
  6.   printf ("Z =  %lf", z);
  7.   return 0;
  8. }
По аналогии добавим код, использующий и новую версию функции получения остатка от деления двух чисел. Не забывайте после каждого изменения проекта пролога копировать новый “*.dll” файл в каталог Debug. Если же вы забудете сделать это, то при запуске рискуете увидеть сообщение вроде такого:



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

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



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





Остается только написать клиентскую часть которая будет вызывать предикаты нашей dll. Здесь нам необходимо уходить от visualc++ ибо попытка объявить функцию которая имеет русское имя никогда не удастся. А вот если сделать прыжок к VBA где и функции и переменные могут иметь русские имена, то может и получится.
  1. Declare Sub AddNumbersVBA Lib "tdll1" Alias "add_numbers" (ByVal A As Double, ByVal B As Double, ByRef C As Double)
  2. Declare Sub Сложи_Два_Числа Lib "tdll1" Alias "сложи_два_числа" (ByVal A As Double, ByVal B As Double, ByRef C As Double)
  3. Declare Sub сложи_два_числа Lib "tdll1" (ByVal A As Double, ByVal B As Double, ByRef C As Double)
  4.  
  5.  
  6.  
  7. Sub zed()
  8.  Dim C As Double
  9.  Call AddNumbersVBA(1, 2, C)
  10.  MsgBox "C = A+B = " & CStr(C)
  11.  Call Сложи_Два_Числа(1, 2, C)
  12.  MsgBox "Русская версия имени функции работает: C = A+B = " & CStr(C)
  13.  Call сложи_два_числа(1, 2, C)
  14.  MsgBox "И даже так русская версия имени функции работает: C = A+B = " & CStr(C)
  15. End Sub
Для работы с символами (char) мы напишем пример предиката, выполняющего кодирование символа с помощью шифра Цезаря, напомню, что в данном шифре каждый символа заменяется на отстоящий от него на X позиций. Например, строка “abc” с ключом 3 будет равна “def”.
  1. cipher (Символ, Смещение, РезультирующийСимвол) :-
  2.   char_int (Символ, КодСимвола),
  3.   РезультирующийКод = КодСимвола + Смещение,
  4.   char_int (РезультирующийСимвол, РезультирующийКод).
Важно, что когда мы создаем глобальные предикаты, то их параметры должны быть либо стандартных типов данных, например integer, real или же если необходим пользовательский тип данных, то его определяют в секции “global domains”, например:
  1. global domains
  2.  Символ = char
  3.  Ключ = integer
  4. global predicates
  5.   procedure cipher (Символ, Символ, Ключ) - (i , i , o) language stdcall
Код на visualc++ будет выглядеть примерно так:
  1. extern "C" void __stdcall add_numbers (double , double , double *);
  2. extern "C" void __stdcall cipher (char , int , char *);
  3.  
  4. int main(int argc, char* argv[])
  5. {
  6.   double z;
  7.   add_numbers (1 , 2 , &z);
  8.   char code;
  9.   cipher ('a' , 3 , &code);
  10.   printf ("Z =  %lf", z);
  11.   printf ("cipher =  %c", code);
  12.   return 0;
  13. }
Подведем итог: для простых типов данных пролога существуют следующие соответствия:

char – char

real – double

integer – int

symbol, string = char*

в том случае если некоторый параметр возвращается из предиката пролога, то его объявляют как указатель. Далее я привожу пример, как можно получить из пролога строку текста: наша программа, предикат, если быть точным, будет спрашивать пользователя как его зовут, а затем введенное значение будет возвращаться программе на visualc++ которая просто выведет его на экран.
  1. global predicates
  2.   procedure get_user_name (string) - (o) language stdcall
  3. clauses
  4.  get_user_name (UserName) :-
  5.   write ("What is your name ?"),
  6.   readln (UserName).
И, соответственно, код на c/c++.
  1. extern "C" void __stdcall get_user_name (char **);
  2. int main(int argc, char* argv[])
  3. {
  4.  char * name ;
  5.  get_user_name (&name);
  6.  printf ("C++: your name is %s" , name);
  7.  return 0;
  8. }
Хотя данный пример замечательно заработал, но натренированный глаз заметит слабое место – это управление памятью, не совсем ясно откуда взялся тот буфер куда была помещена строка и когда она была освобождена. После детального анализа примеров выяснилось, что они в аналогичной ситуации перед получением из пролога строк вызывали специальный предикат: “dll_mark_gstack”, а после того как полученная строка уже не нужна, например мы ее скопировали в какой-нибудь внутренний для кода на c/c++ буфер, то вызывался предикат: “dll_release_gstack”. Ниже приводится пример кода для этих предикатов:
  1. dll_mark_gstack(STACKMARK):- STACKMARK=mem_MarkGStack().
  2.   dll_release_gstack(STACKMARK):-mem_ReleaseGStack(STACKMARK).
Давайте разберемся с тем, как dll на прологе управляет памятью. Когда загружается библиотека то выделяется блок памяти необходимый для работы машины вывода пролога. Данная память называется Gstack – Global Stack. Общее правило при работе с этим стеком (как в принципе и с любыми динамически выделяемыми ресурсами) – закончил пользоваться ресурсами памяти, и освободи их. В справке (воистину кладезь знаний) говорится, что общепринятым способом освобождения памяти является вызов предиката fail.
  1. somecallback( String, 0 ):-
  2. dlg_Note("In callback", String),
  3. fail. %<---- free GStack
  4. somecallback( _, 0).
Однако более легко работать со специальными предикатами управления памятью:

mem_MarkGStack - пометка текущей позиции указателя на вершину GStack,

mem_ReleaseGStack – освобождение памяти, проще говоря позиционирование указателя на сохраненное с помощью предыдущего предиката состояние,

mem_SystemFreeGStack - память которая занимается Gstack и не используется машиной вывода освобождается и передается операционной системе.

Вызывать данные предикаты можно как внутри dll (внутри вызванного извне предиката), например, так:
  1. dll1_custom_Create(Parent,Rct, Id, Win):-          % (i,i,i,o)
  2.   %"Win" is output parameter, but memory for it is not allocated on GStack
  3.   Stackmark = mem_MarkGStack(),
  4.    Win = custom_Create(Parent,RCT, Id),
  5.    mem_ReleaseGStack(Stackmark).
И самое важное то, что вызывать эти предикаты можно и из внешнего кода. Именно таким приемом пользуются разработчики примеров для vip5, например:
  1. unsigned long stackmark;
  2.  char *Out;
  3.  dll_mark_gstack(&stackmark);
  4.  getString(&Out);
  5.  SetDlgItemText( hDlg, idc_s_ret, Out );
  6.  dll_release_gstack(stackmark);
Здесь, getString предикат, который возвращает строку, т.е. объявлен как:
  1. GLOBAL PREDICATES
  2.   procedure getString(string Out) - (o) language stdcall
  3. DATABASE - dll_database
  4.   single s(string)
  5. CLAUSES
  6.   s("Empty").
  7.   dll_mark_gstack(STACKMARK):- STACKMARK=mem_MarkGStack().
  8.   dll_release_gstack(STACKMARK):-mem_ReleaseGStack(STACKMARK).
  9.   setString(In):-
  10.     assert(s(In)).
  11.  
  12.   getString(OutStr):- % OutStr is output parameter allocated on Prolog GStack
  13.     s(PrologStr),                 
  14.     ASCIIZ_2_VB_String(PrologStr, OutStr). %Convert to BSTR string type. Needed for Visual Basic.
Теперь остается разобраться кое с какими техническими деталями, если просто набрать в пролог-коде код тела для предикатов: “dll_mark_gstack” и “dll_release_gstack”, то получится ошибка времени компиляции, нам необходимо подключить специальные библиотеки, например, так:
  1. include "types.dom"
  2. IFDEF use_runtime
  3.   include "pdcrunt.dom"
  4.   include "pdcrunt.pre"
  5. ENDDEF
Теперь все отлично компилируется, и мы можем переписать предыдущий код на visualc++.
  1. extern "C" void __stdcall get_user_name (char **);
  2. extern "C" void __stdcall  dll_mark_gstack(unsigned long *out);
  3. extern "C" void __stdcall  dll_release_gstack(unsigned long in);
  4.  
  5. int main(int argc, char* argv[])
  6. {
  7.  char * name ;
  8.  unsigned long pointee;
  9.  dll_mark_gstack(&pointee);
  10.  get_user_name (&name); // wrapped
  11.  dll_release_gstack(pointee);
  12.  printf ("\nC++: your name is %s" , name);
  13.  return 0;
  14. }
А вот демонстрация того, что все работает просто замечательно:



Что же, передавать и получать простые параметры мы уже научились, остается только научиться работать, например, со списками. Как вы знаете списки в прологе одни из наиболее часто используемых типов данных. Я сразу набросал предикат, который выполняет преобразование элементов списка целых чисел, меняя им знак на противоположный.
  1. global domains
  2.  ИСписок = integer*
  3. predicates
  4.   procedure change_list (ИСписок , ИСписок) - (i , o) language stdcall 
  5. clauses
  6.   change_list ([] , []):-!.
  7.   change_list ([X|Y] , [X1|Y1]):-
  8.    X1 = -X,
  9.    change_list (Y , Y1).
Хотя данный предикат прекрасно работает в среде пролога:
  1. GOAL
  2.   change_list ([1 , 2, -3 , 4, -5], X).
  3. X=[-1,-2,3,-4,5]
  4. 1 Solution
но увы, попытка его вызова из c приводит к ошибке времени выполнения.
  1. extern "C" void __stdcall  get_list (int *, int *);
  2. void main (){
  3.  double x;
  4.  int ix;
  5.  char after;
  6.  add_number (1 , 2 , &x);
  7.  mod_divide_number (10 , 3 , &ix);
  8.  cipher ('a' , 3 , &after);
  9.  printf("add_number %lf\n" , x);
  10.  printf("mod_divide_number %d\n" , ix);
  11.  printf("cipher %c\n" , after);
  12.  int arr []  = {1 , 2,  -3 , 4 , -5};     
  13.  int arr2 [5];
  14.  get_list (arr , arr2);
  15.  for (int i = 0; i < 5; i++)
  16.  printf ("%d, " , arr2 [i]);
  17. }


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

вот пример объявления списка целых чисел:
  1. typedef struct i_wrapper {
  2.  unsigned char is_eolist;
  3.  int  data;
  4.  struct i_wrapper *next;
  5. } iwrapper;
Здесь поле data - (очевидно), это информация, само значение числа хранящегося в списке. поле next - ссылка на последующий элемент списка. и поле is_eolist - признак того что текущий элемент последний и последующего нет. так для того чтобы обозначить что список закончился, используется значение 2. В противной ситуации - 1.

Для проверки данного утверждения попробуем написать простенький предикат, который подсчитывает количество элементов списка:
  1. get_length_of_list ([] , 0):-!.
  2.   get_length_of_list ([H|T] , N):-!,
  3.     get_length_of_list (T , N1),
  4.     N = 1 + N1.
  1. extern "C" void __stdcall  get_length_of_list (i_wrapper *, int *);
  2. void main (void){
  3.  iwrapper * pointee_in = NULL;
  4.  int number;
  5.  do{
  6.  printf ("enter number? ");
  7.  scanf ("%d" , &number);
  8.  iwrapper *iw = new iwrapper;
  9.  iw->data = number;
  10.  iw->is_eolist = 1;
  11.  iw->next = pointee_in;
  12.  pointee_in = iw;
  13.  }while (number != 0);
  14.  
  15.  iwrapper *iw = pointee_in;
  16.  while (iw->next)
  17.  iw = iw->next;
  18.  iw->is_eolist = 2;
  19.  int size;
  20.  get_length_of_list (pointee_in , &size);
  21.  printf ("size = %d\n" , size);
  22. }


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

На стороне пролога мы пишем:
  1. global domains
  2.  ilist = integer*
  3. global predicates
  4.   procedure get_const_list (ilist) - (o) language stdcall
  5. clauses
  6.  get_const_list ([1,2,3,4,5]).
На стороне c мы пишем такой код:
  1. typedef struct i_wrapper {
  2.  unsigned char is_eolist;
  3.  int  data;
  4.  struct i_wrapper *next;
  5. } iwrapper;
  6.  
  7.  
  8. extern "C" void __stdcall  get_const_list (i_wrapper ** );
  9. int main(int argc, char* argv[])
  10. {
  11.  iwrapper * pointee , arr_i [10];
  12.  pointee = arr_i;
  13.  get_const_list (&pointee);
  14.  while (pointee->is_eolist == 1){
  15.   printf ("%d, " , pointee->data);
  16.   pointee = pointee->next;  
  17.  };
  18.  return 0;
  19. }


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


  1. extern "C" void __stdcall  change_list (i_wrapper *, i_wrapper **);
  2. int main(int argc, char* argv[])
  3. {
  4.  iwrapper * pointee;
  5.  iwrapper * pointee_in = NULL;
  6.  int number;
  7.  do{
  8.  printf ("enter number? ");
  9.  scanf ("%d" , &number);
  10.  iwrapper *iw = new iwrapper;
  11.  iw->data = number;
  12.  iw->is_eolist = 1;
  13.  iw->next = pointee_in;
  14.  pointee_in = iw;
  15. }while (number != 0);
  16.  
  17. iwrapper *iw = pointee_in;
  18. while (iw->next)
  19. iw = iw->next;
  20. iw->is_eolist = 2;
  21.  
  22. change_list (pointee_in, &pointee);
  23. while (pointee->is_eolist == 1){
  24.  printf ("%d, " , pointee->data);
  25.  pointee = pointee->next;  
  26. };
  27.  
  28. iw = pointee_in;
  29. while (iw->next){
  30.  iwrapper * iwnext = iw->next;
  31.  delete iw;
  32.  iw = iwnext;
  33. }
  34.  
  35. return 0;
  36. }


А вот и проблема проявилась: точнее не проблема, а ошибка, приведенный мною выше код по формированию списка ошибочен. я не заметил, что элементы помещаются в список в порядке обратном требуемому, что же давайте исправим, а может сами сделаете, не глядя на дальнейшее решение.
  1. int main(int argc, char* argv[])
  2. {
  3.  iwrapper * pointee;
  4.  iwrapper * pre_end = new iwrapper;
  5.  iwrapper * the_end = pre_end;
  6.  pre_end->is_eolist = 2;
  7.  pre_end->data = 0; 
  8. // не имеет значение какое число присвоить информационному полю, ведь оно все равно
  9. // будет проигнорировано
  10.  int number;
  11.  do{
  12.   printf ("enter number? ");
  13.   scanf ("%d" , &number);
  14.   if (number == 0) break;
  15.   iwrapper *iw = new iwrapper;
  16.   iw->data = number;
  17.   iw->is_eolist = 1;
  18.   iw->next = the_end;
  19.   pre_end->next = iw;
  20.   pre_end = iw;
  21. }while (true);
  22.  
  23. change_list (the_end->next, &pointee);
  24. while (pointee->is_eolist == 1){
  25.  printf ("%d, " , pointee->data);
  26.  pointee = pointee->next;  
  27. };
  28.  
  29. iwrapper * iw = the_end->next;
  30. unsigned char is_eolist;
  31. do{
  32.  is_eolist = iw->is_eolist; 
  33.  iwrapper * iwnext = iw->next;
  34.  delete iw;
  35.  iw = iwnext;
  36. }while (is_eolist == 1);
  37.  return 0;
  38. }
Следующим шагом для обмена данными между программами на c/c++ и прологе, будет передача функторов. Примеры функтора и обработка его приводится ниже. Приведенная программа считывает с клавиатуры список функторов и записывает их в файл, например, для дальнейшей обработки.
  1. global database - phonebook 
  2.  phone (string UserName , string Phone)
  3. clauses
  4.   read_phone_book :-
  5.      write ("Enter UserName ?"),
  6.      readln (UserName),
  7.      write ("Enter UserPhone ?"),
  8.      readln (UserPhone),
  9.      asserta (phone (UserName, UserPhone)),
  10.      write ("continue (y/n) ?"),
  11.      readchar (Continue),
  12.      Continue = 'n',!,
  13.      save ("file.base" , phonebook).
  14.   read_phone_book:-
  15.      nl,
  16.      read_phone_book.
  17. GOAL
  18.  read_phone_book.


В результате выполнения мы получим текстовой файл со следующим содержимым:
 phone("Tatiana","345-67-89")
 phone("Petro","234-67-89")
 phone("Vasyan","123-45-67")
Теперь давайте попробуем вернуть в получившийся в результате выполнения данного предиката список телефонов людей в программу на c/c++.

Возникает вполне резонный вопрос: а как вернуть не одно значение а множество. очевидно что только в списке. значит нам необходимо найти способ поместить все эти накопленные факты в список. делается это примерно так:
  1. global database - phonebook 
  2. dphone (string UserName , string Phone)
  3. global domains
  4. the_phone = phone (string UserName , string Phone)
  5.  iphone  = the_phone*
  6. predicates
  7.  procedure form_phone_list (iphone ListOfPhones)  - (o)
  8.  nondeterm get_any_phone (the_phone)  - (o)
  9.  nondeterm read_phone_book ()
  10. clauses
  11.  read_phone_book :-
  12.      write ("Enter UserName ?"),
  13.      readln (UserName),
  14.      write ("Enter UserPhone ?"),
  15.      readln (UserPhone),
  16.      asserta (dphone (UserName, UserPhone)),
  17.      write ("continue (y/n) ?"),
  18.      readchar (Continue),
  19.      Continue = 'n',!,
  20.      save ("file.base" , phonebook).
  21.   read_phone_book:-
  22.      nl,
  23.      read_phone_book.
  24.   get_any_phone  (phone(UserName, UserPhone)):-
  25.    dphone (UserName , UserPhone).
  26.  
  27.   form_phone_list (ListOfPhones):-
  28.     findall ( PhoneEntry ,get_any_phone (PhoneEntry) , ListOfPhones).
  29. GOAL
  30.   read_phone_book, form_phone_list (BigList).


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

Следующим шагом будет передача списка содержащего записи из телефонной книги в программу на C/C++.
  1. global database - phonebook 
  2.  dphone (string UserName , string Phone)
  3. global domains
  4.  the_phone = phone (string UserName , string Phone)
  5.  iphone  = the_phone*
  6. global predicates
  7.  procedure form_phone_list (iphone ListOfPhones, string FileName) - (o, i) language stdcall
  8.  nondeterm get_any_phone (the_phone)  - (o)
  9.  get_length_of_phones_list (iphone, integer) - (i , o)
  10. clauses
  11.  get_length_of_phones_list ([] , 0):-!.
  12.  get_length_of_phones_list ([H|T] , N):-!,
  13.     get_length_of_phones_list (T , N1),
  14.     N = 1 + N1.
  15.   procedure form_phone_list (iphone ListOfPhones, string FileName) - (o, i) language stdcall
  16.   nondeterm get_any_phone (the_phone)  - (o)
  17.    get_any_phone  (phone(UserName, UserPhone)):-
  18.     dphone (UserName , UserPhone).
  19.    form_phone_list (ListOfPhones, FileName):-
  20.      write ("load phones from file " , FileName),
  21.      consult (FileName , phonebook),
  22.      findall ( PhoneEntry ,get_any_phone (PhoneEntry) , ListOfPhones),
  23.      get_length_of_phones_list (ListOfPhones, Size),
  24.      nl, write ("Formed List Size = ", Size).
На стороне c/c++ мы пишем следующий код:
  1. typedef struct phone_tag {
  2.  unsigned char fno;
  3.  char * UserName;
  4.  char * UserPhone;
  5. } phone;
  6.  
  7. typedef struct phone_wrapper {
  8.  unsigned char is_eolist;
  9.  phone  * data;
  10.  struct phone_wrapper *next;
  11. } phonewrapper;
  12.  
  13. extern "C" void __stdcall  form_phone_list  (phonewrapper **, char * fname);
  14. void main (void){
  15.  phonewrapper * pwr;
  16.  printf ("C++ code\n");
  17.  form_phone_list (&pwr , "E:\\Programs_Files_2\\prolog_family\\prolog_documents\\vp5\\test_dll1\\Obj\\file.base");
  18.  printf ("\nagain C++ code\n");
  19.  while (pwr->is_eolist == 1){
  20.  printf ("Phone (%s, %s)\n" , pwr->data->UserName , pwr->data->UserPhone);
  21.  pwr = pwr->next; 
  22. }
  23. }
Обратите внимание на то, что структура phone_tag имеет дополнительное поле "unsigned char fno" - данное поле представляет, играет собой признак того что структура - функтор действительна и должна быть равна единице.