Linux for Windows Developers

Rick RossPILLAR Technology Group, Inc.


Introduction

Linux Features

Architectural Differences

Linux Philosophy

Kernel

File System

Security

Shell

GUI

Key Concepts

Environment

Navigating the file system

Wildcard expansion

Virtual Consoles

Standard Input/Output/Error

Redirection

Peeking into Pipes

GUI

Directory Structure

Miscellaneous Tips,Tricks and Caveats

General porting steps

Transfer files

Copy .dfm to .xfm

Fix {$R} directives

Resolving property issues

Build the project

Resolve errors

Porting example: Taking an existing Delphi/VCL application to Linux

Additional Resources


Introduction

The purpose of this paper is to introduce the Linux operating system to Windows developers. The primary objective is to provide these developers with solid background information regarding the features and architecture of Linux. Along the way, tips and tricks will be pointed out that help when making existing applications cross-platform. Finally, this paper will demonstrate several programming areas where Linux and Windows differ.

Linux Features

Linux is a powerful operating system. First and foremost, it is a true multi-user, multi-tasking operating system. Out of the box, Linux is designed to handle multiple concurrent users. For those familiar with Windows NT/2000, this is similar to Citrix and Terminal Server.

Numerous ports of Linux have been made to a variety of processors like the Alpha, MIPS, Motorola 68000 series, Power PC, and Sparc processors. At this time, Kylix only supports the Intel i386 compatible processors (e.g. Pentium, AMD, etc).

Since Linux is licensed under the GNU General Public License, the source code is freely available. This provides an awesome way for learning what exactly a certain routine is doing, or perhaps even fixing a bug.

Last, but not least, Linux supports a large variety of file systems including FAT, NTFS, HPFS, ext2 and many more. Most file systems support reading and writing, however, others may still be in a development phase and may have a limited feature set.

Architectural Differences

Fundamentally, Linux and Windows have been designed with different goals in mind. In this section, these differences are highlighted. Keep in mind that this is more of a high-level view. In later sections, certain topics will be expanded to demonstrate these differences.

Linux Philosophy

The underlying philosophy of the Linux (and UNIX) is to take a problem and approach it with a “divide and conquer” technique. Linux does not have a single application that does everything the user can ever think or imagine. Instead, a number of utilities exist that perform a specific task extremely well. These utilities can then be combined to solve the problem at hand. Once this philosophy is understood, it becomes evident how powerful this idea is.

Kernel

At the heart of Linux lies the kernel. It contains the code that provides the services needed by processes that are running. The Linux kernel can be described as a mostly monolithic, whereas NT has a microkernel. In a monolithic kernel, all device drivers are part of the kernel, whereas in a microkernel, drivers are loaded and executed on demand. Linux does not need to load drivers, for example, into memory before using them. Windows operating systems must load the appropriate DLLs as needed (sometimes they are already in memory).

The Linux kernel has a number of options for configuring features within it. Certain options can enable or disable kernel functionality. Other features determine if drivers are disabled, compiled directly into the kernel or treat drivers as loadable modules. Since customization is available, not all installations will have the same exact kernel configuration. This is a major divergence from the Windows “everything belongs in the operating system” mentality.

Linux does not distinguish between processes and threads at the kernel level. In fact, a multi-threaded application is seen in the kernel as two processes that have shared memory. Why? Linux was around long before there was the concept of threads. Executing a process has been optimized and refined over the years to make it really fast. So it made more sense to make multi-threaded applications share memory rather than redesign the kernel. However, for a handful of applications, this design has a minor flaw, since the kernel does not realize that two threads are part of the same application. Windows, on the other hand, does distinguish between processes and threads (and fibers).

Looking again at the Linux philosophy, it is easy to see the reasoning for Linux not to include a Graphical User Interface (GUI) in the kernel. This separation allows for the flexibility of replacing the entire GUI subsystem without affecting the kernel. In addition, certain applications like a web server or an application server do not need the extra overhead of a GUI environment.

Linux has a set of special software interrupts called signals. These signals are asynchronous and are generated from various sources like hardware exceptions, certain key combinations and software conditions. Fortunately, Kylix maps most signals to exceptions. The following table shows the mappings

Signal

Description

Kylix Exception

SIGINT

User Interrupt (Control-C)

EControlC

SIGFPE

Floating Point Exception

EDivByZero, EInvalidOp, EZeroDivide, EOverflow, EUnderflow

SIGSEGV

Segmentation Violation (AV)

ERangeError, EIntOverflow, EAccessVioliation, EPrivilege, EStackOverflow

SIGILL

Illegal Instruction

ERangeError, EIntOverflow, EAccessVioliation, EPrivilege, EStackOverflow

SIGBUS

Bus Error (Hardware Fault)

ERangeError, EIntOverflow, EAccessVioliation, EPrivilege, EStackOverflow

SIGQUIT

User Interrupt (Control Backslash)

EQuit

Linux and Windows share similar concepts, although the names are different. For example, Windows NT/2000/XP has services, whereas Linux has daemons. Windows has DLLs and Linux calls them shared object libraries. The Event log in Windows NT/2000/XP is called the syslog in Linux.

File System

Another important part of Linux is the file system. In addition to applications, configuration and data files, files also represent hardware devices as well. Therefore, a robust file system is a necessity. Linux supports numerous types of file systems including DOS, NTFS, HPFS, ISO 9660, UDF, and more. The standard file system that is associated with Linux is ext2, which works well. More recently, journaling file systems like ReiserFS, JFS, and ext3, are being touted since they provide quicker recovery from failures.

Instead of segregating partitions of a hard drive into various drive letters, Linux has a single entry point – the root directory. The root directory is designated with a single forward slash (/), which is also the path separator. Recall that the Windows separator is the backslash (\). For portability, Kylix and Delphi 6 provide the PathDelim constant located in SysUtils.pas. Note that Linux does not have a “drive” concept like the Windows operating system. Kylix and Delphi 6 has a DriveDelim constant also located in SysUtils.pas.

Since there is only one entry point, partitions (or to be more accurate file systems) can be grafted or “mounted” onto the existing directory structure. In fact, network file systems can be mounted as well. Typically there is a specific directory where file systems are mounted — the /mnt directory. However, this is more by convention then by necessity. Once mounted, file systems are seen as a normal part of the directory structure.

The path separator is the colon (:) instead of the semi-colon (;). Kylix provides a constant PathSep constant for easier portability

Security

Linux has a much simpler security model than Windows. A user is required to log in before using Linux. There is no cancel button like Windows 9X has to bypass a log in and begin to use the system.

Anything that is represented by the file system has an owner, an associated group and everyone else (also known as “the world”). Later on, these concepts will be expanded upon.

Other special permissions exist that allow for an application to act like the owner or group of the application, instead of the user running the application. These too will be discussed in a later section.

Shell

Recall the command prompt in Windows. When brought up, it launches a special program called a command interpreter. In NT/2000/XP, this is cmd.exe and for Windows 9x/ME this is command.com.  The job of the command interpreter is to execute applications, built-in commands (like dir, cd, etc.) and scripting mechanisms like if and for.

Shell programs are the Linux counterpart, on steroids. In fact it is common practice in Linux to write shell scripts to do tasks that would require a Delphi application on Windows to do!

There are a variety of shell programs available for Linux. By default, the bash shell is installed for most Linux distributions. Other available shells include: Bourne shell, C shell, and the Korn shell. Here is a little bit of trivia, the bash shell is known as the “Bourne again shell”. The wide variety of shells naturally follows the Linux way of thinking by allowing an easy to replace an individual component with something else that the user likes.

GUI

Linux does not provide a native GUI layer built into the kernel. An additional layer, like the X Window System (X) is needed to provide the GUI functionality. The most commonly distributed version of X is XFree86™.

X is inherently distributed. It has client and server pieces. The server provides the graphical services like managing the resources (e.g. windows, color maps, fonts, etc) drawing and input devices. Clients request these services from a server. For example, they need to draw windows, want to know when a mouse event or a keyboard event fires.

X Window System Architecture

One important concept to understand is that the server knows about windows, but not what they represent. So clicking on a window can mean different things depending on what is being clicked. From the server perspective, it does not care if it is a button or an edit box. It is up to the client to “translate” the click event to the appropriate action. A button would fire an OnClick event whereas the edit box would gain focus.

By separating the client and server into two distinct parts, allows for clients and servers to be located on different machines. They communicate over a network using the X protocol, sending and receiving requests, replies and events. Events begin at the server, while requests and replies are started at the client. Clients also run a special program called Window Managers that handles moving, resizing and other “look and feel” functionality.

There are numerous Window Managers available. Each one provides a different look and feel. Some of the more popular Window managers are FVWM, TWM, AfterStep, Enlightenment, WindowMaker, and Sawfish. Depending on the configuration of the system, replacing a window manager requires some minor modifications. Details can be found at http://www.PLiG.org/xwinman/basics.html.

In the quest to provide a “user-friendly” environment, an additional layer provides a graphical interface to the operating system. This layer is referred to as the Desktop. The most common environments available are KDE and GNOME although others like CDE and KFce are also available. Regardless of the desktop used, Kylix applications will work with all desktops.

Best of all, Kylix developers can tap into the power of the X Window System by using the VisualCLX components, which are remarkably similar to their VCL counterparts.

Key Concepts

In this section, numerous concepts will be presented to help the Windows developer with some of the differences between Linux and Windows.

The Linux Environment

Before writing any applications, it is helpful to understand how Linux searches for applications and shell scripts. Similar to Windows, Linux has a PATH environment variable that specifies where to look for requested applications. Linux only searches those directories listed in the PATH environment variable and does not search the current directory like Windows does. So after writing the obligatory “Hello World” application, running it outside the Kylix IDE, you must specify the directory of where the executable resides.

Another environment variable exists, LD_LIBRARY_PATH to specify which directories Linux shared object libraries are located. However, this is not the only way that shared object libraries are searched. In addition, shared object libraries can also be specified in the /etc/ld.so.conf file, or placed in the /usr/lib directory. If the /etc/ld.so.conf file is modified, the ldconfig program must be used inform the system of the changes. Applications that have special permissions set do not use the LD_LIBRARY_PATH environment variable, as this is a security risk.

A third environment variable, DISPLAY, specifies the location of X client. The X server will send the output to the client where the windows will be drawn. Looking at Figure 1 shown above, the DISPLAY environment variable would be set to DISPLAY=172.16.1.100:0 on the client machine.

There is a number of other environment variables that Linux uses. To see a list, open up a terminal window and type set.

Navigating

Moving around the Linux file system is similar to Windows, especially if you remember the DOS commands. Of course you can use a graphical utility to navigate, but real programmers use the command line! <g> This section will deal with the command line interface, so bring up a terminal window if necessary.

Linux, like Windows, contains a vast amount of directories. These are used to organize the system into a useable way. They can contain both files and other directories.

With the command prompt staring you in the face, you’re probably wondering what directory you’re in. The command pwd is used to print the working directory. Changing directories is accomplished by using the cd [directory name] (change directory) command. If a directory is not specified, an implicit directory is used. This directory is the home directory of the user, specified by the HOME environment variable. Creating a directory is accomplished using the mkdir directory name (make directory) command, while deleting a directory is done by using the rmdir directory (remove directory) command

Directory or File?

Up to this point, the assumption has been that the directory name used as the parameter is already known. Suppose that the directory name is unknown. How are directories and files distinguished? How can they be viewed? Using the ls (list directory) command shows directories and files. Various options are available for changing the way the output looks. Adding an -l (minus “el”, short for long format) option shows much more information. Running the command ls -l /boot reveals something similar to this:

  total 2919
  Col #1       #2 #3       #4           #5        #6      #7 // Line is not in the output!
  lrwxrwxrwx    1 root     root           21 Jun  4 17:12 System.map -> System.map-2.2.14-5.0
  -rw-r--r--    1 root     root       202709 Mar  7 20:58 System.map-2.2.14-5.0
  -rw-r--r--    1 root     root          512 May 25 18:51 boot.0300
  -rw-r--r--    1 root     root         4568 Feb  2 17:03 boot.b
  -rw-r--r--    1 root     root          612 Feb  2 17:03 chain.b
  -rw-r--r--    1 root     root       436038 May 25 18:51 initrd-2.2.14-5.0.img
  -rw-r--r--    1 root     root          237 May 25 22:57 kernel.h
  drwxr-xr-x    2 root     root        12288 May 25 18:19 lost+found
  -rw-------    1 root     root        17920 May 25 18:51 map
  lrwxrwxrwx    1 root     root           22 May 25 18:36 module-info -> module-info-2.2.14-5.0
  -rw-r--r--    1 root     root        11773 Mar  7 20:58 module-info-2.2.14-5.0
  -rw-r--r--    1 root     root          620 Feb  2 17:03 os2_d.b
  -rwxr-xr-x    1 root     root      1638964 Mar  7 20:58 vmlinux-2.2.14-5.0
  lrwxrwxrwx    1 root     root           18 May 25 18:36 vmlinuz -> vmlinuz-2.2.14-5.0
  -rw-r--r--    1 root     root       640052 Mar  7 20:58 vmlinuz-2.2.14-5.0

 

In column one, the first character reveals if the entry is a directory a file or a link. A directory is indicated by a “d”, a file with a dash “-” and a link with an “l”. Links are similar to shortcuts in Windows. The next nine characters are divided into three groups of permission information. Column three specifies the owner, column four specifies the group, column five is the size of the entry, six is the date and time and column seven is the name of the entry. Notice that the only directory above is lost+found.

Special Directories

Like Windows, Linux has shortcuts for specifying the current and parent directory, if there is one. The “.” (one period) refers to the current directory, while two periods “..” refers to the parent directory. It is very common to chain together parent directories to move about the file system. For example, the command “cd ../../..” references the directory three levels higher then the current directory that is the current directory’s great-grandparent.

Permissions

As mentioned previously, Linux provides a simple, yet powerful security system. Using permissions effectively helps to keep the system secure. But before permissions are discussed, the concepts of an owner, group and world must be explained.

An owner is associated with a file or directory. Typically the owner of a file or directory is the user who created it. An owner has free reign over the file and is able to grant or deny permissions as well as remove it from the system. The chown command is used to take ownership of a file or directory, providing the user has the rights to do so. Column three in the listing above, is the owner.

A group is a collection of users that need similar rights to a resource. Suppose a group of programmers are working on a Kylix project and they need to access the same resources. An easy way to accomplish this is to create a group using the groupadd command, then adding each user to the new group, using the useradd command (which also is able to add users to the system). Once the group contains the appropriate users, the resource can be granted the appropriate permissions to the new group. The chgrp command is used to change the group of a file or directory. In the listing above, the group is shown in column four.

If a user is not the owner, or a member of a specified group, they fall into the last category, the world. Normally, permissions for the world are locked down tight to prevent modification to important resources.

Going back to column one, the permissions for each of the three categories, owner, group and world are specified. Permissions can be designated as read, write and execute or any combination thereof. A string of “rwx” designates these permissions for each category. A dash indicates that the specified permission has not been granted. For example, a string with “r-x” reveals that write permissions have not been granted (or that read and execute permissions have been granted). Look again at the listing above, the first column becomes clearer. Notice the entry for vmlinux-2.2.14-5.0. The owner has full permissions (“rwx”) while the group and world only have read and execute permissions (“r-x”).

Use the chmod command to change permissions.

Special Permissions

Applications, by default, run as the user who executes the program. For most applications, this is fine. However, there are times when it would be nice to run an application as a different user or group that has additional rights. Programs that allow for different user rights are called setuid or setguid. They can be set to setuid applications by using the chmod command like this: chmod u=s <name of program>. Similarly, setguid applications are set by using the command: chmod g=s <name of program> Consider the UserIds program shown below.

program UserIds;
{$APPTYPE CONSOLE}
uses
  SysUtils,Libc;

var
  uid : __uid_t;

  function UidToName( uid : __uid_t ) : string;
  var
    pwrec : PPasswordRecord;

  begin
    pwrec := getpwuid( uid );
    if pwrec = nil then
      raise Exception.CreateFmt('User id %d does not exist!',[uid]);
    Result := pwrec.pw_name;
  end;

begin
  uid := getuid;
  writeln('getuid returns ',uid,' which is ', UidToName(uid));
  uid := geteuid;
  writeln('geteuid returns ',uid,' which is ', UidToName(uid));
end.

When the application is run normally, as the rross user, without seting the setuid bit, the output looks like this:

$ ./UserIds
getuid returns 500 which is rross
geteuid returns 500 which is rross

Notice nothing is different between the user id and the effective user id, since nothing special was done with this program. Now if the setuid bit is set, lets see what happens (again running as the rross user):

$ chmod u=s UserIds
$ ls -l UserIds
---Srwxr-x    1 rross    rross       51092 Oct 14 19:52 UserIds
$ ./UserIds
bash: ./UserIds: Permission denied

Interesting. Even though the rross user owns the application, he is unable to execute the application. The reason is because rross is not root and therefore does not have the proper permissions to run a setuid program. However, if we run this application as root we get the following:

$ su
Password: **************
# ./UserIds
getuid returns 0 which is root
geteuid returns 0 which is root

Remember that the rross user still owns the program. Still this is not the results we are after. Now, after changing the owner of the application to root, and returning to the normal user, the following happens:

# chown root UserIds
# exit
$ ./UserIds
getuid returns 500 which is rross
geteuid returns 0 which is root

Finally, we get the results we are after. The owner of the program is root, and with the setuid bit set, the effective user is the root user. Be very careful when writing setuid applications as they are frequently targets of hackers. However, when root permissions are required for non-root users, this method is very effective.

Executable files

Executable files in Linux are those that have the execute permission bit set. Windows, on the other hand, uses file extensions to determine if a file is an executable. Files that can be executed include binary applications, like those generated by Kylix, and special text files that are called shell programs. Shell programs are even more powerful than batch files on Windows systems. In fact, entire books have been written on the subject of shell programs. Typically, shell files have an extension of .sh, but that is more a convention then a standard.

Remember the PATH environment variable? Executable files must be found in one of the directories in the PATH environment variable or the full path of the executable must be used. Linux does not search the current directory for an executable, unlike Windows. In order to run an executable in the current directory, use the shortcut ./ProgramName. Similarly, executing a program in the parent directory is accomplished by using ../ProgramName.

One item to note is that a file can be an executable for the owner but no one else!

Hidden Files and Directories

Hiding directories and files is done frequently in Linux. Typically, these files and directories do not need to be accessed often, so they are hidden to keep them out of site. A hidden directory or file is accomplished by using a period as the first character of the name. In order to see every file in a directory, add an -a option to the ls command.

Go back to your home directory by using the cd command without arguments. Compare the results of the command ls -l with the command ls -la. Note the number of hidden files and directories. Notice the .borland directory. This directory contains the configuration files for Kylix and dbExpress.

Wildcard expansion

Another major difference between Linux and Windows is how wildcards are expanded. Identical to Windows, the wildcard characters are the asterisk (*) and the question mark (?). In Linux, the shells (e.g. bash, csh, etc.) interpret the wildcard characters before an application is executed, unless they are “escaped”. Take the following program for example:

program cmdlineparms;
{$APPTYPE CONSOLE}
uses
  SysUtils;

var
  i : integer;

begin
  writeln('Testing parameters...');
  writeln('The name of this program is ',ParamStr(0));
  for i:=1 to ParamCount do
  begin
    writeln('   ',i,') ',ParamStr(i));
  end;

  writeln('You passed me ',ParamCount,' command line parameters!');
end.

Using *.* as the parameter for Windows, the output looks like this:



Testing parameters…
The name of this program is C:\Delphi Projects\console\cmdlineparms.exe
1) *.*
You passed me 1 command line parameters!

Running the same program in Linux, but using * as the argument, the output looks like this:



Testing parameters…
The name of this program is./cmdlineparms
1) cmdlineparms
2) cmdlineparms.cfg
3) cmdlineparms.dof
4) cmdlineparms.dpr
5) cmdlineparms.exe
6) cmdlineparms.~dpr
7) cmdtest.dpr
You passed me 7 command line parameters!

Notice is how Windows and Linux treat the program name. Under Windows, the entire complete path of the executable is shown. However, Linux returns only what the user has typed.

Next, see how the shell has expanded the asterisk before the program was even executed. Look at the number of parameters and how dramatically it has increased.

In order to prevent the shell program from expanding wildcard characters, the characters need to be escaped or quoted. For example, executing the following command ./cmdlineparms ‘*’ now looks similar to the output from the Windows version.



Testing parameters…
The name of this program is ./cmdlineparms
1) *
You passed me 1 command line parameters!

Virtual Consoles

Recall that Linux is a true multi-user operating system. By definition, this allows for multiple people to use the same system simultaneously. While Windows does have its version of a multi-user operating system (e.g. Citrix or Terminal Server), the versions that most people have are not enabled for multiple simultaneous users.

Linux provides virtual consoles that allow for logging into the system more than once. A virtual console is invoked by pressing CTRL-ALT-F1 through CTRL-ALT-F10. Normally, the X Window System is found on F7. As consoles are switched, a login appears, allowing to start another session.

Standard Input, Standard Output and Standard Error explained

For most applications, standard input (stdin) is a file handle that refers to the input from the keyboard. Screen output is referred to as the Standard Output (stdout) file handle. Standard error (stderr) also refers to the screen, however, it is treated as a different file handle. To better understand these concepts, look at the following Kylix application (which also works on Windows).

program teststdout;
{$APPTYPE CONSOLE}
uses  SysUtils;

var  buf : string;

begin
  writeln('Enter something on the keyboard..');
  while not EOF do
  begin
     readln(buf);
     writeln('You typed in (stdout) |',buf,'|!');
  end;
end.

Running the teststdout application, the output looks similar to this:

$ ./teststdout
Enter something on the keyboard..
This is a test
You typed in (stout) |This is a test|!
<ctrl-d>

Note that in Linux, <ctrl-d> is the equivalent of <ctrl-z> in Windows, the end of file marker.

In the example above, the readln function is used to read standard input and writeln is use to access standard out. Kylix also has two global variables, both of type TextFile, called Input and Output that refer to standard input and standard output. These variables can be used as the first parameter of readln and writeln respectively.

Since most applications define standard input to be the keyboard and output to the console, what do other applications do differently? The next section answers this question. Standard error will be explained in a subsequent section.

Redirection

Redirect means to divert a file stream from one place to another. It is a fancy way of saying “get the input from somewhere else – not the keyboard” or “send this output to someplace else”. Using the example from the previous section, let’s see what happens when we redirect standard input:

Run the application like this:

$ ./teststdout > out.txt
This is a test
<ctrl-d>

Notice that the output disappeared. So what happened to it? The greater than symbol (>) informs the shell that all standard output is redirected to the file named out.txt. Looking at out.txt reveals the following:

$ cat out.txt
Enter something on the keyboard..
You typed in (stout) |This is a test|!

Look back to the previous section. The file out.txt contains the same text that was originally on the screen. (The command cat displays the contents of a file to the screen).

In order to demonstrate redirecting standard input, a file needs to be created. Create a simple one-line text file with an editor. Enter “This is from a file” inside the text file. Save the file as input.txt. Running the program now using the command below reveals the following:

$ ./teststdout < input.txt
Enter something on the keyboard..
You typed in (stout) |This is from a file|!

This time, no keyboard entry was needed, since the contents of file input.txt was used in place of the keyboard. Using the less than symbol (<) informs the shell to take the contents of the file input.txt and send it the teststdout program, acting as if the file was typed in by hand. But that is not all. Linux allows the combination of redirecting both input and output as shown in the example below:

$ ./teststdout < input.txt > out.txt

Now it looks like nothing happened at all. However, if we look a the out.txt file, we see the following:

$ cat out.txt
Enter something on the keyboard..
You typed in (stout) |This is from a file}!

Note: Linux is not particular as to the order of redirection. The same results could have been accomplished by using the command ./testsdtout > out.txt < input.txt.

Standard Error Revisited

Recall that standard error is normally written to the screen. Usually applications print warnings and errors to the standard error device. Since standard error is similar to standard output, it too can be redirected. In fact, applications that write to standard output and standard error are capable of being redirected in a number of ways. For example, standard output can be redirected to a file, while standard error is sent to the screen (the default), to a different file, or to the same file. See the program listed below:

program teststderr;
{$APPTYPE CONSOLE}
uses
{$IFDEF MSWINDOWS}
  Windows,
  SysUtils;
{$ENDIF}
{$IFDEF LINUX}
  Libc,
  SysUtils;
{$ENDIF}

var
  stderr : Textfile;

begin
  // Initially assign stderr to stdout
{$IFDEF LINUX}
  Assign( stderr, '');
{$ENDIF}
{$IFDEF MSWINDOWS}
  AssignFile( stderr, 'con' );
{$ENDIF}
  // open "stderr"
  Rewrite(stderr);

{$IFDEF MSWINDOWS}
  // now make the handle point to the stderr using the WinAPI call..
  TTextRec( stderr ).Handle := GetStdHandle( STD_ERROR_HANDLE );
{$ENDIF}
{$IFDEF LINUX}
  TTextRec( stderr ).Handle := Libc.stderr;
{$ENDIF}

  writeln(Output,'This will be printed on standard out!');
  writeln(stderr,'This will be printed on standard error!');
end.

When executed in normally, without redirection, the output looks like this:

$ ./testsderr
This will be printed on standard out!
This will be printed on standard error!

Redirecting standard output gives the following output:

$ ./teststderr > out.txt
This will be printed on standard error!

Examining out.txt, we find the following:

$ cat out.txt
This will be printed on standard out!

In order to redirect standard error, the number two (2) is placed before the greater than symbol. The number two refers to the integer value of standard error. Standard input is zero, and standard output is one and standard error is two. Running the program again and redirecting only standard error shows the following:

$ ./teststderr 2> out.txt
This will be printed on standard out!

Looking at out., reveals the following:

$ cat out.txt
This will be printed on standard error!

Redirecting both standard output and standard error looks like this:

$ ./teststderr 2&> out.txt

The ampersand (&) informs the shell to combine both the output and standard error to the same place. In a different context, the ampersand can also be used to run a program in the background, freeing up the shell to perform additional commands (e.g. startkylix &)

When running under Windows, Standard Input, Standard Output and Standard Error are only available for console mode applications. Unfortunately, they are not available in GUI mode unless the compiler directive {$APPTYPE CONSOLE} is placed in the .dpr file of the project. A GUI application that specifies the compiler directive will launch a DOS box in addition to the application. All output goes to this DOS box. Applications under Linux always have access to standard input, standard output and standard error even without specifying the compiler directive. This is a useful technique for debugging without using the powerful Kylix debugger.

Please note that the code written above was written to be compatible with Delphi 5. Kylix and Delphi 6 introduced a global variable named ErrOutput that makes accessing standard error much easier. Simply pass ErrOutput to as the first parameter of writeln.

Peeking into Pipes

Recall that the Linux philosophy is to make a number of utilities that perform a specific task extremely well. Then, these specific utilities are combined to accomplish a specific task. One tool that is used to combine tasks is a concept called a pipe.

A pipe is a device that redirects the output from one application and sends it to the input of another application. Frequently, pipes are used to pause scrolling output. An in depth study of pipes is outside the scope of this paper. I wrote a paper for BorCon 2001 titled Creating Message Passing IPC Classes With Kylix. If you don’t have the CD, you can read it on my web site: http:/rick-ross.com/.

The pipe symbol is the vertical bar character (|). An example that demonstrates the usage is shown below:

List all of the files in the /bin directory and stop when you have a page full.
$ ls -las /bin | more

Directory structure

Navigating and understanding the Linux directory structure is a daunting task. To help remove a little of the mystery, this section briefly introduces the standard Linux directories. Each directory is introduced and a description of what is normally found within them. Note that this information is based on the Filesystem Hierarchy Standard 2.1 and not all distributions may follow this standard.

Root Directory

The top-level directory is named the root directory. Designated with a forward slash (/), this is where all other directories are found. Typically, no files are in this directory.

/bin Directory

Bin is an abbreviation for binary. It contains executable files that Linux requires in order to start.

/boot Directory

Binary image(s) of the Linux kernel are located in the /boot directory. These are the images that are loaded when Linux starts.

/dev Directory

Dev is an abbreviation for device. Most hardware in Linux is referred to as a device. As an example, the first hard drive is known as /dev/hda. Other devices, like serial or communication port one (e.g. COM1 under Windows) is known as /dev/ttySO. Another frequently used device is null (/dev/null). When redirected output is not needed, it is sent to the null device.

/etc Directory

Etc is an abbreviation for etcetera. Numerous system configuration files are typically stored in this directory. For example, /etc/passwd contains user information, /etc/fstab contains mounting information, /etc/hosts contains names that map to IP addresses, and /etc/profile contains system wide environment variables.

/home Directory

Location of each user’s own personal directory. Each user is given ownership and rights to manage their own files. Typically, each non-root user has a directory located here.

/lib Directory

Lib is an abbreviation for library. These are the shared object libraries (DLLs in Windows) that contain the routines needed by other executable programs and the C compiler. Libraries typically follow the standard of having the first three letters of the name start with “lib”.

/mnt Directory

Mnt is an abbreviation for mount. Partitions, removable drives, network drives are mounted in the file system under this directory. Usually, this directory contains entries like cdrom, floppy, zip, etc., that when mounted, provide access to the mounted resource. For example, when a cdrom is mounted, it can be accessed by looking at the /mnt/cdrom directory.

/opt Directory

Opt is an abbreviation for optional. Applications should be installed in this directory, using the form of /opt/<package name>. Global configuration files for the package should be installed in the /etc/opt/<package name> directory.

/proc Directory

Proc is an abbreviation for process. Process information, like the information displayed when using the ps command, is located in this directory. Each currently running process has its own directory using the process number as the name of the directory. Other system information, like cpuinfo is also located here. Most production systems keep this directory locked down in order to provide a system that is more secure.

/root Directory

Root, not to be confused with the top-level directory, is the home directory of the root user.

/sbin Directory

Sbin is an abbreviation for system binaries. System administration programs and other root only executables are located here. Examples include ifconfig (network configuration), portmap (used with RPC port mapper) and halt.

/tmp Directory

Tmp is an abbreviation for temporary. This is where applications that require a location for using temporary files. Files that are stored here are not guaranteed to persist between system reboots.

/usr Directory

The /usr directory is another major section of the directory structure. In fact, it contains its own hierarchy as listed below, however, only major directories have been shown.

/usr/X11R6 – contains the X-Windows system, version 11 release 6

/usr/bin – Most user commands are located here. Examples include: gcc c compiler), diff (differences between files), and emacs (editor)

/usr/include – Location of C header files

/usr/lib – Library files that are used internally and not executed by users.

/usr/sbin – System administration files that are not essential. Examples include samba (Windows network connectivity) and traceroute (network utility)

/usr/src – Kernel sources are located in this directory.

/var Directory

Var is an abbreviation for variable. Printer spool directories, logging files, and other administrative data is located here. The /var/tmp directory is provides another location for temporary storage, although files are not deleted between system reboots.

Additional information on the Filesystem Hierarchy Standard can be found at http://www.pathname.com/fhs/

GUI

One of the biggest differences in when using CLX is that the message loop is hidden inside the Qt layer. While direct access is available, it is not recommended for writing cross-platform applications. For the majority of applications, there is a better way of implementing the intended functionality. So for Kylix applications, this means that the WndProc method no longer exists. Instead, override the EventFilter method. In the overridden EventFilter method, events can be examined and depending on the application, either ignored, overridden, or supplemented.

Similarly, message methods that handled CM_*** and CN_*** constants need to be changed to use the appropriate dynamic methods. For example, suppose a Delphi application contained a form that had a method declared like this:

TForm1 = class(TForm)
private
  {...}
  procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
  {...}
end;

In the CMFontChanged method, there is code that acts appropriately when the font changes. Unfortunately, this will not compile on Kylix. Instead, override the FontChanged method. This would look like this:

TForm1 = class(TForm)
private
  {...}
  procedure FontChanged; override; 
  {...}
end;

Likewise, the FontChanged method would have the appropriate code that responds to the event.

For messages that handled WM_*** constants, override the EventFilter method.

Finally, be aware that the event ordering and timing of CLX components are not always the same as those in the VCL

Miscellaneous tips, tricks, an caveats

One thing that is useful when using the command line is the automatic expansion of filenames and directories. This automatic expansion occurs when a couple of letters are typed then the TAB key is pressed. The bash shell will automatically expand the directory or filename as far as possible, to the first non-unique character.

For example, two files are located in a directory. One is named foo1bar and the other foo2bar. Typing f and pressing the tab key will expand to foo. After pressing 1 or 2, the rest of the filename will be expanded after pressing the tab key again.

Linux command line options use either a dash ‘-‘ or a double dash ‘–‘. Windows uses either a dash ‘-‘ or a forward slash ‘/’.

Copying a selection in the X Window system is different than Windows. When a selection is highlighted in X, it is automatically copied into the clipboard. No additional key strokes or menu options are needed.  

The end-of-line character for Linux is a carriage return (ASCII 13). Windows uses a line feed (ASCII 10) and a carriage return. The global variable DefaultTextLineBreakStyle can be used to change which characters are used when reading a line.

Kylix does not come with a Resource Compiler. GNU windres (http://www.gnu.org/directory/binutils.html) and wrc, which is part of WINE (http://www.winehq.com/source/tools/wrc/) are two options available for you.

General porting steps

Several factors determine how difficult it is to port a Delphi VCL Windows application to Linux. The main four factors are: lines of code, platform specific VCL controls used, third party controls, and platform specific APIs. Regardless of these factors, the basic steps in porting an application to Linux are discussed in this section.

Tips for creating new cross-platform applications

If you know that an application will be eventually ported to Linux, save yourself some time by doing the following:

  1. Create a New CLX application (available in Delphi 6 and higher).
  2. Isolate platform specific code into one or more unit(s).
  3. Create an abstraction layer that is platform neutral

Spending a little extra time in the beginning stages of writing a cross-platform application will certainly pay off in the long run.

Transfer files

This first step may seem obvious, but it is the most important step. After all, if the files do not exist on a Linux system, how can you create an executable?

Several ways exist for transferring files to a Linux system. For smaller applications, transferring the files via a floppy disk is the easiest way. Look in the man pages for “mcopy”. Alternatively, if the Linux computer is connected to the same network as the Windows computer, use ftp or samba. Both require some setup and configuration to use properly. Remember to avoid copying a Windows executable to Linux, as it is not needed, and probably won’t work anyway. The whole point of this is to create a native Linux executable, and not be dependent upon WINE (see winehq.com for more information).

Copy .dfm to .xfm

Once the files are transferred, the .dfm form file must be copied to .xfm.

Fix {$R} directives

Start Kylix and open up each unit that contains a resource directive {$R}. Surround the {$R *.dfm} directive conditional compilation directives like this.

{$IFDEF WIN32}
  {$R *.dfm}
{$ENDIF}

Then add another Linux specific section like this:

{$IFDEF LINUX}
  {$R *.xfm}
{$ENDIF}

Now save the unit(s).

Resolving property issues

Open the project with Kylix. For all but the most trivial applications, errors will that occur when Kylix reads the form. These errors indicate that a property does not exist. Choose the “Ignore” button and continue loading the application. Eventually, Kylix will load the application. As these errors occur, take notes, since these errors may require additional coding changes later.

Build the project

Now that the application is loaded, build the project. Many errors will occur. Now the fun begins!

Resolve errors

Resolving the build errors is where the differences between VCL and CLX applications become apparent. Typical errors that need to be resolved include unit name mismatches, missing units, and incompatible parameter lists.

Unit name mismatches are usually due to case-sensitive nature of the Linux file system. Look for subtle differences between filenames. For example, suppose that the filename saved to disk is MyCoolUnit.pas, but in a uses clause is referenced as Mycoolunit. The error message would look similar to “[Fatal Error] (line number): File not found: ‘Mycoolunit.dcu'”.

Most VCL units will show up as missing units, since CLX prefixes all of these units with the letter “Q”. Another common item is units specific to the Windows platform. A good example is the Windows.pas unit, which has not been, nor will be ported to Linux. Don’t panic if a unit is missing — functionality may be found in another unit, or it may not be needed at all.

For single-source applications, use conditional compilation directives to separate platform specific units. The example below shows what a typical GUI form application might look like:

uses
{$IFDEF WIN32}
  Windows, Messages, Graphics, Menus, Controls, Forms, ExtCtrls, ComCtrls, Dialogs,
{$ENDIF}

{$IFDEF LINUX}
  QGraphics, QMenus, QControls, QForms, QExtCtrls,  QComCtrls, QDialogs, QTypes,
{$ENDIF}

  SysUtils, Variants, Classes, Types;

Finally, incompatible parameter list errors occur when there are changes between the VCL and CLX event handlers. One example is the OnProgess event handler of TImage. In a VCL application the definition of the event handler looks like this:

procedure Image1Progress(Sender: TObject; Stage: TProgressStage;
                         PercentDone: Byte; RedrawNow: Boolean; const R: TRect;
                         const Msg: String);

However, for a CLX application, the event handler definition looks like this:

procedure Image1Progress(Sender: TObject; Stage: TProgressStage;
                         PercentDone: Byte; RedrawNow: Boolean; const R: TRect;
                         const Msg: WideString; var Continue: Boolean);

Here the strategy is to surround the VCL definition with a conditional compilation directive first. Then create the new event handler and surround it with a Linux specific compilation directive. Remember to surround the method definition and the implementation portions.

Test the Application

Once the build errors are resolved, the final step is to test the application thoroughly. Make sure to be complete as possible with your testing. Ideally, all code paths should be tested and verified. In addition, when writing a cross-platform application, transfer the files back to Windows, recompile and retest again.

Porting example: Taking an existing Delphi/VCL application to Linux

Recently, I have invested in a very nice digital camera. One evening, while browsing a huge collection of pictures, I was very tired of manually launching every file (and I wasn’t using XP at the time). So I decided to write a simple slideshow application in Delphi (on Windows). This application prompts for a path to a directory and a time period to wait before displaying the next picture. I then decided to port it to Linux. The result is a cross-platform slideshow application. Refer to the source code for the specific implementation details.

Additional Resources

Kylix 2 Development by Eric Whipple and Rick Ross, published by Wordware, ISBN 1556227744

Additional online resources can be found at http://www.rick-ross.com/

Source Coode

Source Code

Leave a Reply