6.10 Platform Specific Issues

6.10.1 Introduction

OpenEuphoria currently supports Euphoria on many different platforms. More platforms will be added in the future.

**DOS** platform support has been discontinued.

**Windows** in particular, the 32-bit x86 compatible version of Windows. The minimum version is Windows 95 Original Equipment Manufacturer Service Release 2.5. EUPHORIA will work on all old and new versions of Windows written after Windows 95. However, to use all of the features you must use Windows XP or later. See ''.

Linux. Linux is based on the UNIX operating system. It has recently become very popular on PCs. There are many distributors of Linux, including Red Hat, Debian, Caldera, etc. Linux can be obtained on a CD for a very low price. Linux is an open-source operating system.

FreeBSD. FreeBSD is also based on the UNIX operating system. It is very popular on Internet server machines. It's also open source.

Apple's OS X. OS X is also based on the UNIX operating system. While it is closed source, it is gaining a wide following due to it's ease of use and power.

OpenBSD. Open BSD is also a UNIX-like Operating System and is developed by volunteers.

NetBSD. Net BSD is also a UNIX-like Operating System and is designed to be easily portable to other hardware platforms.

Euphoria source files use various file extensions. The common extensions are:

extension application
.e Euphoria include file
.ew Euphoria include file for a Windowed (GUI) application only
.ex Console main program file
or any executable program
.exw Windowed (GUI) main program file
or a Windows specific program
.exu Unix specific program

It is convenient to use these file extensions, but they are not mandatory.

The Euphoria for Windows installation file contains eui.exe. It runs Euphoria programs on the Windows 32bit platform.

The Euphoria for Linux .tar file contains only eui. It runs Euphoria programs on the Linux platform.

Other versions of Euphoria are installed by first installing the Linux version of Euphoria, replacing eui with the version of eui for that Operating System, then rebuilding the other binaries from the source.

Sometimes you'll find that the majority of your code will be the same on all platforms, but some small parts will have to be written differently for each platform. Use the ifdef statement to tell you which platform you are currently running on.

You can also use the platform and platform_name functions:

printf(1, "Our platform number is: %d", {platform()})

The evaluation of platform occurs at 'runtime', you may even use a switch statement with it.

switch platform() do
    case WINDOWS then
       -- Windows code
    case LINUX then
       -- LINUX code
    case FREEBSD,NETBSD then
       -- BSD code
       ... etc
    case else
       crash("Unsupported platform")
end switch

Another way is to use parse-time evaluation using ifdefs.

ifdef WINDOWS then
  -- Windows code
elsifdef LINUX then
  -- LINUX code
elsifdef FREEBSD or NETBSD then
  -- BSD code
elsedef
    crash("Unsupported platform")
end ifdef

With parse-time evalution you get faster execution, for there is no conditional in the final code. You can put this deeply inside a loop without penalty. You can test for UNIX to see if the platform has UNIX like properties and thus will work on new UNIX like platforms without modification. You can even put statements that are top-level, such as constant and routine defintions. However, since the interpreter skips over the platforms you are not running on, syntax errors can hide in this construct and if you misspell an OS name you will not get warned.

ifdef UNIX then
	public constant SLASH='/'
	public constant SLASHES = "/"
	public constant EOLSEP = "\n"
	public constant PATHSEP = ':'
	public constant NULLDEVICE = "/dev/null"
	ifdef OSX then
		public constant SHARED_LIB_EXT = "dylib"
	elsedef
		public constant SHARED_LIB_EXT = "so"
	end ifdef
        
	public constant FOO =  SLASH == PATHSEP -- this has a hidden syntax error
	
elsifdef WINDOWS then

	public constant SLASH='\\'
	public constant SLASHES = "\\/:"
	public constant EOLSEP = "\r\n"
	public constant PATHSEP = ';'
	public constant NULLDEVICE = "NUL:"
	public constant SHARED_LIB_EXT = "dll"
	
elsifdef TRASHOS then  -- this symbol is never defined -- no error here either
       
end ifdef

In this above example, we have constant declarations which are different according to OS such things. The line with FOO has a syntax error but your interpreter will not catch it if you are running WINDOWS. There is no OS with the name 'TRASHOS'. I simply made it up and this construct will not warn you about mistakes like these.

Run-time evalution provides you something that is always syntax-checked and you can even make expressions using comparatives to avoid both parse-time and run-time branching all together.

add_code = {
    -- first int argument is at stack offset +4, 2nd int is at +8 
       #8B, #44, #24, #04,                  -- mov   eax, +4[esp]
       #03, #44, #24, #08,                  -- add   eax, +8[esp]
       #C2, #00, #08 * (platform() = WINDOWS) -- ret 8  
                                            -- pop 8 bytes off the stack
}

This is machine code to be put into memory as an example from demo/callmach.ex. Here if platform() = WINDOWS is true, then the code will pop 8 bytes off of the stack, if not it will pop 0 bytes off of the stack. This has to be done because of where the function call conventions are implemented in the various compilers. We use Watcom C for WINDOWS and GCC for the others. Now if the programmer had put a non-existent symbol, such as ARCH64, the parser would stop, point out the error, and the programmer would then fix it.

6.10.2 The Discontinued DOS32 Platform

This platform is no longer supported.

Those interested in writing DOS programs in Euphoria may use version 3.1 downloadable from the original ~RapidEuphoria website: http://www.rapideuphoria.com/v20.htm.

The DOS32 platform was for computers without Windows OS, and though people could still use the Euphoria binaries built for this platform on Windows, it was slower than and lacked features available on binaries built for the WINDOWS platform.

The binaries for this platform had support for low-level graphics and though DOS was 16-bit, the Euphoria binaries for DOS32 used techniques that allowed you to use 32-bit addresses transparently, hence the name of the platform: DOS32. However, in this platform you could not use dynamically loaded libraries and filenames had to be in a format of: eight letters, a dot, and three letters when creating a file. You could not use the Windowing system even if your computer had Windows. You were limited to full-sreen mode graphics and the text console.

6.10.3 The WINDOWS Platform

With the WINDOWS platform, your programs can still use the text console. Because most library routines work the same way on each platform most text mode programs can be run using the console interpreter of any platform without any change.

Since the Euphoria interpreter can work directly with your OS you can also create GUI programs. You can use a user submitted library from the archive or handle calls directly into the DLLs. There are high-level graphics libraries for Direct3D and OpenGL available from the Euphoria Web site.

A console window will be created automatically when a WINDOWS Euphoria program first outputs something to the screen or reads from the keyboard. If your program is displaying a screen, you will also see a console window when you read standard input or write to standard output, even when these have been redirected to files. The console will disappear when your program finishes execution, or via a call to free_console.

If you don't want a console to appear, it might help to put the following statements at the top of your Euphoria program:

-- Now, when there is input or output to the console we will get an error
-- and see in which line number this happens. 
close(STDOUT)
close(STDIN)

Now with these lines the interpreter is forced to give you a runtime error, report where in the program the standard input or output is used. It can be hard to find the offending I/O statement in programs that contain many commented out or debug mode only console I/O statements.

If you actually *want* to use the console, and there is something on the console that you want your user to read, you should prompt them and wait for his input before terminating. To prevent the console from quickly disappearing you might include a statement such as:

include std/console.e

any_key("Press any key to close this Window")

which will wait for the user enters something.

If you want to run an interpreted Euphoria program to use the current console use eui.exe but if you want it to create a new console window use euiw.exe.

Programs translated by the translator for this platform will also pop up a new console whenever input is asked for our output is sent to the screen unless you specify the -CON option.

When running an interpreter or translator for the WINDOWS platform, platform returns WINDOWS and a parsetime branch (with ifdef/end ifdef) with WINDOWS will be followed.

In order to use sockets you must have Windows 2000 Professional or later. In order for the the routines has_console and maybe_any_key to have useful behavior you must have Windows XP or later.

6.10.3.1 High-Level WINDOWS Programming

Thanks to David Cuny, Derek Parnell, Judith Evans and many others, there's a package called Win32Lib that you can use to develop Windows GUI applications in Euphoria. It's remarkably easy to learn and use, and comes with good documentation and many small example programs.

If you have a SVN client, you can get a Euphoria version 4.0-compatible Win32lib at: https://win32libex.svn.sourceforge.net/svnroot/win32libex/trunk. Get version 68.

There is also an IDE, by Judith Evans for use with Win32lib. https://euvide.svn.sourceforge.net/svnroot/euvide.

Matt Lewis has developed a wrapper for the wxWidgets library for Euphoria: wxEuphoria. It is cross-platform.

You can download WxEuphoria, Win32Lib and Judith's IDE from the Euphoria Web site.

6.10.3.2 Low-Level WINDOWS Programming

To allow access to WINDOWS at a lower level, Euphoria provides a mechanism for calling any C function in any WINDOWS API .dll file, or indeed in any 32-bit Windows .dll file that you create or someone else creates. There is also a call-back mechanism that lets Windows call your Euphoria routines. Call-backs are necessary when you create a graphical user interface.

To make full use of the WINDOWS platform, you need documentation on 32-bit Windows programming, in particular the WINDOWS Application Program Interface (API), including the C structures defined by the API. There is a large WINDOWS.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 Euphoria programming for WINDOWS. A good book is Programming Windows by Charles Petzold.

A WINDOWS API Windows help file (8 Mb) can be downloaded from ftp://ftp.borland.com/pub/delphi/techpubs/delphi2/win32.zip, Borland's Web site.

6.10.4 The Unix Platforms

As with WINDOWS, you can write text on a console, or xterm window, in multiple colors and at any line or column position.

Just as in WINDOWS, you can call C routines in shared libraries and C code can call back to your Euphoria routines.

You can get a Euphoria interface to high level graphics library OpenGL from the Euphoria Web site. OpenGL also works with Windows.

Easy X-windows GUI programming is available using either Irv Mullin's EuGTK interface to the GTK GUI library, or wxEuphoria developed by Matt Lewis. wxEuphoria also runs on Windows.

When porting code from Windows to Unix, you'll notice the following differences:

  • Some of the numbers assigned to the 16 main colors in graphics.e are different. If you use the constants defined in graphics.e you won't have a problem. If you hard-code your color numbers you will see that blue and red have been switched etc.
  • The key codes for special keys such as Home, End, arrow keys are different, and there are some additional differences when you run under XTERM.
  • The Enter key is code 10 (line-feed) on Linux, where on Windows it was 13 (carriage-return).
  • Other OSes use '/' (slash) on file paths. Windows use '\' (backslash). If you use the SLASH constant from std/filesys.e you don't have to worry about this however.
  • Calls to system() and system_exec() that contain Windows commands will obviously have to be changed to the corresponding Linux or FreeBSD command. e.g. "DEL" becomes "rm", and "MOVE" becomes "mv". Often you can use a standard library call instead and it will be portable across platforms. For example you can use filesys:create_directory or filesys:delete_file.

When running an interpreter or translator for a UNIX platform, platform will return one of the several symbols for UNIX and a parsetime branch (with ifdef/end ifdef) with UNIX and the symbol that is that of the specific OS will be followed.

We assume that the environment is always run from some kind of CLI in two routines: The routine has_console always returns 0, and maybe_any_key never waits for key input.

6.10.5 Interfacing with C Code

On WINDOWS and Unix it's possible to interface Euphoria code with C code. Your Euphoria program can call C routines and read and write C variables. C routines can even call ("callback") your Euphoria routines. The C code must reside in a dynamic link or shared library. By interfacing with dynamic link libraries and shared libraries, you can access the full programming interface on these systems.

Using the Euphoria to C Translator, you can translate Euphoria routines to C, and compile them into a shared library file. You can pass Euphoria atoms and sequences to these compiled Euphoria routines, and receive Euphoria data as a result. Translated/compiled routines typically run much faster than interpreted routines. For more information, see the Translator.

6.10.5.1 Calling C Functions

To call a C function in a shared library file you must perform the following steps:

  1. Open the shared library file that contains the C function by calling open_dll.
  2. Define the C function, by calling define_c_func or define_c_proc. This tells Euphoria the number and type of the arguments as well as the type of value returned.
    Euphoria 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 rarely required for operating system calls.
    Euphoria also supports all forms of Euphoria data - atoms and arbitrarily-complex sequences, as arguments to translated/compiled Euphoria routines.
  3. Call the C function by calling c_func or c_proc
include dll.e

atom user32
integer LoadIcon, icon

user32 = open_dll("user32.dll")

-- The name of the routine in user32.dll is "LoadIconA".
-- It takes a pointer and an 32-bit integers as arguments,
-- and it returns a 32-bit integer.
LoadIcon = define_c_func(user32, "LoadIconA", {C_POINTER, C_INT}, C_INT)

icon = c_func(LoadIcon, {NULL, IDI_APPLICATION})

See c_func, c_proc, define_c_func, define_c_proc, open_dll

See demo\win32 or demo/linux for example programs.

On Windows there is more than one C calling convention. The Windows API routines all use the __stdcall convention. Most C compilers however have __cdecl as their default. __cdecl allows for variable numbers of arguments to be passed. Euphoria assumes __stdcall, but if you need to call a C routine that uses __cdecl, you can put a '+' sign at the start of the routine name in define_c_proc() and define_c_func(). In the example above, you would have "+LoadIconA", instead of "LoadIconA".

You can examine a dll file by right-clicking on it, and choosing "QuickView" (if it's on your system). You will see a list of all the C routines that the dll exports.

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

6.10.5.2 Accessing C Variables

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.

6.10.5.3 Accessing C Structures

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 and poke, or peek4u, peek4s, and poke4. You can allocate 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 int's, char's, unsigned int's, pointers to anything, etc. will all take 4 bytes. If the C declaration looks like:

// Warning C code ahead!

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 = allocate(16) -- size of "struct example"

The address that you get from allocate is always at least 4-byte aligned. This is useful, since WINDOWS 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 Euphoria constants for the field offsets. See Example below.
constant RECT_LEFT = 0,
RECT_TOP  = 4,
RECT_RIGHT = 8,
RECT_BOTTOM = 12,
RECT_SIZEOF = 16

atom rect = allocate(RECT_SIZEOF)

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 Euphoria 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 Win32Lib, EuWinGUI, or Irv Mullin's X Windows library. Most of your program will be written in pure Euphoria, which will give you a big advantage over those forced to code in C.

6.10.5.4 Call-backs to your Euphoria routines

When you create a window, the Windows operating system will need to call your Euphoria routine. 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 Euphoria procedure or function by returning an 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 32-bit 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 Euphoria 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 Euphoria routine that meets the following conditions: * the routine must be a function, not a procedure * it must have from 0 to 9 parameters * 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, but you should not call call_back for the same Euphoria routine multiple times - each call-back address that you create requires a small block of memory.

The values that are passed to your Euphoria 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. If a value is passed that does not fit the type you have chosen for a given parameter, a Euphoria type-check error may occur (depending on type_check)
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 Euphoria routines. You just have to declare the C routine properly, and pass it the call-back address.

Here's an example of a WATCOM C routine that takes your call-back address as its only parameter, and then calls your 3-parameter Euphoria routine:

/* 1-parameter C routine that you call from Euphoria */
unsigned EXPORT APIENTRY test1(
    LRESULT CALLBACK (*eu_callback)(unsigned a,
    unsigned b,
    unsigned c))
{
    /* Your 3-parameter Euphoria 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 Euphoria routine.

In WATCOM C, "CALLBACK" is the same as "__stdcall". This is the calling convention that's used to call WINDOWS API routines, and the C pointer to your Euphoria routine should be declared this way too, or you'll get an error when your Euphoria routine tries to return to your .DLL.

If you need your Euphoria routine to be called using the __cdecl convention, you must code the call to call_back() as:

myroutineaddr = call_back({'+', id})

The plus sign and braces indicate the __cdecl convention. The simple case, with no braces, is __stdcall.

In the example above, your Euphoria 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 Euphoria program).

A call-back address can be passed to the UNIX signal() function to specify a Euphoria routine to handle various signals (e.g. SIGTERM). It can also be passed to C routines such as qsort(), to specify a Euphoria comparison function.