Calling C Functions

A Phix program can call C routines and read and write C variables. C routines can also call Phix routines. The C code must reside in a Windows dynamic link library (.dll file), or a Linux shared library (.so file). By interfacing with .dlls and shared libraries, you can access the full programming interface on the host operating system, be that Windows or Linux.

Phix provides a mechanism for calling any C function in any Windows API .dll file, or indeed in any Windows .dll file that you or someone else creates. There is also a call-back mechanism that lets Windows call your Phix routines. Call-backs are necessary when you create a graphical user interface.

open_dll - open a Windows dynamic link library (.dll file) or Linux shared library (.so file)
define_c_func - define a C function that returns a value that your program will use
define_c_proc - define a C function that is VOID (no value returned), or whose value your program will ignore
define_c_var - get the memory address of a C variable.
c_proc - call a C function, ignoring any return value
c_func - call a C function and get the return value
call_back - get a machine address for a Phix routine to use as a call-back address
message_box - pop up a small window to get a Yes/No/Cancel response from the user
instance - get the instance handle for the current program

 
To make full use of the WINDOWS platform, you need documentation on Windows programming, in particular the WINDOWS Application Program Interface (API), including the C structures defined by the API. There is a large WIN32.HLP file (c) Microsoft that is available with many programming tools for Windows. There are numerous books available on the subject of WINDOWS programming for C/C++. You can adapt most of what you find in those books to the world of Phix programming for Windows. A good book is Programming Windows by Charles Petzold, Microsoft Press

To call a C function in a .dll or .so file you must perform the following steps:
  1. Open the .dll or .so file that contains the C function by calling open_dll() and ensuring the result is non-zero.
  2. Define the C function, by calling define_c_func() or define_c_proc(), with the number and type of the arguments as well as the type of value returned.

    Phix currently supports all C integer and pointer types as arguments and return values. It also supports floating-point arguments and return values (C double type). It is currently not possible to pass C structures by value or receive a structure as a function result, although you can certainly pass a pointer to a structure and get a pointer to a structure as a return value. Passing C structures by value is hardly ever required in Linux or the Windows API.
  3. Call the C function by calling c_func() or c_proc().


Example:
atom user32, icon
integer LoadIcon
    -- The name of the routine in user32.dll is "LoadIconA".
    -- It takes a pointer and an int as arguments, and returns an int.
    user32 = open_dll("user32.dll")
    if usr32=0 then puts(1,"cannot open user32.dll") abort(0) end if
    LoadIcon = define_c_func(user32, "LoadIconA",{C_POINTER,C_INT},C_INT)
    if LoadIcon=-1 then puts(1,"LoadIconA not found") abort(0) end if
    -- The function can now be invoked (as many times as needed).
    icon = c_func(xLoadIcon, {NULL, IDI_APPLICATION})
See demo\win32 or demo\linux for example programs.

You can examine a .dll file with Dependency Walker. You will see a list of all the C routines that the .dll exports.

To find out which .dll file contains a particular WIN32 C function, run demo\win32\dsearch.exw.

You can get the address of a C variable using define_c_var(). You can then use poke() and peek() to access the value of the variable.

Many C routines require that you pass pointers to structures. You can simulate C structures using allocated blocks of memory. The address returned by allocate() can be passed as if it were a C pointer.

You can read and write members of C structures using peek(), poke(), or their N(s|u) variants. You can obtain space for structures using allocate(). You must calculate the offset of a member of a C structure. This is usually easy, because anything in C that needs 4 bytes will be assigned 4 bytes in the structure. Thus C ints, chars, unsigned ints, pointers to anything, etc. will all take 4 bytes. If the C declaration looks like:
        struct example {
            int a;           // offset  0
            char *b;         // offset  4
            char c;          // offset  8
            long d;          // offset 12
        };
 
To allocate space for "struct example" you would need:
        atom p
        p = allocate(16) -- size of "struct example"
The address that you get from allocate() is always at least 4-byte aligned. This is useful, since WIN32 structures are supposed to start on a 4-byte boundary. Fields within a C structure that are 4-bytes or more in size must start on a 4-byte boundary in memory. 2-byte fields must start on a 2-byte boundary. To achieve this you may have to leave small gaps within the structure. In practice it is not hard to align most structures since 90% of the fields are 4-byte pointers or 4-byte integers.

You can set the fields using something like:
        poke4(p + 0, a)
        poke4(p + 4, b)
        poke4(p + 8, c)
        poke4(p +12, d)
You can read a field with something like:
     d = peek4(p+12)


Tip: For readability, make up constants for the field offsets. See Example below. (See also cffi)
        constant RECT_LEFT = 0,
         RECT_TOP  = 4,
         RECT_RIGHT = 8,
         RECT_BOTTOM = 12
         
atom rect = allocate(16)

    poke4(rect + RECT_LEFT,    10)
    poke4(rect + RECT_TOP,     20)
    poke4(rect + RECT_RIGHT,   90)
    poke4(rect + RECT_BOTTOM, 100)
     
    -- pass rect as a pointer to a C structure
    -- hWnd is a "handle" to the window
    if not c_func(InvalidateRect, {hWnd, rect, 1}) then
        puts(2, "InvalidateRect failed\n")
    end if
The Phix code that accesses C routines and data structures may look a bit ugly, but it will typically form just a small part of your program, especially if you use pGUI. Most of your program will be written in pure Phix, which will give you a big advantage over C.

Call-backs to your Phix routines



When you create a window, the Windows operating system needs to call a routine that you must provide. To set this up, you must get a 32-bit "call-back" address for your routine and give it to Windows. For example (taken from demo\win32\window.exw):

        integer id
        atom WndProcAddress
        
        id = routine_id("WndProc") 
        WndProcAddress = call_back(id)
routine_id() uniquely identifies a Phix procedure or function by returning a small integer value. This value can be used later to call the routine. You can also use it as an argument to the call_back() function.

In the example above, The call-back address, WndProcAddress, can be stored in a C structure and passed to Windows via the RegisterClass() C API function. This gives Windows the ability to call the Phix routine, WndProc(), whenever the user performs an action on a certain class of window. Actions include clicking the mouse, typing a key, resizing the window etc. See the window.exw demo program for the whole story.

Note: It is possible to get a call-back address for any Phix routine that meets the following conditions:
  • the routine must be a function, not a procedure
  • the parameters should all be of type atom (or integer etc.), not sequence
  • the return value should be an integer value up to 32-bits in size
  • You can create as many call-back addresses as you like.
The values that are passed to your routine can be any 32-bit unsigned atoms, i.e. non-negative. Your routine could choose to interpret large positive numbers as negative if that is desirable. For instance, if a C routine tried to pass you -1, it would appear as hex FFFFFFFF. [DEV] If a value is passed that does not fit the type you have chosen for a given parameter, a type-check error may occur (depending on with/without type_check etc.) No error will occur if you declare all parameters as atom.

Normally, as in the case of WndProc() above, Windows initiates these call-backs to your routines. It is also possible for a C routine in any .dll to call one of your Phix routines. You just have to declare the C routine properly, and pass it the call-back address.

Here is an example of a WATCOM C routine that takes your call-back address as its only parameter, and then calls your 3-parameter Phix routine:
        / * 1-parameter C routine that you call from Phix * /
        unsigned EXPORT APIENTRY test1(
                 LRESULT CALLBACK (*eu_callback)(unsigned a,
                                                 unsigned b,
                                                 unsigned c))
        {
            / * Your 3-parameter Phix routine is called here 
               via eu_callback pointer * /
            return (*eu_callback)(111, 222, 333); 
        }
The C declaration above declares test1 as an externally-callable C routine that takes a single parameter. The single parameter is a pointer to a routine that takes 3 unsigned parameters - i.e. your Phix routine.

In WATCOM C, "CALLBACK" is the same as "__stdcall", i.e "standard call". This is the calling convention that is used to call WIN32 API routines, and the C pointer to your routine must be declared this way too, or you will get an error when your routine tries to return to your .DLL. Note that the default calling convention for Windows C compilers is something different, called "__cdecl".

In the example above, your Phix routine will be passed the three values 111, 222 and 333 as arguments. Your routine will return a value to test1. That value will then be immediately returned to the caller of test1 (which could be at some other place in your Phix program).