Multiplatform Programming Guide/fr

From Free Pascal wiki
Revision as of 09:10, 12 July 2007 by Wile64 (talk | contribs)
Jump to navigationJump to search

Deutsch (de) English (en) español (es) français (fr) 日本語 (ja) polski (pl) русский (ru) 中文(中国大陆)‎ (zh_CN)

Cette page est le début d'un cours d'instruction qui concerne l'écriture d'applications multiplatforme sur Lazarus. Il couvrira les précautions nécessaires pour s'assurer qu'un programme peut être facilement porté et le processus pour porter un programme déjà existant. Je vous invites a m'aider pour améliorer l'article.

Introduction to Multiplatform Programming

How many boxes do you need?

To answer this question, you should first determine who your potential users are and how your program will be used. This question depends on where you are deploying your application.

If you are developing generic desktop software, Windows is obviously the most important platform, but also including a Mac OS X and a Linux versions can sometimes be the difference that will make your software be choosen instead of a non-cross-platform app.

The exact value of the various desktop operating systems changes a lot occording to the country, to the type of software, and with the target audience, so generic rules cannot be set. One small example of this is that Mac OS X is quite popular on western europe, while on South America it's mostly restricted to Video and Sound work.

On many contract projects only one platform is relevant, and that's not a problem. Free Pascal and Lazarus are quite capable of writing software targeted at a specific platform. You can, for example, access the full Windows API to write a well integrated Windows software.

If you're developing software that will run on a Web server, the most used platforms by far is UNIX, on all flavors. In this case, perhaps only Linux, Solaris, *BSD and other Unixes make sense as your target platforms. You may also want to add support for Windows NT.

Once you've mastered cross-platform development, you can usually just focus on the problem the software is designed to solve and do most of your development on whatever platform your have available or feel most comfortable with. That is, once you've addressed any cross-platform issues in your design, you can largely ignore the other platforms, much as you would when developing for a single platform. However, at some point you'll need to test deploying and running your program on the other platforms and it will be helpful to have unrestricted access to machines running the all target operating systems. If you don't want multiple physical boxes, you can look into configuring a dual-boot Windows-Linux box or running Windows and Linux on your Mac under an emulator like Virtual PC or iEmulator.

Cross-platform Programming

Working with files and folders

When working with files and folders, this is important to use non-platform specific path delimiters and line ending sequences. Here is a list of declared constants in Lazarus to be used when working with files and folders.

  • PathSep, PathSeparator: path separator when adding many paths together (';', ...)
  • PathDelim, DirectorySeparator: directory separator for each platform ('/', '\', ...)
  • LineEnding: proper line ending character sequence (#13#10 - CRLF, #10 - LF, ...)

Another important thing to be noted is the case sensitiveness of the file system. On Windows filenames are not case sensitive, while they are on Unix platforms. This can be the cause of annoying bugs, so any portable application should use consistently filenames.

Configuration files

You can use the GetAppConfigDir function from SysUtils unit to get a suitable place to store configuration files on different system. The function has one parameter, called Global. If it is True then the directory returned is a global directory, i.e. valid for all users on the system. If the parameter Global is false, then the directory is specific for the user who is executing the program. On systems that do not support multi-user environments, these two directories may be the same.

There is also the GetAppConfigFile which will return an appropriate name for an application configuration file. You can use it like this:

ConfigFilePath := GetAppConfigFile(False) + '.conf';

Bellow are examples of the output of default path functions on different systems:

program project1;

{$mode objfpc}{$H+}

uses
  SysUtils;

begin
  WriteLn(GetAppConfigDir(True));
  WriteLn(GetAppConfigDir(False));
  WriteLn(GetAppConfigFile(True));
  WriteLn(GetAppConfigFile(False));
end.

The output on a GNU/Linux system:

/etc
/home/felipe/project1
/etc/project1.cfg
/home/felipe/.project1

You can notice that glocal configuration files are stored on the /etc directory and local configurations are stored on a hidden folder on the user's home directory. Directories whose name begin with a dot (.) are hidden on Linux. You can create a directory on the location returned by GetAppConfigDir and then store configuration files there.

The output on Windows XP:

C:\Programas\teste
C:\Documents and Settings\felipe\Configurações Locais\Dados de aplicativos\project1
C:\Programas\teste\project1.cfg
C:\Documents and Settings\felipe\Configurações Locais\Dados de aplicativos\project1\project1.cfg

Notice that the function uses the directory where the application is to store global configurations on Windows.

The output on Windows 98:

C:\Programas\PROJECT1
C:\Windows\Configurações Locais\Dados de aplicativos\PROJECT1
C:\Programas\teste\PROJECT1.cfg
C:\Windows\Configurações Locais\Dados de aplicativos\PROJECT1\PROJECT1.cfg

The output on Mac OS X:

/etc
/Users/philhess/.config/testfelipe1
/etc/testfelipe1.cfg
/Users/philhess/.config/testfelipe1.cfg

Note: The use of UPX interferes with the use of the GetAppConfigDir and GetAppConfigFile functions.

Data and resource files

A very common question is where to store data files an application might need, such as Images, Music, XML files, database files, help files, etc. Unfortunately there is no cross-platform function to get the best location to look for data files. The solution is to implement differently on each platform using IFDEFs.

On Windows you can simply assume that the files are at the same directory as the executable, or at a position relative to it.

On most unixes (like Linux, BSDs, solaris, etc), data files are located on a fixed location, that can be something like: /usr/share/app_name or /opt/app_name

Most programs will be executed without root privileges and thus a cross-platform software should store data files on a place where it will always have read access to, but not necessarely write access, and configuration files on a place where it has read and write access.

Mac OS X is an exception among UNIXes. There the best way to deploy applications is using an application bundle, which includes all files your software will need. Then your resource files should be located inside the bundle, so it can be moved and continue working normally. You need to use CoreFoundation API calls to find the location of the bundle.

Example

This section presents a particular solution where under Windows the data files are stored on the same directory as the executable (or any other directory based on it, like MyDirectory + 'data' + PathDelim + 'myfile.dat'), and on Unixes it will be on a directory read from a configuration file. If no configuration file exists or it contains no info, then a constant ('/usr/share/myapp/') is utilized as the default directory.

The configuration file path is located with the GetAppConfigFile function from the Free Pascal Runtime Library.

Below is a full unit which you can use at your applications.

unit appsettings;

interface

{$ifdef fpc}
  {$mode delphi}{$H+}
{$endif}

uses
{$IFDEF Win32}
  Windows,
{$ENDIF}
  Classes, SysUtils, Forms, IniFiles, constants;

type

 { TConfigurations }

 TConfigurations = class(TObject)
 private
   ConfigFilePath: string;
 public
   {other settings as fields here}
   MyDirectory: string;
   constructor Create;
   destructor Destroy; override;
   procedure ReadFromFile(Sender: TObject);
   procedure Save(Sender: TObject);
 end;


var
 vConfigurations: TConfigurations;

implementation

const
  DefaultDirectory = '/usr/share/myapp/';

  SectionGeneral = 'General';
  SectionUnix = 'UNIX';

  IdentMyDirectory = 'MyDirectory';

{ TConfigurations }

constructor TConfigurations.Create;
begin
{$ifdef win32}
 ConfigFilePath := ExtractFilePath(Application.EXEName) + 'myapp.ini';
{$endif}
{$ifdef Unix}
 ConfigFilePath := GetAppConfigFile(False) + '.conf';
{$endif}

 ReadFromFile(nil);
end;

destructor TConfigurations.Destroy;
begin
 Save(nil);

 inherited Destroy;
end;

procedure TConfigurations.Save(Sender: TObject);
var
 MyFile: TIniFile;
begin
 MyFile := TIniFile.Create(ConfigFilePath);
 try
   MyFile.WriteString(SectionUnix, IdentMyDirectory, MyDirectory);
 finally
   MyFile.Free;
 end;
end;

procedure TConfigurations.ReadFromFile(Sender: TObject);
var
 MyFile: TIniFile;
begin
 MyFile := TIniFile.Create(ConfigFilePath);
 try
  // Here you can read other information from the config file

{$ifdef Win32}
   MyDirectory := MyFile.ReadString(SectionUnix, IdentMyDirectory,
ExtractFilePath(Application.EXEName));
{$else}
   MyDirectory := MyFile.ReadString(SectionUnix, IdentMyDirectory,
DefaultDirectory);
{$endif}
 finally
   MyFile.Free;
 end;
end;

initialization

 vConfigurations := TConfigurations.Create;

finalization

 FreeAndNil(vTranslations);

end.

and here example code of how to use that unit:

  bmp := TBitmap.Create
  try
    bmp.LoadFromFile(vConfigurations.MyDirectory + 'MyBitmap.bmp');
  finally
    bmp.Free;
  end;

Windows specific issues

Windows API Functions

Many Windows programs use the Windows API extensively. On crossplatform applications those functions cannot appear, or must be enclosed by a condition compile (e.g. {$IFDEF Win32} ).

Fortunately many Windows API functions - the most often used - are implemented in a multiplatform way in the unit LCLIntf. This can be a solution for programs which rely heavily on the Windows API, although the best solution is to substitute these calls with true crossplatform components from the LCL. You can substitute calls to GDI painting functions with the TCanvas object, for example.

On Unix there is no "application directory"

Many programmers are used to call ExtractFilePath(ParamStr(0)) or Application.ExeName to get the location of the executable, and then search for the necessary files for the program execution (Images, XML files, database files, etc) based on the location of the executable. This is wrong on unixes. The string on ParamStr(0) may contain a directory other then the one of the executable, and it also varies between different shell programs (sh, bash, etc).

Even if Application.ExeName could in fact know the directory where the executable is, that file could be a symbolic link, so you would get the directory of the link instead.

To avoid this read the sections about configuration files and data files.

Making do without Windows COM Automation

With Windows, Automation is a powerful way not only of manipulating other programs remotely but also for allowing other programs to manipulate your program. With Delphi you can make your program both an Automation client and an Automation server, meaning it can both manipulate other programs and in turn be manipulated by other programs.

Unfortunately, Automation isn't available on OS X and Linux. However, you can simulate some of the functionality of Automation on OS X using AppleScript.

AppleScript is similar to Automation in some ways. For example, you can write scripts that manipulate other programs. Here's a very simple example of AppleScript that starts NeoOffice (the Mac version of OpenOffice.org):

 tell application "NeoOffice"
   launch
 end tell

An app that is designed to be manipulated by AppleScript provides a "dictionary" of classes and commands that can be used with the app, similar to the classes of a Windows Automation server. However, even apps like NeoOffice that don't provide a dictionary will still respond to the commands "launch", "activate" and "quit". AppleScript can be run from the OS X Script Editor or Finder or even converted to an app that you can drop on the dock just like any app. You can also run AppleScript from your program, as in this example:

 Shell('myscript.applescript');

This assumes the script is in the indicated file. You can also run scripts on the fly from your app using the OS X OsaScript command:

 Shell('osascript -e '#39'tell application "NeoOffice"'#39 +
       ' -e '#39'launch'#39' -e '#39'end tell'#39);
       {Note use of #39 to single-quote the parameters}

However, these examples are just the equivalent of the following Open command:

 Shell('open -a NeoOffice');

The real power of AppleScript is to manipulate programs remotely to create and open documents and automate other activities. How much you can do with a program depends on how extensive its AppleScript dictionary is (if it has one). For example, Microsoft's Office X programs are not very useable with AppleScript, whereas the newer Office 2004 programs have completely rewritten AppleScript dictionaries that compare in many ways with what's available via the Windows Office Automation servers.

While Linux shells support sophisticated command line scripting, the type of scripting is limited to what can be passed to a program on the command line. There is no single, unified way to access a program's internal classes and commands with Linux the way they are via Windows Automation and OS X AppleScript. However, individual desktop environments (GNOME/KDE) and application frameworks often provide such methods of Interprocess Communication. On GNOME see Bonobo Components. KDE has the KParts framework, DCOP. OpenOffice has a platform neutral API for controlling the office remotely (google OpenOffice SDK) - though you would probably have to write glue code in another language that has bindings (such as Python) to use it. In addition, some applications have "server modes" activated by special command-line options that allow them to be controlled from another process. It is also possible (Borland did it with Kylix document browser) to "embed" one top-level X application window into another using XReparentWindow (I think).

As with Windows, many OS X and Linux programs are made up of multiple library files (.dylib and .so extensions). Sometimes these libraries are designed so you can also use them in programs you write. While this can be a way of adding some of the functionality of an external program to your program, it's not really the same as running and manipulating the external program itself. Instead, your program is just linking to and using the external program's library similar to the way it would use any programming library.

See Also