Sign in Join
Wiki copytemporary

Multiplatform Programming Guide: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11This is a tutorial on writing cross-platform applications with Lazarus and Free Pascal. It will cover the necessary precautions to aid in creating a cross-platform ready program that is ready to [[Deploying Your Application|deploy]].
22
33Most [[LCL]] applications work in a cross-platform way without any extra effort. This principle is called [[Write once compile anywhere|„write once, compile anywhere“]].
44
55## Introduction to Multiplatform (Cross-platform) Programming
66
77### How many platforms do you need?
88
99To 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.
1010
1111If you are developing generic desktop software in 2014, Microsoft Windows may be the most important platform. Note that macOS and/or Linux are gaining in popularity, and may be a significant target for your application.
1212
1313The popularity of the various desktop operating systems differs by country, by the type of software used, and with the target audience; there's no general rule. For example, macOS is quite popular in North America and western Europe, while in South America macOS is mostly restricted to video and sound work.
1414
1515On many contract projects, only one platform is relevant. 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 program.
1616
1717If you're developing software that will run on a web server, a Unix platform in one of its various flavors is commonly used. In this case, perhaps only Linux, Solaris, *BSD and other Unixes make sense as your target platforms, although you may want to add support for Windows for completeness.
1818
1919Once 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. For that, it will be helpful to have unrestricted access to machines running the target operating systems. If you don't want multiple physical computers, investigate dual-booting or Virtual Machine (VM) solutions like:
2020
2121- [VMware Fusion for Mac](https://www.vmware.com/products/fusion.html) - Host system must be running Intel macOS (Commercial/Free for non-commercial use).
2222- [VMware Workstation Player](https://www.vmware.com/products/workstation-player.html) - Host system running Windows 64 bit or Linux 64 bit (Commercial/Free for personal use).
2323- [VMware Workstation Pro](https://www.vmware.com/products/workstation-pro.html) - Host system running Windows 64 bit or Linux 64 bit (Commercial/Free trial).
2424- [Parallels Desktop for Mac](https://www.parallels.com/) - Host system must be running Intel macOS (Commercial/Free trial). Note: There is a [Technical Preview](https://b2b.parallels.com/Apple-Silicon) that will run on Apple M1 ARM64 processors which will run the ARM versions of Linux and [Windows 10 Insider Preview](https://www.microsoft.com/en-us/software-download/windowsinsiderpreviewARM64).
2525- [VirtualBox](https://www.virtualbox.org/) - Host system can be running Intel macOS, Windows, Linux or Solaris (Open Source).
2626
2727Some, eg Parallels on the Mac, make it trivial to install a desktop Linux operating system with a single click.
2828
2929**See also:** [[Small Virtual Machines]] and [[Qemu and other emulators]].
3030
3131## Cross-platform Programming
3232
3333### Working with files and folders
3434
3535When working with files and folders, it is important to use non-platform specific path delimiters and [[End of Line|line ending]] sequences. Here is a list of declared [[Constant|constants]] in Lazarus to be used when working with files and folders.
3636
3737- **PathSep**, **PathSeparator**: path separator when adding many paths together (';', ...)
3838- **PathDelim**, **DirectorySeparator**: directory separator for each platform ('/', '\\', ...)
3939- **LineEnding**: proper line ending character sequence (#13#10 - CRLF, \#10 - [[Line feed|LF]], ...)
4040
4141Another important thing to be noted is the case sensitiveness of the file system. On Windows filenames are usually not case sensitive, while they usually are case sensitive on Unix platforms (eg Linux, FreeBSD) but not macOS despite its Unix heritage. But note that if an EXT2, EXT3, etc file system is mounted on Windows, it would be case sensitive. Likewise, a FAT file system mounted on Linux should not be case sensitive.
4242
4343Special attention should be paid to NTFS which is not case sensitive when used in Windows, but is case sensitive when mounted by POSIX OSes. This could cause **various problems, including loss of files** if files with the same filenames in different cases exist on an NTFS partition mounted in Windows. Using custom functions for checking and preventing creation of several files with the same names on NTFS should be considered by developers.
4444
4545macOS uses case insensitive filenames by default. This can be the cause of annoying bugs, so any portable application should use filenames consistently.
4646
4747The RTL file functions use the system encoding for file names. Under Windows this is one of the Windows code pages, while Linux, BSD and macOS usually use UTF-8. The unit **FileUtil** of the LCL provides file functions which takes UTF-8 strings like the rest of the LCL.
4848
4949``` pascal
5050// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, macOS
5151// but normally these OS use UTF-8 as system encoding so the widestringmanager
5252// is not needed.
5353function NeedRTLAnsi: boolean;// true if system encoding is not UTF-8
5454procedure SetNeedRTLAnsi(NewValue: boolean);
5555function UTF8ToSys(const s: string): string; // as UTF8ToAnsi but more independent of widestringmanager
5656function SysToUTF8(const s: string): string; // as AnsiToUTF8 but more independent of widestringmanager
5757function UTF8ToConsole(const s: string): string; // converts UTF8 string to console encoding (used by Write, WriteLn)
5858
5959// file operations
6060function FileExistsUTF8(const Filename: string): boolean;
6161function FileAgeUTF8(const FileName: string): Longint;
6262function DirectoryExistsUTF8(const Directory: string): Boolean;
6363function ExpandFileNameUTF8(const FileName: string): string;
6464function ExpandUNCFileNameUTF8(const FileName: string): string;
6565function ExtractShortPathNameUTF8(Const FileName : String) : String;
6666function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;
6767function FindNextUTF8(var Rslt: TSearchRec): Longint;
6868procedure FindCloseUTF8(var F: TSearchrec);
6969function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;
7070function FileGetAttrUTF8(const FileName: String): Longint;
7171function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;
7272function DeleteFileUTF8(const FileName: String): Boolean;
7373function RenameFileUTF8(const OldName, NewName: String): Boolean;
7474function FileSearchUTF8(const Name, DirList : String): String;
7575function FileIsReadOnlyUTF8(const FileName: String): Boolean;
7676function GetCurrentDirUTF8: String;
7777function SetCurrentDirUTF8(const NewDir: String): Boolean;
7878function CreateDirUTF8(const NewDir: String): Boolean;
7979function RemoveDirUTF8(const Dir: String): Boolean;
8080function ForceDirectoriesUTF8(const Dir: string): Boolean;
8181
8282// environment
8383function ParamStrUTF8(Param: Integer): string;
8484function GetEnvironmentStringUTF8(Index: Integer): string;
8585function GetEnvironmentVariableUTF8(const EnvVar: string): String;
8686function GetAppConfigDirUTF8(Global: Boolean): string;
8787
8888// other
8989function SysErrorMessageUTF8(ErrorCode: Integer): String;
9090```
9191
9292### Empty file names and double path delimiters
9393
9494There are differences in file/directory name handling in Windows versus Linux, Unix and Unix like systems.
9595
9696- Windows allows empty file names. That's why FileExistsUTF8('..\\') checks under Windows in the parent directory for a file without name.
9797- On Linux/Unix/Unix-like systems, an empty file is mapped to the directory and directories are treated as files. This means that FileExistsUTF8('../') under Unix checks for the existence of the parent directory, which normally results true.
9898
9999Double path delimiters in file names are also treated differently:
100100
101101- Windows: 'C:\\' is not the same as 'C:\\\\'
102102- Unix like OS: the path '/usr//' is the same as '/usr/'. If '/usr' is a directory then even all three are the same.
103103
104104This is important when concatenating file names. For example:
105105
106106``` pascal
107107FullFilename:=FilePath+PathDelim+ShortFilename; // can result in two PathDelims which gives different results under Windows and Linux
108108FullFilename:=AppendPathDelim(FilePath) + ShortFilename; // creates only one PathDelim
109109FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // creates only one PathDelim and do some more clean up
110110```
111111
112112The function TrimFilename replaces double path delimiters with single ones and shorten '..' paths. For example /usr//lib/../src is trimmed to /usr/src.
113113
114114If you want to know if a directory exists use **DirectoryExistsUTF8**.
115115
116116Another common task is to check if the path part of a file name exists. You can get the path with ExtractFilePath, but this will contain the path delimiter.
117117
118118- Under Unix like system you can simply use FileExistsUTF8 on the path. For example FileExistsUTF8('/home/user/') will return true if the directory /home/user exists.
119119- Under Windows you must use the DirectoryExistsUTF8 function, but before that you must delete the path delimiter, for example with the ChompPathDelim function.
120120
121121Under Unix like systems the root directory is '/' and using the ChompPathDelim function will create an empty string. The function DirPathExists works like the DirectoryExistsUTF8 function, but trims the given path.
122122
123123Note that Unix/Linux uses the '~' (tilde) symbol to stand for the home directory, typically '/home/jim/' for a user called jim. So '~/myapp/myfile' and '/home/jim/myapp/myfile' are identical on the command line and in scripts. However, the tilde is not automatically expanded by Lazarus. It is necessary to use ExpandFileNameUTF8('~/myapp/myfile') to get the full path.
124124
125125### Text encoding
126126
127127Text files are often encoded in the current system encoding. Under Windows this is usually one of the windows code pages, while Linux, BSD, and macOS usually use UTF-8. There is no 100% rule to find out which encoding a text file uses. The LCL unit **lconvencoding** has a function to guess the encoding:
128128
129129``` pascal
130130function GuessEncoding(const s: string): string;
131131function GetDefaultTextEncoding: string;
132132```
133133
134134And it contains functions to convert from one encoding to another:
135135
136136``` pascal
137137function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;
138138
139139function UTF8BOMToUTF8(const s: string): string; // UTF8 with BOM
140140function ISO_8859_1ToUTF8(const s: string): string; // central europe
141141function CP1250ToUTF8(const s: string): string; // central europe
142142function CP1251ToUTF8(const s: string): string; // cyrillic
143143function CP1252ToUTF8(const s: string): string; // latin 1
144144...
145145function UTF8ToUTF8BOM(const s: string): string; // UTF8 with BOM
146146function UTF8ToISO_8859_1(const s: string): string; // central europe
147147function UTF8ToCP1250(const s: string): string; // central europe
148148function UTF8ToCP1251(const s: string): string; // cyrillic
149149function UTF8ToCP1252(const s: string): string; // latin 1
150150...
151151```
152152
153153For example to load a text file and convert it to UTF-8 you can use:
154154
155155``` pascal
156156var
157157 sl: TStringList;
158158 OriginalText: String;
159159 TextAsUTF8: String;
160160begin
161161 sl:=TStringList.Create;
162162 try
163163 sl.LoadFromFile('sometext.txt'); // beware: this changes line endings to system line endings
164164 OriginalText:=sl.Text;
165165 TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);
166166 ...
167167 finally
168168 sl.Free;
169169 end;
170170end;
171171```
172172
173173And to save a text file in the system encoding you can use:
174174
175175``` pascal
176176sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);
177177sl.SaveToFile('sometext.txt');
178178```
179179
180180### Configuration files
181181
182182You can use the [[doc:rtl/sysutils/getappconfigdir.html|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.
183183
184184There is also the [[doc:rtl/sysutils/getappconfigfile.html|GetAppConfigFile]] which will return an appropriate name for an application configuration file. You can use it like this:
185185
186186``` pascal
187187ConfigFilePath := GetAppConfigFile(False);
188188```
189189
190190Below are examples of the output of default path functions on different systems:
191191
192192``` pascal
193193program project1;
194194
195195{$mode objfpc}{$H+}
196196
197197uses
198198 SysUtils;
199199
200200begin
201201 WriteLn(GetAppConfigDir(True));
202202 WriteLn(GetAppConfigDir(False));
203203 WriteLn(GetAppConfigFile(True));
204204 WriteLn(GetAppConfigFile(False));
205205end.
206206```
207207
208208You can notice that global configuration files are stored on the /etc directory and local configurations are stored in a hidden directory in the user's home directory. Directories whose name begin with a dot (.) are hidden on UNIX and UNIX-like operating systems. You can create a directory in the location returned by GetAppConfigDir and then store configuration files there.
209209
210210> **Note:** Normal users are not allowed to write to the /etc directory. Only users with administration rights can do this.
211211
212212Notice that before FPC 2.2.4 the function was using the directory where the application was to store global configurations on Windows.
213213
214214The output on Windows 98 with FPC 2.2.0:
215215
216216 C:\Program Files\PROJECT1
217217 C:\Windows\Local Settings\Application Data\PROJECT1
218218 C:\Program Files\PROJECT1\PROJECT1.cfg
219219 C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg
220220
221221The output on Windows XP with FPC 3.0.4:
222222
223223 C:\Documents and Settings\All Users\Application Data\project1\
224224 C:\Documents and Settings\user\Local Settings\Application Data\project1\
225225 C:\Documents and Settings\All Users\Application Data\project1\project1.cfg
226226 C:\Documents and Settings\user\Local Settings\Application Data\project1\project1.cfg
227227
228228The output on Windows 7 and Windows 10 and FPC 3.0.4:
229229
230230 C:\ProgramData\project1\
231231 C:\Users\user\AppData\Local\project1\
232232 C:\ProgramData\project1\project1.cfg
233233 C:\Users\user\AppData\Local\project1\project1.cfg
234234
235235The output on macOS 10.14.5 with FPC 3.0.4 (violates Apple Guidelines - see below for the correct macOS file locations):
236236
237237 /etc/project1/
238238 /Users/user/.config/project1/
239239 /etc/project1.cfg
240240 /Users/user/.config/project1.cfg
241241
242242The output on FreeBSD 12.1 with FPC 3.0.4:
243243
244244 /etc/project1/
245245 /home/user/.config/project1/
246246 /etc/project1.cfg
247247 /home/user/.config/project1.cfg
248248
249249The observant will have noticed a difference between the Windows and non-Windows operating systems output - the Windows output for the `WriteLn(GetAppConfigFile(True));` global configuration code includes a subdirectory but the other operating systems do not. To obtain the same results for non-Windows operating systems, you need to include an additional [[Boolean]] parameter: `WriteLn(GetAppConfigFile(True,True));`.
250250
251251> **Note:** The use of UPX interferes with the use of the GetAppConfigDir and GetAppConfigFile functions.
252252
253253#### macOS
254254
255255In most cases configuration files are preference files, which in macOS should be XML files ending with the ".plist" extension and be stored in /Library/Preferences or ~/Library/Preferences with filenames taken from the "Bundle identifier" field in the [[macOS property list files|Info.plist]] file of the [[Application Bundle|application bundle]]. Using .config files in the user directory is a violation of the Apple programming guidelines. See the [[Locating macOS app support, preferences folders]] article for code which handles this in an Apple-compliant manner.
256256
257257### Data and resource files
258258
259259A 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.
260260
261261#### Windows
262262
263263On Windows, application data that the program modifies should not be put in the application's directory (e.g. C:\Program Files\\) but in a specific location (see e.g. ["Classify Application Data"](http://support.microsoft.com/kb/310294) (*link is broken*). Windows Vista and newer actively enforce this (users only have write access to these directories when using elevation or disabling UAC) but uses a folder redirection mechanism to accommodate older, wrongly-programmed applications. Just reading, not writing, data from application directories would still work but is not recommended.
264264
265265In short: use such folder:
266266
267267``` pascal
268268 OpDirLocal:= GetEnvironmentVariableUTF8('appdata')+'\MyAppName';
269269```
270270
271271See [[Windows Programming Tips|Windows Programming Tips - Getting Special Folders]]
272272
273273#### Unix/Linux
274274
275275On most Unixes (like Linux, FreeBSD, OpenBSD, Solaris but **not macOS**), application data files are located in a fixed location, which can be something like: /usr/local/share/app_name or /opt/app_name.
276276
277277Application data that needs to be written to by the application often gets stored in places like /usr/local/var/app_name, /usr/local/share/app_name or /usr/local/app_name, with appropriate permissions set.
278278
279279Help files (aka man pages) should be stored in /usr/local/man/man\[appropriate manual section number\]/app_name\>, with appropriate permissions set. Note that some FreeBSD and Linux manual sections differ or do not exist.
280280
281281User-specific read/write config/data will normally be stored somewhere under the user's home directory (eg in ~/.config/\<programname\>). Refer to [[Multiplatform Programming Guide|Configuration Files]] above.
282282
283283#### macOS
284284
285285macOS is an exception among UNIX operating systems. An application is published in a bundle (a directory with ".app" extension) which is treated by the Finder file manager as a file (in a Terminal you can "cd path/myapp.app" or in Finder right click on the app and choose "Show Package Contents"). Your resource files should be located inside the bundle. If the bundle is "path/MyApp.app", then the:
286286
287287- executable file is "path/MyApp.app/Contents/MacOS/myapp"
288288- resources directory is "path/MyApp.app/Contents/Resources"
289289
290290Application supplied data and resource files should generally be stored in the Resources directory of the application bundle. For code which handles this in an Apple compliant manner, see the [[Locating the macOS application resources directory]] article.
291291
292292Application data files generated by the user should be stored in the user's '~/Library/Application Support' directory. For code which handles this in an Apple compliant manner, see the [[Locating macOS app support, preferences folders]] article.
293293
294294> **Warning:** **Never** use `paramStr(0)`, or any function which uses it, on any UNIX platform to *determine* the location of the executable, as this is a DOS-Windows-OS/2 convention and has several conceptual problems, which cannot be solved using emulation on other platforms. The only thing `paramStr(0)` is guaranteed to return on UNIX platforms is the name with which the program was started. The directory in which it is located and the name of the actual binary (in case it was started using a symbolic link) are not guaranteed to be available via `paramStr(0)`. For macOS, you can **reliably** locate the application bundle directory using the native code in [[Locating the macOS application resources directory|this article]].
295295
296296### 32/64 bit
297297
298298#### Detecting bitness at runtime
299299
300300While you can control whether you compile for 32 or 64 bit with compiler defines, sometimes you want to know what bitness the operating system runs. For example, if you are running a 32 bit Lazarus program on 64 bit Windows, you might want to run an external program in a 32 bit program files directory, or you might want to give different information to users: I need this in my LazUpdater Lazarus installer to offer the user a choice of 32 and 64 bit compilers. Code: [[Detect Windows x32-x64 example]].
301301
302302#### Detecting bitness of external library before loading it
303303
304304When you want to load functions from dynamic library into your program, it has to have same bitness as your application. On 64 bit Windows, your application might be 32-bit or 64-bit, and there can be 32-bit and 64-bit libraries on your system. So you might want to check whether dll's bitness is same as your application's bitness before loading the dll dynamically. Here is a function which tests dll's bitness ([contributed in forum by GetMem](http://forum.lazarus.freepascal.org/index.php/topic,36834.msg245859.html#msg245859)):
305305
306306``` pascal
307307
308308uses {..., } JwaWindows;
309309
310310function GetPEType(const APath: WideString): Byte;
311311const
312312 PE_UNKNOWN = 0; //if the file is not a valid dll, 0 is returned
313313 // PE_16BIT = 1; // not supported by this function
314314 PE_32BIT = 2;
315315 PE_64BIT = 3;
316316var
317317 hFile, hFileMap: THandle;
318318 PMapView: Pointer;
319319 PIDH: PImageDosHeader;
320320 PINTH: PImageNtHeaders;
321321 Base: Pointer;
322322begin
323323 Result := PE_UNKNOWN;
324324
325325 hFile := CreateFileW(PWideChar(APath), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
326326 if hFile = INVALID_HANDLE_VALUE then
327327 begin
328328 CloseHandle(hFile);
329329 Exit;
330330 end;
331331
332332 hFileMap := CreateFileMapping(hFile, nil, PAGE_READONLY, 0, 0, nil);
333333 if hFileMap = 0 then
334334 begin
335335 CloseHandle(hFile);
336336 CloseHandle(hFileMap);
337337 Exit;
338338 end;
339339
340340 PMapView := MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
341341 if PMapView = nil then
342342 begin
343343 CloseHandle(hFile);
344344 CloseHandle(hFileMap);
345345 Exit;
346346 end;
347347
348348 PIDH := PImageDosHeader(PMapView);
349349 if PIDH^.e_magic <> IMAGE_DOS_SIGNATURE then
350350 begin
351351 CloseHandle(hFile);
352352 CloseHandle(hFileMap);
353353 UnmapViewOfFile(PMapView);
354354 Exit;
355355 end;
356356
357357 Base := PIDH;
358358 PINTH := PIMAGENTHEADERS(Base + LongWord(PIDH^.e_lfanew));
359359 if PINTH^.Signature = IMAGE_NT_SIGNATURE then
360360 begin
361361 case PINTH^.OptionalHeader.Magic of
362362 $10b: Result := PE_32BIT;
363363 $20b: Result := PE_64BIT
364364 end;
365365 end;
366366
367367 CloseHandle(hFile);
368368 CloseHandle(hFileMap);
369369 UnmapViewOfFile(PMapView);
370370end;
371371
372372//Now, if you compile your application for 32-bit and 64-bit windows, you can check if dll's bitness is same as your application's:
373373function IsCorrectBitness(const APath: WideString): Boolean;
374374begin
375375 {$ifdef CPU32}
376376 Result := GetPEType(APath) = 2; //the application is compiled as 32-bit, we ask if GetPeType returns 2
377377 {$endif}
378378 {$ifdef CPU64}
379379 Result := GetPEType(APath) = 3; //the application is compiled as 64-bit, we ask if GetPeType returns 3
380380 {$endif}
381381end;
382382```
383383
384384#### Pointer / Integer Typecasts
385385
386386Pointers under 64bit need 8 bytes instead of 4 on 32bit. The 'Integer' type remains 32bit on all platforms for compatibility. This means you can not typecast pointers into integers and back.
387387
388388FPC defines two types for this: PtrInt and PtrUInt. PtrInt is a 32bit signed integer on 32 bit platforms and a 64bit signed integer on 64bit platforms. The same for PtrUInt, but *unsigned* integer instead.
389389
390390Use for code that should work with Delphi and FPC:
391391
392392``` pascal
393393 {$IFNDEF FPC}
394394 type
395395 PtrInt = integer;
396396 PtrUInt = cardinal;
397397 {$ENDIF}
398398```
399399
400400Replace all **integer(SomePointerOrObject)** with **PtrInt(SomePointerOrObject)**.
401401
402402### Endianess
403403
404404Intel platforms are little endian, that means the least significant byte comes first. For example the two bytes of a word \$1234 is stored as \$34 \$12 on little endian systems. On big endian systems like the powerpc the two bytes of a word \$1234 are stored as \$12 \$34. The difference is important when reading files created on other systems.
405405
406406Use for code that should work on both:
407407
408408``` pascal
409409{$IFDEF ENDIAN_BIG}
410410...
411411{$ELSE}
412412...
413413{$ENDIF}
414414```
415415
416416The opposite is ENDIAN_LITTLE.
417417
418418The system unit provides plenty of endian converting functions, like SwapEndian, BEtoN (big endian to current endian), LEtoN (little endian to current endian), NtoBE (current endian to big endian) and NtoLE (current endian to little endian).
419419
420420#### Libc and other special units
421421
422422Avoid legacy units like "oldlinux" and "libc" that are not supported outside of linux/i386.
423423
424424#### Assembler
425425
426426Avoid [[Assembly language|assembler]].
427427
428428#### Compiler defines
429429
430430``` pascal
431431{$ifdef CPU32}
432432...write here code for 32 bit processors
433433{$ENDIF}
434434{$ifdef CPU64}
435435...write here code for 64 bit processors
436436{$ENDIF}
437437```
438438
439439### Projects, packages and search paths
440440
441441Lazarus projects and packages are designed for multi platforms. Normally you can simply copy the project and the required packages to another machine and compile them there. You don't need to create one project per platform.
442442
443443Some advice to achieve this
444444
445445The compiler creates for every unit a ppu with the same name. This ppu can be used by other projects and packages. The unit source files (e.g. unit1.pas) should not be shared. Simply give the compiler a unit output directory where to create the ppu files. The IDE does that by default, so nothing to do for you here.
446446
447447Every unit file must be part of **one** project or package. If a unit file is only used by a single project, add it to this project. Otherwise add it to a package. If you have not yet created a package for your shared units, see here: [[Lazarus Packages|Creating a package for your common units]]
448448
449449Every project and every package should have **disjunct directories** - they should not share directories. Otherwise you must be an expert in the art of compiler search paths. If you are not an expert or if others who may use your project/package are not experts: do not share directories between projects/packages.
450450
451451#### Platform specific units
452452
453453For example the unit wintricks.pas should only be used under Windows. In the uses section use:
454454
455455``` pascal
456456uses
457457 Classes, SysUtils
458458 {$IFDEF Windows}
459459 ,WinTricks
460460 {$ENDIF}
461461 ;
462462```
463463
464464If the unit is part of a package, you must also select the unit in the package editor of the package and disable the *Use unit* checkbox.
465465
466466See also [[Lazarus Packages|Platform specific units]]
467467
468468#### Platform specific search paths
469469
470470When you target several platforms and access the operating system directly, then you will quickly get tired of endless IFDEF constructions. One solution that is used often in the FPC and Lazarus sources is to use include files. Create one sub directory per target. For example win32, linux, bsd, darwin. Put into each directory an include file with the same name. Then use a macro in the include path. The unit can use a normal include directive.
471471
472472An example for one include file for each LCL widget set:
473473
474474Create one file for each widget set you want to support:
475475
476-`win32/example.inc`\
477-`gtk/example.inc`\
478-`gtk2/example.inc`\
479-`carbon/example.inc`
476+```text
477+win32/example.inc
478+gtk/example.inc
479+gtk2/example.inc
480+carbon/example.inc
481+```
480482
481483You do not need to add the files to the package or project. Add the include search path *\$(LCLWidgetType)* to the compiler options of your package or project.
482484
483485In your unit use the directive: {\$I example.inc}
484486
485487Here are some useful macros and common values:
486488
487489- LCLWidgetType: win32, gtk, gtk2, qt, carbon, fpgui, nogui
488490- TargetOS: linux, win32, win64, wince, freebsd, netbsd, openbsd, darwin (many more)
489491- TargetCPU: i386, x86_64, arm, powerpc, sparc
490492- SrcOS: win, unix
491493
492494You can use the \$Env() macro to use environment variables.
493495
494496And of course you can use combinations. For example the LCL uses:
495497
496-`$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)`
498+```text
499+$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)
500+```
497501
498502See here the complete list of macros: [[IDE Macros in paths and filenames]]
499503
500504#### Machine / User specific search paths
501505
502506For example you have two windows machines stan and oliver. On stan your units are in *C:\units* and on oliver your units are in *D:\path*. The units belong to the package *SharedStuff* which is *C:\units\sharedstuff.lpk* on stan and *D:\path\sharedstuff.lpk* on oliver. Once you opened the lpk in the IDE or by lazbuild, the path is automatically stored in its configuration files (packagefiles.xml). When compiling a project that requires the package *SharedStuff*, the IDE and lazbuild knows where it is. So no configuration is needed.
503507
504508If you have want to deploy a package over many machine or for all users of a machine (e.g. a pool for students), then you can add a lpl file in the lazarus source directory. See packager/globallinks for examples.
505509
506510### Locale differences
507511
508512Some functions from Free Pascal, like StrToFloat behave differently depending on the current [[locale]]. For example, in the USA the [[DecimalSeparator|decimal separator]] is usually ".", but in many European and South American countries it is ",". This can be a problem as sometimes it is desired to have these functions behave in a fixed way, independently from the locale. An example is a file format with decimal points that always needs to be interpreted the same way.
509513
510514The next sections explain how to do that.
511515
512516#### macOS
513517
514518Refer to the [[Locale settings for macOS]] article for details of setting the locale on macOS.
515519
516520#### StrToFloat
517521
518522A new set of format settings which set a fixed decimal separator can be created with the following code:
519523
520524``` pascal
521525// in your .lpr project file
522526uses
523527...
524528{$IFDEF UNIX}
525529clocale
526530{ required on Linux/Unix for formatsettings support. Should be one of the first (probably after cthreads?}
527531{$ENDIF}
528532```
529533
530534and:
531535
532536``` pascal
533537// in your code:
534538var
535539 FPointSeparator, FCommaSeparator: TFormatSettings;
536540begin
537541 // Format settings to convert a string to a float
538542 FPointSeparator := DefaultFormatSettings;
539543 FPointSeparator.DecimalSeparator := '.';
540544 FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
541545 FCommaSeparator := DefaultFormatSettings;
542546 FCommaSeparator.DecimalSeparator := ',';
543547 FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator
544548```
545549
546550Later on you can use this format settings when calling StrToFloat, like this:
547551
548552``` pascal
549553// This function works like StrToFloat, but simply tries two possible decimal separator
550554// This will avoid an exception when the string format doesn't match the locale
551555function AnSemantico.StringToFloat(AStr: string): Double;
552556begin
553557 if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator)
554558 else Result := StrToFloat(AStr, FCommaSeparator);
555559end;
556560```
557561
558562### Gtk2 and masking FPU exceptions
559563
560564Gtk2 library changes the default value of FPU (floating point unit) exception mask. The consequence of this is that some floating point exceptions do not get raised if Gtk2 library is used by the application. That means that, if for example you develop a LCL application on Windows with win32/64 widgetset (which is Windows default) and plan to compile for Linux (where Gtk2 is default widgetset), you should keep this incompatibilities in mind.
561565
562566After [this forum topic](http://www.lazarus.freepascal.org/index.php/topic,13460.0.html) and answers on [this bug report](https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/19674) it became clear that nothing can be done about this, so we must know what actually these differences are.
563567
564568Therefore, let's do a test:
565569
566570``` pascal
567571uses
568572 ..., math,...
569573
570574{...}
571575
572576var
573577 FPUException: TFPUException;
574578 FPUExceptionMask: TFPUExceptionMask;
575579begin
576580 FPUExceptionMask := GetExceptionMask;
577581 for FPUException := Low(TFPUException) to High(TFPUException) do begin
578582 write(FPUException, ' - ');
579583 if not (FPUException in FPUExceptionMask) then
580584 write('not ');
581585
582586 writeln('masked!');
583587 end;
584588 readln;
585589end.
586590```
587591
588592Our simple program will get what FPC default is:
589593
590594 exInvalidOp - not masked!
591595 exDenormalized - masked!
592596 exZeroDivide - not masked!
593597 exOverflow - not masked!
594598 exUnderflow - masked!
595599 exPrecision - masked!
596600
597601However, with Gtk2, only exOverflow is not masked.
598602
599603The consequence is that EInvalidOp and EZeroDivide exceptions do not get raised if the application links to Gtk2 library! Normally, dividing non-zero value by zero raises EZeroDivide exception and dividing zero by zero raises EInvalidOp. For example the code like this:
600604
601605``` pascal
602606var
603607 X, A, B: Double;
604608// ...
605609
606610try
607611 X := A / B;
608612 // code block 1
609613except
610614 // code block 2
611615end;
612616// ...
613617```
614618
615619will take different direction when compiled in application with Gtk2 widgetset. On win widgetset, when B equals zero, an exception will get raised (EZeroDivide or EInvalidOp, depending on whether A is zero) and "code block 2" will be executed. On Gtk2 X becomes [Infinity](http://www.freepascal.org/docs-html/rtl/math/infinity.html), [NegInfinity](http://www.freepascal.org/docs-html/rtl/math/neginfinity.html), or [NaN](http://www.freepascal.org/docs-html/rtl/math/nan.html) and "code block 1" will be executed.
616620
617621We can think of different ways to overcome this inconsistency. Most of the time you can simply test if B equals zero and don't try the dividing in that case. However, sometimes you will need some different approach. So, take a look at the following examples:
618622
619623``` pascal
620624uses
621625 ..., math,...
622626
623627//...
624628var
625629 X, A, B: Double;
626630 Ind: Boolean;
627631// ...
628632try
629633 X := A / B;
630634 Ind := IsInfinite(X) or IsNan(X); // with gtk2, we fall here
631635except
632636 Ind := True; // in windows, we fall here when B equals zero
633637end;
634638if Ind then begin
635639 // code block 2
636640end else begin
637641 // code block 1
638642end;
639643// ...
640644```
641645
642646Or:
643647
644648``` pascal
645649uses
646650 ..., math,...
647651
648652//...
649653var
650654 X, A, B: Double;
651655 FPUExceptionMask: TFPUExceptionMask;
652656// ...
653657
654658try
655659 FPUExceptionMask := GetExceptionMask;
656660 SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // unmask
657661 try
658662 X := A / B;
659663 finally
660664 SetExceptionMask(FPUExceptionMask); // return previous masking immediately, we must not let Gtk2 internals to be called without the mask
661665 end;
662666 // code block 1
663667except
664668 // code block 2
665669end;
666670// ...
667671```
668672
669673Be cautious, do not do something like this (call LCL with still removed mask):
670674
671675``` pascal
672676try
673677 FPUExceptionMask := GetExceptionMask;
674678 SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
675679 try
676680 Edit1.Text := FloatToStr(A / B); // NO! Setting Edit's text goes down to widgetset internals and Gtk2 API must not be called without the mask!
677681 finally
678682 SetExceptionMask(FPUExceptionMask);
679683 end;
680684 // code block 1
681685except
682686 // code block 2
683687end;
684688// ...
685689```
686690
687691But use an auxiliary variable:
688692
689693``` pascal
690694try
691695 FPUExceptionMask := GetExceptionMask;
692696 SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
693697 try
694698 X := A / B; // First, we set auxiliary variable X
695699 finally
696700 SetExceptionMask(FPUExceptionMask);
697701 end;
698702 Edit1.Text := FloatToStr(X); // Now we can set Edit's text.
699703 // code block 1
700704except
701705 // code block 2
702706end;
703707// ...
704708```
705709
706710In all situations, when developing LCL applications, it is most important to know about this and to keep in mind that some floating point operations can go different way with different widgetsets. Then you can think of an appropriate way to workaround this, but this should not go unnoticed.
707711
708712## Issues when moving from Windows to *nix etc
709713
710714Issues specific to Linux, macOS, Android and other Unixes are described here. Not all subjects may apply to all platforms
711715
712716### On Unix there is no "application directory"
713717
714718Many programmers are used to calling 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 than the one of the executable, and it also varies between different shell programs (sh, bash, etc).
715719
716720Even if Application.ExeName could in fact know the directory where the executable is, that file could be a symbolic link, so you could get the directory of the link instead (depending on the Linux kernel version, you either get the directory of the link or of the program binary itself).
717721
718722To avoid this read the sections about [[Multiplatform Programming Guide|configuration files]] and [[Multiplatform Programming Guide|data files]].
719723
720724### Making do without Windows COM Automation
721725
722726With Windows, COM 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 COM Automation client and a COM Automation server, meaning it can both manipulate other programs and in turn be manipulated by other programs. For examples, see [Using COM Automation to interact with OpenOffice and Microsoft Office](http://wiki.lazarus.freepascal.org/Office_Automation#Using_COM_Automation_to_interact_with_OpenOffice_and_Microsoft_Office).
723727
724728#### macOS alternative
725729
726730Unfortunately, COM Automation isn't available on macOS and Linux. However, you can simulate some of the functionality of COM Automation on macOS using AppleScript.
727731
728732AppleScript is similar to COM 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):
729733
730734```applescript
731735 tell application "NeoOffice"
732736 launch
733737 end tell
734738```
735739
736740An 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 macOS 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:
737741
738742``` pascal
739743 fpsystem('myscript.applescript');
740744```
741745
742746This assumes the script is in the indicated file. You can also run scripts on the fly from your app using the macOS OsaScript command:
743747
744748``` pascal
745749 fpsystem('osascript -e '#39'tell application "NeoOffice"'#39 +
746750 ' -e '#39'launch'#39' -e '#39'end tell'#39);
747751 {Note use of #39 to single-quote the parameters}
748752```
749753
750754However, these examples are just the equivalent of the following Open command:
751755
752756``` pascal
753757 fpsystem('open -a NeoOffice');
754758```
755759
756760Similarly, in macOS you can emulate the Windows shell commands to launch a web browser and launch an email client with:
757761
758762``` pascal
759763 fpsystem('open -a safari "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');
760764```
761765
762766and
763767
764768``` pascal
765769 fpsystem('open -a mail "mailto:ss4200@invalid.org"');
766770```
767771
768772which assumes, fairly safely, that a macOS system will have the Safari and Mail applications installed. Of course, you should never make assumptions like this, and for the two previous examples, you can in fact just rely on macOS to do the right thing and pick the user's default web browser and email client if you instead use these variations:
769773
770774``` pascal
771775 fpsystem('open "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');
772776```
773777
774778and
775779
776780``` pascal
777781 fpsystem('open "mailto:ss4200@invalid.org"');
778782```
779783
780784Do not forget to include the Unix unit in your uses clause if you use `fpsystem` or `shell` (interchangeable).
781785
782786The 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 usable 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.
783787
784788#### Linux alternatives
785789
786790While 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 COM Automation and macOS 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).
787791
788792As with Windows, many macOS 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.
789793
790794### Alternatives for Windows API functions
791795
792796Many Windows programs use the Windows API extensively. In cross-platform applications Win API functions in the Windows unit should not be used, or should be enclosed by a conditional compile (e.g. {\$IFDEF MSWINDOWS} ).
793797
794798Fortunately many of the commonly used Windows API functions 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 replace these calls with true cross-platform components from the LCL. You can replace calls to GDI painting functions with calls to a TCanvas object's methods, for example.
795799
796800### Key codes
797801
798802Fortunately, detecting key codes (e.g. on KeyUp events) is portable: see [[LCL Key Handling]].
799803
800804### Installing your application
801805
802806See [[Deploying Your Application]].
803807
804808## See also
805809
806810- [[Writing portable code regarding the processor architecture]]
807811- [[Introduction to platform-sensitive development]]
808812- [[Apple-specific UI elements]]
809813
810814## External links
811815
812816- <http://www.midnightbeach.com/KylixForDelphiProgrammers.html> A guide for Windows programmers starting with Kylix. Many of concepts / code snippets apply to Lazarus.
813817- <http://www.stack.nl/~marcov/porting.pdf> A guide for writing portable source code, mainly between different compilers.
814818
815819---
816820*From the Free Pascal wiki, page "Multiplatform Programming Guide", as it stood on 2022-01-27 after a change by Zoran, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history