Although Visual Prolog is an excellent tool for many purposes, there are still reasons to use other languages. For example, it's easier to perform numeric integration in C, and interrupt-handling and low-level stuff is perhaps better done in Assembly language. Moreover, if you've developed a large program in another language that already solves some aspect of the problem, this work should not be wasted. For these reasons, Visual Prolog allows you to interface your programs with other languages, as long as those languages produce standard object files and follows the conventions outlined in this chapter.
In this chapter you will find a number of examples of interfacing C and Visual Prolog. Their source files are in the DOC¡¬EXAMPLES directory or the FOREIGN directory of your distribution. In order to run them, you need to have the appropriate development system and/or C compiler and libraries installed.
The process to compile and link the examples varies considerably between the different operating systems and the different C compilers. In the foreign subdirectory of your distribution you will find thorough instructions and examples for the different platforms and compilers. Read these instructions carefully.
When using the Visual Prolog Development Environment, you don't, strictly speaking, need to know how to compile C programs, how to run the linker, or which libraries to specify and how to do it. This is handled automatically. However, you should have a fairly thorough understanding about C, and be able to write, compile and link multi-module C programs yourself.
A dynamic-link library (DLL) is a binary file that acts as a shared library of predicates that can be used simultaneously by multiple applications.
Visual Prolog can generate dll¡¯s and link in dll¡¯s staticly or load dll¡¯s dynamicly. For more information about Visual Prolog and dll¡¯s please see the examples VPI¡¬EXAMPLES¡¬DLL and VPI¡¬TOOLEXAMP¡¬BUILD.
In this section, we cover what you need to know to call C, Pascal and assembler routines from Visual Prolog.
Before calling routines and functions written in other languages, you need to declare them as external predicates in Visual Prolog. You also need to understand the correct calling conventions and parameter-pushing sequences, and you need to know how to name the different flow variants of your external predicates.
To inform the Visual Prolog system that a given global predicate is implemented in another language, you need to append a language specification to the global predicates declaration, as briefly mentioned in chapter 17:
GLOBAL PREDICATES
add(integer,integer,integer) -
(i,i,o),(i,i,i) language c
scanner(string,token) - (i,o) language
pascal
triple(integer,real) - (i,o) language asm
In Visual Prolog, you explicitly list the interfaced language; this simplifies the problems inherent in calling conventions, such as activation record format, naming convention and returning conventions.
The 80x86 processor family gives programmers a choice between NEAR and FAR subroutine calls, when running 16-bit programs. Visual Prolog requires all global routines to be FAR. The same applies to pointers to data objects. Many 16-bit compilers for the 80x86 family require you to choose between 16-bit and 32-bit pointers, where the 16-bit pointers refer to a default segment. In order to access all of memory, Visual Prolog always uses 32-bit pointers.
For 32-bit programs, "NEAR" means 32 bits and the above considerations are irrelevant.
(1) Input parameters
For input parameters, the value is pushed directly, and the size of the parameter depends on its type.
(2) Output parameters
An output parameter is pushed as a 32-bit pointer to where a values must be assigned.
(3) Return Values
Visual Prolog follows the most widely adopted register convention for function values on the 80x86 CPU family. This should not be of any concern in most cases, but is included here for completeness.
Table 18.1: Registers for Return Values
Operand Size |
Program Type |
16 bit |
32 bit |
byte (8 bits) |
AL |
word (16 bits) |
AX |
dword (32 bits) |
DX:AX ³ EAX |
Pointers are 32 bits in size and are handled as dwords. The Program Type is determined by the operating system,
Floating point values are exceedingly troublesome to handle. They may be returned in registers, on the (emulated) coprocessor stack, and the pascal calling convention will frequently return them through pointers. Currently pascal functions cannot return floating point values. See the notes in the FOREIGN subdirectory of your distribution for any special considerations for your platform.
In any case, floating point values can always be returned in arguments. However, take special note that Visual Prolog's real corresponds to a C double (8 bytes).
You should also be aware that currently external C functions cannot return C structs (but they may of course return pointers to structs).
(4) Multiple declarations
In Visual Prolog, a predicate can have several type variants, arities, and flow variants, and a separate procedure is needed for each type and flow variant. When you implement predicates, having several versions, in C, each C function must have a name corresponding to the name generated by Visual Prolog. The naming convention used by Visual Prolog is straightforward; the predicate name is used as the root, and the suffix _X is appended to signify the variant number, where X is an integer starting at 0. If there is only one variant, no suffix is appended.
Consider the following program:
GLOBAL PREDICATES
add(integer,integer,integer) - (i,i,o),(i,o,i),(o,i,i),(i,i,i)
language c
square(integer,integer) - (i,o)
GOAL
add(2,3,X), write("2 + 3 = ",X),
nl,
add(2,Y,5), write("5 - 2 = ",Y),
nl,
add(Z,3,5), write("5 - 3 = ",Z),
nl,
add(2,3,5), write("2 + 3 is 5"),
nl,
square(5,Sq), write("5 squared is
",Sq).
A module linked with this program should contain the following C functions:
add_0 for the first flow pattern (i,i,o)
add_1 for the (i,o,i) flow pattern
add_2 for (o,i,i)
add_3 for (i,i,i)
square
As an example, the following C module implements square as well as all flow patterns for add:
add_0(int x, int y, int *z) /*
(i,i,o) flow pattern */
{ *z = x + y; }
add_1(int x, int *y, int z) /*
(i,o,i) flow pattern */
{ *y = z - x; }
add_2(int *x, int y, int z) /*
(o,i,i) flow pattern */
{ *x = z - y; }
add_3(int x, int y, int z) /*
(i,i,i) flow pattern */
{ if ( (x + y) != z ) RUN_Fail(); }
square(int i,int *i_sq)
{ *i_sq = i*i; }
(5) Parameter pushing order
When interfacing to a routine written in C, the parameters are pushed onto the stack in reverse order and, after return, the stack pointer is automatically adjusted by Visual Prolog.
When calling languages other than C, the parameters are pushed in the normal order, and the called function is responsible for removing the parameters from the stack.
(6) Leading underscored
On the 16bit platforms, C compilers will prefix the name of public C functions with an underscore. Therefore, global predicates declared as language C will also have their name prefixed with an underscore if the target platform is one of these.
(7) NT naming convention
For the Win32 API, the number of bytes pushed on the stack will together with a dollar sign be suffixed to the predicate name. this means that a predicate p which has two integer arguments will be names p$8. When choosing the calling convention stdcall under Win32, this convention will be used.
(8) Converting the name to Uppercase (Pascal)
PASCAl uses the convention, that the name is converted to uppercase. So if language PASCAl is used, the name will during .OBJ module generation be converted to uppercase.
(9) Adjustment of stackpointer
There are two possibilities of adjusting the SP register . This can be done either by the called function or the calling function. Traditionally PASCAL does this in the called function, while C does it in the calling function.
Table 18.2: Calling conventions
|
Convert name to upper case |
Add leading under Score |
Push args Reversed |
Adjust SP after return |
NT naming convention |
pascal |
X |
|
|
|
|
c |
|
X |
X |
X |
|
stdcall |
|
X |
X |
|
X |
syscall |
|
|
X |
X |
|
(10) The AS "external_name" Declaration
As an alternative to the automatic naming convention, you can use the as keyword in the global declaration, like this:
GLOBAL PREDICATES
scanner(string,token) - (i,o) language c
as "_myscan"
The result of this is that Visual Prolog will refer to the name _myscan in the object file instead of _scanner. You would still refer to the name scanner in your Visual Prolog source.
You can only use the as option if there is a single flow variant for the predicate.