Computing: Free Pascal Programming

Free Pascal console programming: Running external programs.


This tutorial is about running an external program or application from within a Free Pascal console program using the Exec procedure of the Dos unit. The sample code has been tested with Lazarus/Free Pascal 2.2.6 (FPC 3.2.2) on Windows 10. The tutorial should apply as such to other versions of Lazarus and Windows; no idea, how far it may be applied to Linux and macOS.

The Exec procedure of the old Dos unit is probably not the best choice to run external programs from a Windows Command Prompt program, but it has one big advantage: It's extremely simple to use! There is a big disadvantage, too: You cannot capture the external program's output in the "calling" Free Pascal program.

The Exec procedure has 2 arguments of data type "string":

  1. The external program name (with or without path). If the path or the program name contains spaces, put the whole between double quotes!
  2. The command line parameters for the external program. If the program doesn't have any command line parameters, pass an empty string as second argument. If there are several arguments, separate them with a space (just as you would do on the command line).

Examples:

Running the program ascii.exe, located in the same directory as the "calling" Free Pascal program:
    Exec('ascii.exe', '');
Running the program ascii.exe, located in its original directory (the double quotes are optional in this example, as the path doesn't include spaces):
    Exec('"C:\Users\allu\Programming\Lazarus\ascii\ascii.exe"', '');
Running the program tsort.exe, located in the same directory as the "calling" Free Pascal program, and passing it "help" as command line parameter:
    Exec('tsort.exe', 'help');
Running the program tsort.exe, located in the same directory as the "calling" Free Pascal program, and passing it several parameters:
    Exec('tsort.exe', 'data.txt * DESC COLS:1-10');

What happens if we try to run an external program, that the system can't find (for example, if we misspell the path or program name, forget the path, or omit the path because we thought that the program was in the executables path)? Or (on Windows, this is very rare, but possible) if the access to the external program is denied? The Dos unit includes an integer variable called DosError. This variable is used by the procedures in the DOS unit to report errors. It can have the following values (other values are possible, but are not documented):

DosErrorMeaning
  0Successful execution (no error)
  2File not found
  3Path not found
  5Access denied
  6Invalid handle
  8Not enough memory
10Invalid environment
11Invalid format
18No more files

I suppose that most of these values are part of the old Dos days, but some of them (at least 0, 2, 3, and 4) are still set if you run a program using the Exec procedure.

Example:

Trying to run the program tsort.exe, located in the same directory as the "calling" Free Pascal program, and misspelled as "tssort.exe":
    Exec('tssort.exe', 'data.txt * DESC COLS:1-10');
    if DosError <> 0 then
        Writeln('Error: DOS error code = ', DosError);
will result in the output:
    Error: DOS error code = 2

The Dos unit also contains a function called DosExitCode. The return value of this function is of data type "word", and contains (in the low byte) the exit-code of a program executed with the Exec procedure. If all is ok, the return value is 0. Otherwise, the value returned by DosExitCode primarily depends on the "called" program; we'll see further down in the text, how to return our own custom values from a Free Pascal program.

When running an external program using the Exec procedure, you should always test DosError and DosExitCode. This is the best way to get a maximum of feedback from the external program.

Example:

Here is a complete program sample, running the external program tsort.exe, located in the same directory as the "calling" Free Pascal program, and passing it the command line parameter "help".
    program pexec1;
    uses
        Dos;
    begin
        Exec('tsort.exe', 'help');
        Writeln;
        if DosError <> 0 then
            Writeln('Error: DOS error code = ', DosError)
        else if Lo(DosExitCode) <> 0 then
            Writeln('Error: DOS exit code = ', Lo(DosExitCode))
        else
            Writeln;
        Write('ENTER to terminate... '); Readln;
    end.

"tsort" actually is my Simple text file sort program. When run with the command line parameter "help", it displays the program's help text, that it reads from the file "help.txt". So, if "help.txt" is located together with "tsort.exe", the external program executes correctly, the help text is displayed and both DosError and DosExitCode are 0. But, what happens if "help.txt" does not exist? First, "tsort" assumes that the help text is accessible, thus if it is not found, "tsort" will abort with a runtime error. Second, as you can see on the screenshot below, DosExitCode returns an error 217. I suppose this is a standard value (set by the Free Pascal Reset procedure?).

Using the Lazarus/Free Pascal 'Exec' procedure: Error 217 returned by the 'DosExitCode' function

The Pascal procedure Halt terminates a running program. The interesting thing is that we can pass a value of data type word as argument. If the program has been started from within another program, this value can be retrieved in the "calling" program using the DosExitCode function. A simple way to implement custom exit codes in a Free Pascal program.

Example:

Consider the following sample program. It reads one parameter from the command line, checks if it is a positive number and if it is, calculates and displays its square root. If there is no parameter passed, or if the parameter passed is not numeric or negative, the program terminates with setting the Dos exit code to some custom values.
    program squareroot;
    var
        C: Integer;
        N: Real;
    begin
        if ParamCount <> 1 then
            Halt(200)
        else begin
            Val(ParamStr(1), N, C);
            if Code <> 0 then
                Halt(201)
            else if N < 0 then
                Halt(202)
            else
                Writeln('Sqrt(', N:0:3, ') = ', Sqrt(N):0:7);
        end;
    end.

And here is the code of a simple program that runs "squareroot.exe".
    program pexec2;
    uses
        Dos;
    var
        S: string;
    begin
        Write('Enter command line parameter for squareroot.exe? '); Readln(S);
        Exec('squareroot.exe', S);
        Writeln;
        if DosError <> 0 then
            Writeln('Error: DOS error code = ', DosError)
        else if Lo(DosExitCode) <> 0 then
            Writeln('Error: DOS exit code = ', Lo(DosExitCode));
    end.

The screenshot shows the output of the two programs for different parameter values passed to "squareroot.exe". In the case of a positive number, "squareroot.exe" displays the number's square root. In all other cases, "squareroot.exe" sets the Dos exit code and aborts. The exit code is retrieved by "pexec2.exe" and displayed.

Using the Lazarus/Free Pascal 'Exec' procedure: Setting custom Dos exit codes

The first argument of the Exec procedure must be some kind of "running code", such as for example a Windows executable (.exe), or a Windows batch file (.bat). This means, that it's not possible to "exec" the Windows shell commands, such as for example "copy", "rename", "erase" ("del"). There is, however, a simple work-around: include the shell command in a batch file and then run this batch file from within the Free Pascal program using the Exec procedure.

Example:

The following example uses the Windows shell "copy" command to copy the file "help.txt" to the file "help.bak". First, lets create a batch file (I called it "filecopy.bat"), including the following shell commands:
    @echo off
    copy %1 %2

Then, lets run the batch file from within the following sample program:
    program pexec3;
    uses
        Dos;
    begin
        Exec('filecopy.bat', 'help.txt help.bak');
        Writeln;
        if DosError <> 0 then
            Writeln('Error: DOS error code = ', DosError)
        else if Lo(DosExitCode) <> 0 then
            Writeln('Error: DOS exit code = ', Lo(DosExitCode));
    end.

The Exec procedure passes the parameters "help.txt" and "help.bak" as %1 resp. %2 to the batch file, that uses them in the shell's "copy" command.

The screenshot shows the output when running ""pexec3" in the following 3 cases: (a) the file "filecopy.bat" does not exist: standard "DosError 2" (file not found); (b) the file "help.txt" does not exist: DosExitCode 1 seems to be the typical Windows error code if a file to be opened can't be found (remember that in the case of a Pascal "Reset" procedure, DosExitCode seems to be set to 217, if the file does not exist); (c) all files present, the shell's "copy" command executes successfully.

Using the Lazarus/Free Pascal 'Exec' procedure: Using a batch file to run Windows shell commands

May I use Exec to launch Windows desktop applications, or works it only with command line programs, you may ask. The answer is yes. I thought to remember that on earlier Windows releases this did not work, and was surprised that I could launch Firefox just the same way that I launched my tsort.exe. Here is, for example, the Exec procedure to launch Firefox with the file help.txt (located in the same directory as the "calling" Free Pascal program) opened in the browser window. Note, that in this example the double quotes enclosing the path (containing spaces) are mandatory!
    Exec('"C:\Program Files\Mozilla Firefox\firefox.exe"', 'help.txt');

The screenshot shows Firefox with help.txt open, and Command Prompt, where I ran the Free Pascal program that started the web browser (note that this pexec3 has nothing to do with the one described above when talking about the batch files...).

Using the Lazarus/Free Pascal 'Exec' procedure: Starting the Firefox web browser

There is however a difference between starting a command line program and starting a desktop application: there is no feedback from a GUI application started using the Exec procedure. If, for example, I specify, as second argument, a document that Firefox can't find, the web browser displays a "Hmm. We're having trouble finding that site" message, but the Free Pascal program will terminate without displaying an error message; because with GUI applications, DosExitCode is not set (returns always 0).

As you can see on the screenshot above, the "calling" Free Pascal program continues, after Firefox has been started, i.e. it does not wait for Firefox to terminate (as it is the case when starting a command line program).

I'm not sure if all GUI applications can be started this way. But, there is a method that should really always work: starting the application using the Windows start command. As it is a command and not a program, we have to create first a batch file (I called it "start.bat") with the following content:
    @echo off
    start %1 %2 %3 %4 %5 %6 %7 %8 %9
Normally, all these parameters aren't needed, but better to specify to much of them than to few.

The start command has lots of options (type help start in Command Prompt for details); here is a simple form:
    start /d <path> <program> [<program-options>]

Example:

To start GIMP2 with the existing JPG image file "apes.jpg", located in the "Work" directory on drive X:, use the following code in your Free Pascal program:
    Exec('start.bat', '/d "C:\Program Files\GIMP 2\bin" gimp-2.10.exe "X:\Work\apes.jpg"');

The screenshot shows the result...

Using the Lazarus/Free Pascal 'Exec' procedure: Starting GIMP2

If you find this text helpful, please, support me and this website by signing my guestbook.