Difference between revisions of "Multiplatform Programming Guide/zh CN"

From Lazarus wiki
Jump to navigationJump to search
 
m (Fixed syntax highlighting; removed categories included in template)
 
(10 intermediate revisions by 2 users not shown)
Line 11: Line 11:
 
=== 你需要跨平台吗? ===
 
=== 你需要跨平台吗? ===
  
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 in 2014, Microsoft Windows may be the most important platform. Note that Mac OS X and/or Linux are making gaining in popularity, and may be a significant target for your application.
+
2014 年,如果你正在开发通用桌面软件,微软的 Windows 可能是重要的平台。需要注意的是,Mac OS X Linux 正变得流行,他们或许会成为你开发应用的另一个平台。
  
The 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, Mac OS X is quite popular in North America and western Europe, while in South America Macs are mostly restricted to video and sound work.
+
不同的国家流行不同的桌面操作系统,按使用软件类型、人员,没有通用规则,像 Mac OS X在美国和西欧相当受欢迎,而在南美 Mac 大多用于视频和音频工作。
  
On 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.
+
在许多签约项目里,只与一个平台相关。Free Pascal Lazarus 完全有能力针对特定平台编写软件。如,访问完整 Windows API 写一个 Windows 程序。
  
If 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.
+
If 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.  
 +
(<strike>如果你开发的软件运行在 Web 服务器上,在 Unix 平台,你可以将 Linux、Solaris、*BSD 和其他 Unix 作为目标平台,但你也想增加对 Windows 的完整支持。</strike>)
  
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. 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 VM solutions like VMware or Parallels.
+
一旦你解决了跨平台的问题,你可以在很大程度上忽略了其他平台,就像在一个平台开发。然而,在某些时候你需要测试部署在其他平台上的程序;像这样,你不想使用多个物理计算机,可以使用双引导、虚拟机VMware或类似的解决方案。这将有助于不受限制地访问目标操作系统。
  
 
== 跨平台编程 ==
 
== 跨平台编程 ==
  
=== 文件和文件夹 ===
+
=== 处理文件和文件夹 ===
  
When working with files and folders, this 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.
+
在处理文件和文件夹时,使用于非特定平台的路径分隔符和[[End_of_Line|行结束]]符很重要。这个列表定义了 Lazarus 处理文件或文件夹时的[[Constant/zh_CN|常量]]列表。
  
* '''PathSep''', '''PathSeparator''': path separator when adding many paths together (';', ...)
+
* '''PathSep''', '''PathSeparator''': 多路径分隔符 (';', ...)
* '''PathDelim''', '''DirectorySeparator''': directory separator for each platform ('/', '\', ...)
+
* '''PathDelim''', '''DirectorySeparator''': 各平台路径分隔符 ('/', '\', ...)
* '''LineEnding''': proper line ending character sequence (#13#10 - CRLF, #10 - [[Line_feed|LF]], ...)
+
* '''LineEnding''': 行结束符 (#13#10 - CRLF, #10 - [[Line_feed|LF]], ...)
  
Another 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 on Linux and BSD platforms. But if a EXT2, EXT3, etc file system is mounted on Windows, it would be case-sensitive. Respectively a FAT file system mounted on Linux should not be case sensitive.
 
  
It shall be paid special attention, that NTFS is non-case sensitive when used in Windows, but it is case sensitive when mounted by POSIX OSes. This could cause '''various problems, including loss of files''' if files with same filenames in different cases exist on a 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 the developers.
+
Windows 中,文件名通常不区分大小写,但在 Linux 和 BSD 平台将区分。但,如果一个EXT2、EXT3等系统文件挂载到 Windows 中,这仍将区分大小写。Linux 挂载 FAT 文件系统却不区分大小写。
  
Mac OS X use case insensitive filenames by default. This can be the cause of annoying bugs, so any portable application should use consistently filenames.
+
It shall be paid special attention, that NTFS is non-case sensitive when used in Windows, but it is case sensitive when mounted by POSIX OSes. This could cause '''various problems, including loss of files''' if files with same filenames in different cases exist on a 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 the developers.(<strike>特别注意的是,Windows 中的 NTFS 文件系统不区分大小写,但通过安装 POSIX 系统时是区分大小写的。这将会导致各种问题,其中包括文件丢失。</strike>)
 +
 
 +
Mac OS X 默认不区分文件名大小写,这可能是恼人错误的原因,所以任何便携式应用程序都应该坚持文件名大小写统一。
  
 
The RTL file functions use the system encoding for file names. Under Windows this is one of the windows code pages, while Linux, BSD and Mac OS X usually use UTF-8. The unit '''FileUtil''' of the LCL provides file functions which takes UTF-8 strings like the rest of the LCL.
 
The RTL file functions use the system encoding for file names. Under Windows this is one of the windows code pages, while Linux, BSD and Mac OS X usually use UTF-8. The unit '''FileUtil''' of the LCL provides file functions which takes UTF-8 strings like the rest of the LCL.
  
<syntaxhighlight>// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, MacOSX
+
(<strike>RTL(Free Pascal 运行库)文件使用系统编码。在 Windows 中是一代码页,在 Linux, BSD 和 Mac OS X 使用 UTF-8,LCL(Lazarus 组件库) 中的 '''FileUtil'''单元,提供了文件操作的相关功能。</strike>)
 +
 
 +
<syntaxhighlight lang=pascal>// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, MacOSX
 
// but normally these OS use UTF-8 as system encoding so the widestringmanager
 
// but normally these OS use UTF-8 as system encoding so the widestringmanager
// is not needed.
+
// 是不需要的。
function NeedRTLAnsi: boolean;// true if system encoding is not UTF-8
+
function NeedRTLAnsi: boolean;// 为真,则系统编码不是 UTF-8
 
procedure SetNeedRTLAnsi(NewValue: boolean);
 
procedure SetNeedRTLAnsi(NewValue: boolean);
function UTF8ToSys(const s: string): string;// as UTF8ToAnsi but more independent of widestringmanager
+
function UTF8ToSys(const s: string): string;// 作为 AnsiToUTF8 需要独立的 widestringmanager
function SysToUTF8(const s: string): string;// as AnsiToUTF8 but more independent of widestringmanager
+
function SysToUTF8(const s: string): string;// 作为 AnsiToUTF8 需要独立的 widestringmanager
function UTF8ToConsole(const s: string): string;// converts UTF8 string to console encoding (used by Write, WriteLn)
+
function UTF8ToConsole(const s: string): string;//转换 UTF8 编码字符串到控制台编码(使用 Write, WriteLn)
  
// file operations
+
// 文件操作
 
function FileExistsUTF8(const Filename: string): boolean;
 
function FileExistsUTF8(const Filename: string): boolean;
 
function FileAgeUTF8(const FileName: string): Longint;
 
function FileAgeUTF8(const FileName: string): Longint;
Line 74: Line 78:
 
function ForceDirectoriesUTF8(const Dir: string): Boolean;
 
function ForceDirectoriesUTF8(const Dir: string): Boolean;
  
// environment
+
// 环境
 
function ParamStrUTF8(Param: Integer): string;
 
function ParamStrUTF8(Param: Integer): string;
 
function GetEnvironmentStringUTF8(Index: Integer): string;
 
function GetEnvironmentStringUTF8(Index: Integer): string;
Line 80: Line 84:
 
function GetAppConfigDirUTF8(Global: Boolean): string;
 
function GetAppConfigDirUTF8(Global: Boolean): string;
  
// other
+
// 其他
 
function SysErrorMessageUTF8(ErrorCode: Integer): String;</syntaxhighlight>
 
function SysErrorMessageUTF8(ErrorCode: Integer): String;</syntaxhighlight>
  
 
===空文件名和路径双分隔符===
 
===空文件名和路径双分隔符===
  
There are differences in file/directory name handling in Windows versus Linux/Unix/Unix like systems.
+
Windows Linux/Unix/Unix 等系统在处理文件/文件夹名时有差异。
  
* Windows allows empty file names. That's why FileExistsUTF8('..\') checks under Windows in the parent directory for a file without name.
+
* Windows 允许空文件名。这就是为什么 FileExistsUTF8('..\') 可以检查空文件名的父目录。
* 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.
+
* Linux/Unix/类Unix系统空文件映射到目录上将被视为文件。这意味着,FileExistsUTF8('../') 将检查父目录,这会返回 真。
  
Double path delimiters in file names are also treated differently:
+
文件名的双路径分隔符应该区别对待:
* Windows: 'C:\' is not the same as 'C:\\'
+
* Windows:是 'C:\' 不是 'C:\\'
* Unix like OS: the path '/usr//' is the same as '/usr/'. If '/usr' is a directory then even all three are the same.
+
* 类Unix系统:路径 '/usr//' 等同于 '/usr/',如果 '/usr' 是目录,则,也等同于上面。
  
This is important when concatenating file names. For example:
+
连接文件名时,要注意,如:
  
<syntaxhighlight>FullFilename:=FilePath+PathDelim+ShortFilename; // can result in two PathDelims which gives different results under Windows and Linux
+
<syntaxhighlight lang=pascal>FullFilename:=FilePath+PathDelim+ShortFilename; // 这会返回 2个 PathDelims ,在 Windows和 Linux 将导致不同结果
FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // creates only one PathDelim
+
FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // 仅创建一个 PathDelim
FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // creates only one PathDelim and do some more clean up</syntaxhighlight>
+
FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // 仅创建 1个 PathDelim 并做些清理</syntaxhighlight>
  
The function TrimFilename replaces double path delimiters with single ones and shorten '..' paths. For example /usr//lib/../src is trimmed to /usr/src.
+
TrimFilename 函数将替换双路径分隔符为单分隔符,并缩短 '..' 路径,如,/usr//lib/../src 将修整为 /usr/src
  
If you want to know if a directory exists use '''DirectoryExistsUTF8'''.
+
如果想判断一个目录是否可用,使用 '''DirectoryExistsUTF8'''
  
Another 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.
+
使用 ExtractFilePath 可以返回文件名的路径部分,并包含路径定界符。
* 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.
+
* 在类 Unix 系统上,可以使用 FileExistsUTF8 ,如 FileExistsUTF8('/home/user/') ,如果 /home/user 存在,将返回真。
* Under Windows you must use the DirectoryExistsUTF8 function, but before that you must delete the path delimiter, for example with the ChompPathDelim function.
+
* Windows 下,你必须使用 DirectoryExistsUTF8 函数,但在此之前,你必须删除路径分隔符,如 ChompPathDelim 函数。
  
Under 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.
+
在类Unix系统的根目录是 '/',使用 ChompPathDelim 将创建一个空字符串。DirPathExists 函数工作方式类同于 DirectoryExistsUTF8,将修剪给定的路径。
  
Note 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.
+
需要注意的是,Unix/Linux 上使用  '~' (波浪号)代表用户目录,典型的例子,'/home/jim/' 将调用 jim 用户的文件。因此 '~/myapp/myfile' '/home/jim/myapp/myfile' 在命令行和脚本中相同。然则,Lazarus 不会自动扩展波浪号。这需要使用 ExpandFileNameUTF8('~/myapp/myfile') 来获取完整路径。
  
 
=== 文本编码 ===
 
=== 文本编码 ===
 
Text files are often encoded in the current system encoding. Under Windows this is usually one of the windows code pages, while Linux, BSD and Mac OS X 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:
 
  
<syntaxhighlight>function GuessEncoding(const s: string): string;
+
文本文件的编码通常是当前系统的编码。在Windows下,是代码页之一,而在Linux,BSD和Mac OS X,通常是 UTF-8编码。没有 100% 的规则找出文本文件的编码。LCL 的'''lconvencoding''' 有一个函数可以获得文件编码:
 +
 
 +
<syntaxhighlight lang=pascal>function GuessEncoding(const s: string): string;
 
function GetDefaultTextEncoding: string;</syntaxhighlight>
 
function GetDefaultTextEncoding: string;</syntaxhighlight>
  
And it contains functions to convert from one encoding to another:
+
编码转换:
  
<syntaxhighlight>function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;
+
<syntaxhighlight lang=pascal>function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;
  
function UTF8BOMToUTF8(const s: string): string; // UTF8 with BOM
+
function UTF8BOMToUTF8(const s: string): string; // UTF8 BOM
function ISO_8859_1ToUTF8(const s: string): string; // central europe
+
function ISO_8859_1ToUTF8(const s: string): string; // 中欧
function CP1250ToUTF8(const s: string): string; // central europe
+
function CP1250ToUTF8(const s: string): string; // 中欧
function CP1251ToUTF8(const s: string): string; // cyrillic
+
function CP1251ToUTF8(const s: string): string; // 西里尔
function CP1252ToUTF8(const s: string): string; // latin 1
+
function CP1252ToUTF8(const s: string): string; // 拉丁语1
 
...
 
...
function UTF8ToUTF8BOM(const s: string): string; // UTF8 with BOM
+
function UTF8ToUTF8BOM(const s: string): string; // UTF8 BOM
function UTF8ToISO_8859_1(const s: string): string; // central europe
+
function UTF8ToISO_8859_1(const s: string): string; // 中欧
function UTF8ToCP1250(const s: string): string; // central europe
+
function UTF8ToCP1250(const s: string): string; // 中欧
function UTF8ToCP1251(const s: string): string; // cyrillic
+
function UTF8ToCP1251(const s: string): string; // 西里尔
function UTF8ToCP1252(const s: string): string; // latin 1
+
function UTF8ToCP1252(const s: string): string; // 拉丁语1
 
...</syntaxhighlight>
 
...</syntaxhighlight>
  
For example to load a text file and convert it to UTF-8 you can use:
+
如,加载一个文本文件并将其转换为 UTF-8 编码,你可以使用:
  
<syntaxhighlight>var
+
<syntaxhighlight lang=pascal>var
 
   sl: TStringList;
 
   sl: TStringList;
 
   OriginalText: String;
 
   OriginalText: String;
Line 146: Line 149:
 
   sl:=TStringList.Create;
 
   sl:=TStringList.Create;
 
   try
 
   try
     sl.LoadFromFile('sometext.txt'); // beware: this changes line endings to system line endings
+
     sl.LoadFromFile('sometext.txt'); // 注意:此更改系统行行尾
 
     OriginalText:=sl.Text;
 
     OriginalText:=sl.Text;
 
     TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);
 
     TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);
Line 155: Line 158:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
And to save a text file in the system encoding you can use:
+
保存文本文件到系统编码可以使用:
<syntaxhighlight>sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);
+
<syntaxhighlight lang=pascal>sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);
 
sl.SaveToFile('sometext.txt');</syntaxhighlight>
 
sl.SaveToFile('sometext.txt');</syntaxhighlight>
  
 
=== 配置文件 ===
 
=== 配置文件 ===
  
You 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.
+
你可以使用 SysUtils 单元的 [[doc:rtl/sysutils/getappconfigdir.html|GetAppConfigDir]] 函数获取合适的位置存储系统配置文件。该函数有一个参数,称为全局。如果为真,则返回一个全局目录,即适用于系统上的所有用户。如果为参数为假,则为运行该程序的用户配置目录。在不支持多用户环境的系统,这两个目录是相同的。
  
There 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:
+
也可以使用 [[doc:rtl/sysutils/getappconfigfile.html|GetAppConfigFile]] 函数,将返回一个合适的配置文件名,你可以这样使用它:
  
 
ConfigFilePath := GetAppConfigFile(False) + '.conf';
 
ConfigFilePath := GetAppConfigFile(False) + '.conf';
  
Below are examples of the output of default path functions on different systems:
+
以下是不同系统默认路径输出的例子:
  
<syntaxhighlight>program project1;
+
<syntaxhighlight lang=pascal>program project1;
  
 
{$mode objfpc}{$H+}
 
{$mode objfpc}{$H+}
Line 183: Line 186:
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
The output on a GNU/Linux system with FPC 2.2.2. Note that using True is buggy, already fixed in 2.2.3:
+
GNU/Linux系统上FPC 2.2.2输出,为 TRUE 时会有问题,在 2.2.3 已经修复。
  
 
<pre>/etc/project1/
 
<pre>/etc/project1/
Line 190: Line 193:
 
/home/user/.config/project1.cfg</pre>
 
/home/user/.config/project1.cfg</pre>
  
You can notice that global 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.
+
你可以看到,全局配置文件存储在 /etc 目录和本地配置存储在用户的主目录中隐藏文件夹。目录中,其名称以点(.)被隐藏在Linux上。你可以使用 GetAppConfigDir 返回的路径以创建目录,然后保存配置文件。
  
{{Note| Normal users are not allowed to write to the /etc directory. Only users with administration rights can do this.}}
+
{{Note|普通用户不允许写入 /etc 目录。只有拥有超级管理员的用户才可以写入。}}
  
The output on Windows XP with FPC 2.2.4 + :
+
Windows XP 上的 FPC 2.2.4上输出:
  
 
<pre>C:\Documents and Settings\All Users\Application Data\project1\
 
<pre>C:\Documents and Settings\All Users\Application Data\project1\
Line 201: Line 204:
 
C:\Documents and Settings\user\Local Settings\Application Data\project1\project1.cfg</pre>
 
C:\Documents and Settings\user\Local Settings\Application Data\project1\project1.cfg</pre>
  
Notice that before FPC 2.2.4 the function was using the directory where the application was to store global configurations on Windows.
+
请注意,在 FPC 2.2.4 之前,全局配置在 Windows 目录。
  
The output on Windows 98 with FPC 2.2.0:
+
Windows 98 上的 FPC 2.2.0上输出:
  
 
<pre>C:\Program Files\PROJECT1
 
<pre>C:\Program Files\PROJECT1
Line 210: Line 213:
 
C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg</pre>
 
C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg</pre>
  
The output on Mac OS X with FPC 2.2.0:
+
Mac OS X 上的 FPC 2.2.0上输出:
  
 
<pre>/etc
 
<pre>/etc
Line 217: Line 220:
 
/Users/user/.config/project1/project1.cfg</pre>
 
/Users/user/.config/project1/project1.cfg</pre>
  
{{Note| The use of UPX interferes with the use of the GetAppConfigDir and GetAppConfigFile functions.}}
+
{{Note| 使用 UPX 会干扰 GetAppConfigDir GetAppConfigFile 函数。}}
  
{{Note| Under Mac OS X, in most cases config files are preference files, which should be XML files with the ending ".plist" and be stored in /Library/Preferences or ~/Library/Preferences with Names taken from the field "Bundle identifier" in the Info.plist of the application bundle. Using the Carbon calls CFPreference... is probably the easiest way to achieve this. .config files in the User directory are a violation of the programming guide lines.}}
+
{{Note| Mac OS X中,大多数情况下,配置文件是首选文件,是以 ".plist"结尾的XML文件,存放在 /Library/Preferences ~/Library/Preferences中,应用程序包所用的名字来自 Info.plist 的 "Bundle identifier" 字段。 使用 Carbon 调用 CFPreference... 是实现这一目标最简单的方法。.config 文件在用户目录违反的设计指导。}}
  
 
=== 数据和资源文件 ===
 
=== 数据和资源文件 ===
 
+
一个常见的问题是应用程序需要的数据文件在哪里存储,像图像、音乐、XML文件、数据库文件、帮助文件等等。不幸的是,它没有跨平台的功能去取得最佳位置和寻找数据文件。解决办法是,在不同平台上使用 IFDEF。
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.
 
  
 
==== Windows ====
 
==== Windows ====
On 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. [http://support.microsoft.com/kb/310294], under "Classify Application Data"). 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.
+
在Windows上,数据文件不应该存放在应用程序目录 (如 C:\Program Files\),应该在特定位置(见 [http://support.microsoft.com/kb/310294] "对应用程序数据进行分类")。Windows Vista 或较新的将强制 (用户只有 elevation 或 禁用 UAC时才可以写访问这些目录) 使用文件夹重定向机制,以适应旧的,错误的编程应用。只读不写数据,应用程序仍然可以正常工作。
  
See [[Windows_Programming_Tips#Getting_special_folders_.28My_documents.2C_Desktop.2C_local_application_data.2C_etc.29]]
+
查看 [[Windows_Programming_Tips#Getting_special_folders_.28My_documents.2C_Desktop.2C_local_application_data.2C_etc.29|获取特殊文件夹(我的文档、桌面、本地应用程序数据等)]]
  
 
==== Unix/Linux ====
 
==== Unix/Linux ====
On most Unixes (like Linux, FreeBSD, OpenBSD, Solaris), application data files are located in a fixed location, which can be something like: /usr/share/app_name or /opt/app_name.
+
在大多数Unix系统(像Linux, FreeBSD, OpenBSD, Solaris),应用程序数据存储在一个固定的地方,它可以在  /usr/share/app_name /opt/app_name。
  
Application data that needs to be written to by the application often gets stored in places like /var/<programname>, with appropriate permissions set.
+
写入的数据通常存储在 /var/<programname>,具有适当的权限。
  
User-specific read/write config/data will normally be stored somewhere under the user's home directory (e.g. in ~/.myfancyprogram).
+
用户特定的 读/写、配置/数据文件,将存储的用户主目录下(如存储在~/.myfancyprogram)。
  
 
==== OSX ====
 
==== OSX ====
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. Your so-called resource files should be located inside the bundle, so it can be moved and still continue to work normally. You need to use CoreFoundation API calls to find the location of the bundle. This path maps to MyBundle.app/Contents/Resources.
+
Mac OS X是一个异常在UNIX系统。部署应用程序的最佳方法是使用一个应用程序包,其中包括你的软件需要的所有文件。你所谓的资源文件应该位于包内,即便被移动,它仍可以正常工作。您需要使用CoreFoundation API调用找到包的位置。将路径映射到 MyBundle.app/Contents/Resources。
  
 
==== 示例 ====
 
==== 示例 ====
  
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 ResourcesPath + 'data' + PathDelim + 'myfile.dat')
+
* Windows 下的数据文件与可执行文件存放在一起(或在此基础上的其他目录,像  ResourcesPath + 'data' + PathDelim + 'myfile.dat'
* 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.
+
* 在 Unix系统中将从配置文件中读取一个目录。如果配置文件不存在或不包含信息,将使用  ('/usr/share/myapp/')做为默认目录。
{{Warning|The behaviour of this code under Windows SEEMS WRONG given the above explanation - unless it is meant for read-only access!}}
+
{{Warning|基于上面的解释,这段代码在 Windows 上是错误的——除非它是只读访问!}}
  
The configuration file path is located with the GetAppConfigFile function from the Free Pascal Runtime Library.
+
通过 GetAppConfigFile 函数来获得配置文件路径。
  
Below is a full unit which you can use at your applications.
+
下面是一个完整的单元,可以在你的程序上使用。
  
  
<syntaxhighlight>unit appsettings;
+
<syntaxhighlight lang=pascal>unit appsettings;
  
 
interface
 
interface
Line 345: Line 347:
 
  MyFile := TIniFile.Create(ConfigFilePath);
 
  MyFile := TIniFile.Create(ConfigFilePath);
 
  try
 
  try
   // Here you can read other information from the config file
+
   // 在这里你可以从配置文件中读取其他信息
  
 
{$ifdef Win32}
 
{$ifdef Win32}
Line 377: Line 379:
 
   CFRelease(pathRef);
 
   CFRelease(pathRef);
 
   CFRelease(pathCFStr);
 
   CFRelease(pathCFStr);
+
 
 
   Result := pathStr + BundleResourcesDirectory;
 
   Result := pathStr + BundleResourcesDirectory;
 
{$else}
 
{$else}
Line 399: Line 401:
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
and here is an example code of how to use that unit to get a resource file from it's correct location:
+
这个代码示例,从正确的位置得到一个资源文件:
  
<syntaxhighlight>bmp := TBitmap.Create
+
<syntaxhighlight lang=pascal>bmp := TBitmap.Create
 
try
 
try
 
   bmp.LoadFromFile(vConfigurations.ResourcesPath + 'MyBitmap.bmp');
 
   bmp.LoadFromFile(vConfigurations.ResourcesPath + 'MyBitmap.bmp');
Line 412: Line 414:
  
 
====在运行时检测位数====
 
====在运行时检测位数====
While 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.
+
虽然你可以控制定义编译 32位或64位编译器,但有时你想知道程序运行在多少位操作系统上。
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, if appropriate.
+
例如,你在64位的Windows系统上运行 32位的Lazarus 程序,你想在32位程序文件目录运行外部程序,或者你想给用户提供不同的信息:我需要在我的 LazUpdater 程序中为用户提供 32位或64位编译器的选择,如果合适的话。
  
For Windows, works for me on Vista x64 with FPC x86 compiler - thanks to [http://www.lazarusforum.de/viewtopic.php?f=55&t=5287|th German Lazarus forum]:
+
对于 Windows,FPC x86 编译器工作在我的 Vista x64上 —— 感谢 [http://www.lazarusforum.de/viewtopic.php?f=55&t=5287 Lazarus 德国论坛]
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
program bitness;
 
program bitness;
  
Line 429: Line 431:
 
   function IsWindows64: boolean;
 
   function IsWindows64: boolean;
 
   {
 
   {
   Detect if we are running on 64 bit Windows or 32 bit Windows,
+
   检测如果运行在 32/64 位Windows,
   independently of bitness of this program.
+
   独立于程序位数。
   Original source:
+
   原始来源:
 
   http://www.delphipraxis.net/118485-ermitteln-ob-32-bit-oder-64-bit-betriebssystem.html
 
   http://www.delphipraxis.net/118485-ermitteln-ob-32-bit-oder-64-bit-betriebssystem.html
   modified for FreePascal in German Lazarus forum:
+
   修改自 FreePascal 的Lazarus 德国论坛:
 
   http://www.lazarusforum.de/viewtopic.php?f=55&t=5287
 
   http://www.lazarusforum.de/viewtopic.php?f=55&t=5287
 
   }
 
   }
{$ifdef WIN32} //Modified KpjComp for 64bit compile mode
+
{$ifdef WIN32} //修改 KpjComp 的64位编译模式
 
   type
 
   type
     TIsWow64Process = function( // Type of IsWow64Process API fn
+
     TIsWow64Process = function( // IsWow64Process API 类型
 
         Handle: Windows.THandle; var Res: Windows.BOOL): Windows.BOOL; stdcall;
 
         Handle: Windows.THandle; var Res: Windows.BOOL): Windows.BOOL; stdcall;
 
   var
 
   var
     IsWow64Result: Windows.BOOL; // Result from IsWow64Process
+
     IsWow64Result: Windows.BOOL; // 返回 IsWow64Process
     IsWow64Process: TIsWow64Process; // IsWow64Process fn reference
+
     IsWow64Process: TIsWow64Process; // IsWow64Process 函数参考
 
   begin
 
   begin
     // Try to load required function from kernel32
+
     // 尝试从 kernel32 加载所需功能
 
     IsWow64Process := TIsWow64Process(Windows.GetProcAddress(
 
     IsWow64Process := TIsWow64Process(Windows.GetProcAddress(
 
       Windows.GetModuleHandle('kernel32'), 'IsWow64Process'));
 
       Windows.GetModuleHandle('kernel32'), 'IsWow64Process'));
 
     if Assigned(IsWow64Process) then
 
     if Assigned(IsWow64Process) then
 
     begin
 
     begin
       // Function is implemented: call it
+
       // 功能实现:调用它
 
       if not IsWow64Process(Windows.GetCurrentProcess, IsWow64Result) then
 
       if not IsWow64Process(Windows.GetCurrentProcess, IsWow64Result) then
 
         raise SysUtils.Exception.Create('IsWindows64: bad process handle');
 
         raise SysUtils.Exception.Create('IsWindows64: bad process handle');
       // Return result of function
+
       // 返回函数结果
 
       Result := IsWow64Result;
 
       Result := IsWow64Result;
 
     end
 
     end
 
     else
 
     else
       // Function not implemented: can't be running on Wow64
+
       // 功能未实现:不能运行在 Wow64
 
       Result := False;
 
       Result := False;
{$else} //if were running 64bit code, OS must be 64bit :)
+
{$else} //如果正在运行的是 64位代码,则系统必须是 64位 :)
 
   begin
 
   begin
 
   Result := True;
 
   Result := True;
Line 486: Line 488:
 
==== 指针/整数类型转换 ====
 
==== 指针/整数类型转换 ====
  
Pointers 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.  
+
指针在64位上需要 8字节,而不是 32位的4字节。The 'Integer' type remains 32bit on all platforms for compatibility. This means you can not typecast pointers into integers and back.(<strike>'Integer'类型仍是 32位,并兼容所有平台。这意味着你不能在整型与指针间互转。</strike>)
  
FPC 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.
+
FPC 为此定义了两种类型: PtrInt 和 PtrUInt。 PtrInt 在32位平台是32位有符号整数,在64位上是64位有符号整数。PtrUInt同上,不同是无符号。
  
Use for code that should work with Delphi and FPC:
+
工作在 Delphi FPC 的代码:
 
  {$IFNDEF FPC}
 
  {$IFNDEF FPC}
 
  type
 
  type
Line 497: Line 499:
 
  {$ENDIF}
 
  {$ENDIF}
  
Replace all '''integer(SomePointerOrObject)''' with '''PtrInt(SomePointerOrObject)'''.
+
替换所有  '''integer(指针或对象)''' '''PtrInt(指针或对象)'''
  
 
=== 字节顺序 ===
 
=== 字节顺序 ===
  
Intel 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.
+
Intel 平台是little endian(小端字节序),这意味着,首位存放低字节。像,$1234,存储方式为 $34 $12在小端字节序系统中。
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.
+
在  big endian(大端字节序)系统,像powerpc ,$1234 存储为 $12 $34。读取其他系统上的文件时,此不同处很重要。
 +
 
 +
参见:[http://smilejay.com/2012/01/big-endian_little-endian/ 大端、小端的区别(Big-endian or Little-endian)]
  
Use for code that should work on both:
+
下面代码在两种方式下工作:
<syntaxhighlight>{$IFDEF ENDIAN_BIG}
+
<syntaxhighlight lang=pascal>{$IFDEF ENDIAN_BIG}
 
...
 
...
 
{$ELSE}
 
{$ELSE}
Line 511: Line 515:
 
{$ENDIF}</syntaxhighlight>
 
{$ENDIF}</syntaxhighlight>
  
The opposite is ENDIAN_LITTLE.
+
此相反的是ENDIAN_LITTLE。
 
 
The 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).
 
  
 +
系统单元提供了大量字节序转换功能,像 SwapEndian, BEtoN(大端转换小端)、LEtoN(小端转大端),NtoBE(转换为大端) 和 NtoLE(转换为小端)。
  
 
==== Libc和其他特殊单位 ====
 
==== Libc和其他特殊单位 ====
  
Avoid legacy units like "oldlinux" and "libc" that are not supported outside of linux/i386.
+
避免使用像"oldlinux" "libc" 的单元,因为其在 linux/i386 以外不被支持。
  
 
==== 汇编 ====
 
==== 汇编 ====
  
Avoid assembler.
+
避免汇编。
  
 
==== 编译器定义 ====
 
==== 编译器定义 ====
  
<syntaxhighlight>{$ifdef CPU32}
+
<syntaxhighlight lang=pascal>{$ifdef CPU32}
...write here code for 32 bit processors
+
...这里写34位处理器代码
 
{$ENDIF}
 
{$ENDIF}
 
{$ifdef CPU64}
 
{$ifdef CPU64}
...write here code for 64 bit processors
+
...这里写64位处理器代码
 
{$ENDIF}</syntaxhighlight>
 
{$ENDIF}</syntaxhighlight>
  
Line 548: Line 551:
 
For example the unit wintricks.pas should only be used under Windows. In the uses section use:
 
For example the unit wintricks.pas should only be used under Windows. In the uses section use:
  
<syntaxhighlight>uses
+
<syntaxhighlight lang=pascal>uses
 
   Classes, SysUtils
 
   Classes, SysUtils
 
   {$IFDEF Windows}
 
   {$IFDEF Windows}
Line 610: Line 613:
 
A new set of format settings which set a fixed decimal separator can be created with the following code:
 
A new set of format settings which set a fixed decimal separator can be created with the following code:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
// in your .lpr project file
 
// in your .lpr project file
 
uses
 
uses
Line 622: Line 625:
 
and:
 
and:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
// in your code:
 
// in your code:
 
var
 
var
Line 637: Line 640:
 
Latter on you can use this format settings when calling StrToFloat, like this:
 
Latter on you can use this format settings when calling StrToFloat, like this:
  
<syntaxhighlight>// This function works like StrToFloat, but simply tries two possible decimal separator
+
<syntaxhighlight lang=pascal>// This function works like StrToFloat, but simply tries two possible decimal separator
 
// This will avoid an exception when the string format doesn't match the locale
 
// This will avoid an exception when the string format doesn't match the locale
 
function AnSemantico.StringToFloat(AStr: string): Double;
 
function AnSemantico.StringToFloat(AStr: string): Double;
Line 653: Line 656:
 
Therefore, let's do a test:
 
Therefore, let's do a test:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses
 
uses
 
   ..., math,...
 
   ..., math,...
Line 690: Line 693:
 
The 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:
 
The 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:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
var
 
var
 
   X, A, B: Double;
 
   X, A, B: Double;
Line 708: Line 711:
 
We 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:
 
We 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:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses
 
uses
 
   ..., math,...
 
   ..., math,...
Line 732: Line 735:
  
 
Or:
 
Or:
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses
 
uses
 
   ..., math,...
 
   ..., math,...
Line 758: Line 761:
  
 
Be cautious, do not do something like this (call LCL with still removed mask):
 
Be cautious, do not do something like this (call LCL with still removed mask):
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
try
 
try
 
   FPUExceptionMask := GetExceptionMask;
 
   FPUExceptionMask := GetExceptionMask;
Line 775: Line 778:
  
 
But use an auxiliary variable:
 
But use an auxiliary variable:
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
try
 
try
 
   FPUExceptionMask := GetExceptionMask;
 
   FPUExceptionMask := GetExceptionMask;
Line 874: Line 877:
 
* http://www.midnightbeach.com/KylixForDelphiProgrammers.html A guide for Windows programmers starting with Kylix. Many of concepts / code snippets apply to Lazarus.
 
* http://www.midnightbeach.com/KylixForDelphiProgrammers.html A guide for Windows programmers starting with Kylix. Many of concepts / code snippets apply to Lazarus.
 
* http://www.stack.nl/~marcov/porting.pdf A guide for writing portable source code, mainly between different compilers.
 
* http://www.stack.nl/~marcov/porting.pdf A guide for writing portable source code, mainly between different compilers.
 
[[Category:Tutorials]]
 
[[Category:Multiplatform Programming]]
 
[[Category:Platform-sensitive development]]
 
[[Category:Inter-process communication]]
 

Latest revision as of 01:27, 21 February 2020

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

大多数 LCL 可以跨平台使用,几乎不需要做修改。

这是在 Lazarus 和 Free Pascal 上编写跨平台应用程序的教程。涵盖了必要的注意事项,从准备到部署的相关帮助。

跨平台编程简介

你需要跨平台吗?

要回答这个问题,你需要先确定哪些用户使用你的应用程序,以便在那里部署。

在 2014 年,如果你正在开发通用桌面软件,微软的 Windows 可能是重要的平台。需要注意的是,Mac OS X 或 Linux 正变得流行,他们或许会成为你开发应用的另一个平台。

不同的国家流行不同的桌面操作系统,按使用软件类型、人员,没有通用规则,像 Mac OS X在美国和西欧相当受欢迎,而在南美 Mac 大多用于视频和音频工作。

在许多签约项目里,只与一个平台相关。Free Pascal 和 Lazarus 完全有能力针对特定平台编写软件。如,访问完整 Windows API 写一个 Windows 程序。

If 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. (如果你开发的软件运行在 Web 服务器上,在 Unix 平台,你可以将 Linux、Solaris、*BSD 和其他 Unix 作为目标平台,但你也想增加对 Windows 的完整支持。

一旦你解决了跨平台的问题,你可以在很大程度上忽略了其他平台,就像在一个平台开发。然而,在某些时候你需要测试部署在其他平台上的程序;像这样,你不想使用多个物理计算机,可以使用双引导、虚拟机VMware或类似的解决方案。这将有助于不受限制地访问目标操作系统。

跨平台编程

处理文件和文件夹

在处理文件和文件夹时,使用于非特定平台的路径分隔符和行结束符很重要。这个列表定义了 Lazarus 处理文件或文件夹时的常量列表。

  • PathSep, PathSeparator: 多路径分隔符 (';', ...)
  • PathDelim, DirectorySeparator: 各平台路径分隔符 ('/', '\', ...)
  • LineEnding: 行结束符 (#13#10 - CRLF, #10 - LF, ...)

要特别注意的是文件系统问题。

在 Windows 中,文件名通常不区分大小写,但在 Linux 和 BSD 平台将区分。但,如果一个EXT2、EXT3等系统文件挂载到 Windows 中,这仍将区分大小写。Linux 挂载 FAT 文件系统却不区分大小写。

It shall be paid special attention, that NTFS is non-case sensitive when used in Windows, but it is case sensitive when mounted by POSIX OSes. This could cause various problems, including loss of files if files with same filenames in different cases exist on a 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 the developers.(特别注意的是,Windows 中的 NTFS 文件系统不区分大小写,但通过安装 POSIX 系统时是区分大小写的。这将会导致各种问题,其中包括文件丢失。)

Mac OS X 默认不区分文件名大小写,这可能是恼人错误的原因,所以任何便携式应用程序都应该坚持文件名大小写统一。

The RTL file functions use the system encoding for file names. Under Windows this is one of the windows code pages, while Linux, BSD and Mac OS X usually use UTF-8. The unit FileUtil of the LCL provides file functions which takes UTF-8 strings like the rest of the LCL.

(RTL(Free Pascal 运行库)文件使用系统编码。在 Windows 中是一代码页,在 Linux, BSD 和 Mac OS X 使用 UTF-8,LCL(Lazarus 组件库) 中的 FileUtil单元,提供了文件操作的相关功能。)

// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, MacOSX
// but normally these OS use UTF-8 as system encoding so the widestringmanager
// 是不需要的。
function NeedRTLAnsi: boolean;// 为真,则系统编码不是 UTF-8
procedure SetNeedRTLAnsi(NewValue: boolean);
function UTF8ToSys(const s: string): string;// 作为 AnsiToUTF8 需要独立的 widestringmanager
function SysToUTF8(const s: string): string;// 作为 AnsiToUTF8 需要独立的 widestringmanager
function UTF8ToConsole(const s: string): string;//转换 UTF8 编码字符串到控制台编码(使用 Write, WriteLn)

// 文件操作
function FileExistsUTF8(const Filename: string): boolean;
function FileAgeUTF8(const FileName: string): Longint;
function DirectoryExistsUTF8(const Directory: string): Boolean;
function ExpandFileNameUTF8(const FileName: string): string;
function ExpandUNCFileNameUTF8(const FileName: string): string;
function ExtractShortPathNameUTF8(Const FileName : String) : String;
function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;
function FindNextUTF8(var Rslt: TSearchRec): Longint;
procedure FindCloseUTF8(var F: TSearchrec);
function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;
function FileGetAttrUTF8(const FileName: String): Longint;
function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;
function DeleteFileUTF8(const FileName: String): Boolean;
function RenameFileUTF8(const OldName, NewName: String): Boolean;
function FileSearchUTF8(const Name, DirList : String): String;
function FileIsReadOnlyUTF8(const FileName: String): Boolean;
function GetCurrentDirUTF8: String;
function SetCurrentDirUTF8(const NewDir: String): Boolean;
function CreateDirUTF8(const NewDir: String): Boolean;
function RemoveDirUTF8(const Dir: String): Boolean;
function ForceDirectoriesUTF8(const Dir: string): Boolean;

// 环境
function ParamStrUTF8(Param: Integer): string;
function GetEnvironmentStringUTF8(Index: Integer): string;
function GetEnvironmentVariableUTF8(const EnvVar: string): String;
function GetAppConfigDirUTF8(Global: Boolean): string;

// 其他
function SysErrorMessageUTF8(ErrorCode: Integer): String;

空文件名和路径双分隔符

Windows 和 Linux/Unix/Unix 等系统在处理文件/文件夹名时有差异。

  • Windows 允许空文件名。这就是为什么 FileExistsUTF8('..\') 可以检查空文件名的父目录。
  • 在 Linux/Unix/类Unix系统空文件映射到目录上将被视为文件。这意味着,FileExistsUTF8('../') 将检查父目录,这会返回 真。

文件名的双路径分隔符应该区别对待:

  • Windows:是 'C:\' 不是 'C:\\'
  • 类Unix系统:路径 '/usr//' 等同于 '/usr/',如果 '/usr' 是目录,则,也等同于上面。

连接文件名时,要注意,如:

FullFilename:=FilePath+PathDelim+ShortFilename; // 这会返回 2个 PathDelims ,在 Windows和 Linux 将导致不同结果
FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // 仅创建一个 PathDelim
FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // 仅创建 1个 PathDelim 并做些清理

TrimFilename 函数将替换双路径分隔符为单分隔符,并缩短 '..' 路径,如,/usr//lib/../src 将修整为 /usr/src 。

如果想判断一个目录是否可用,使用 DirectoryExistsUTF8

使用 ExtractFilePath 可以返回文件名的路径部分,并包含路径定界符。

  • 在类 Unix 系统上,可以使用 FileExistsUTF8 ,如 FileExistsUTF8('/home/user/') ,如果 /home/user 存在,将返回真。
  • 在 Windows 下,你必须使用 DirectoryExistsUTF8 函数,但在此之前,你必须删除路径分隔符,如 ChompPathDelim 函数。

在类Unix系统的根目录是 '/',使用 ChompPathDelim 将创建一个空字符串。DirPathExists 函数工作方式类同于 DirectoryExistsUTF8,将修剪给定的路径。

需要注意的是,Unix/Linux 上使用 '~' (波浪号)代表用户目录,典型的例子,'/home/jim/' 将调用 jim 用户的文件。因此 '~/myapp/myfile' 和 '/home/jim/myapp/myfile' 在命令行和脚本中相同。然则,Lazarus 不会自动扩展波浪号。这需要使用 ExpandFileNameUTF8('~/myapp/myfile') 来获取完整路径。

文本编码

文本文件的编码通常是当前系统的编码。在Windows下,是代码页之一,而在Linux,BSD和Mac OS X,通常是 UTF-8编码。没有 100% 的规则找出文本文件的编码。LCL 的lconvencoding 有一个函数可以获得文件编码:

function GuessEncoding(const s: string): string;
function GetDefaultTextEncoding: string;

编码转换:

function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;

function UTF8BOMToUTF8(const s: string): string; // UTF8 与 BOM
function ISO_8859_1ToUTF8(const s: string): string; // 中欧
function CP1250ToUTF8(const s: string): string; // 中欧
function CP1251ToUTF8(const s: string): string; // 西里尔
function CP1252ToUTF8(const s: string): string; // 拉丁语1
...
function UTF8ToUTF8BOM(const s: string): string; // UTF8 与 BOM
function UTF8ToISO_8859_1(const s: string): string; // 中欧
function UTF8ToCP1250(const s: string): string; // 中欧
function UTF8ToCP1251(const s: string): string; // 西里尔
function UTF8ToCP1252(const s: string): string; // 拉丁语1
...

如,加载一个文本文件并将其转换为 UTF-8 编码,你可以使用:

var
  sl: TStringList;
  OriginalText: String;
  TextAsUTF8: String;
begin
  sl:=TStringList.Create;
  try
    sl.LoadFromFile('sometext.txt'); // 注意:此更改系统行行尾
    OriginalText:=sl.Text;
    TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);
    ...
  finally
    sl.Free;
  end;
end;

保存文本文件到系统编码可以使用:

sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);
sl.SaveToFile('sometext.txt');

配置文件

你可以使用 SysUtils 单元的 GetAppConfigDir 函数获取合适的位置存储系统配置文件。该函数有一个参数,称为全局。如果为真,则返回一个全局目录,即适用于系统上的所有用户。如果为参数为假,则为运行该程序的用户配置目录。在不支持多用户环境的系统,这两个目录是相同的。

也可以使用 GetAppConfigFile 函数,将返回一个合适的配置文件名,你可以这样使用它:

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

以下是不同系统默认路径输出的例子:

program project1;

{$mode objfpc}{$H+}

uses
  SysUtils;

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

在 GNU/Linux系统上FPC 2.2.2输出,为 TRUE 时会有问题,在 2.2.3 已经修复。

/etc/project1/
/home/user/.config/project1/
/etc/project1.cfg
/home/user/.config/project1.cfg

你可以看到,全局配置文件存储在 /etc 目录和本地配置存储在用户的主目录中隐藏文件夹。目录中,其名称以点(.)被隐藏在Linux上。你可以使用 GetAppConfigDir 返回的路径以创建目录,然后保存配置文件。

Light bulb  Note: 普通用户不允许写入 /etc 目录。只有拥有超级管理员的用户才可以写入。

在 Windows XP 上的 FPC 2.2.4上输出:

C:\Documents and Settings\All Users\Application Data\project1\
C:\Documents and Settings\user\Local Settings\Application Data\project1
C:\Documents and Settings\All Users\Application Data\project1\project1.cfg
C:\Documents and Settings\user\Local Settings\Application Data\project1\project1.cfg

请注意,在 FPC 2.2.4 之前,全局配置在 Windows 目录。

在 Windows 98 上的 FPC 2.2.0上输出:

C:\Program Files\PROJECT1
C:\Windows\Local Settings\Application Data\PROJECT1
C:\Program Files\PROJECT1\PROJECT1.cfg
C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg

在 Mac OS X 上的 FPC 2.2.0上输出:

/etc
/Users/user/.config/project1
/etc/project1.cfg
/Users/user/.config/project1/project1.cfg
Light bulb  Note: 使用 UPX 会干扰 GetAppConfigDir 和 GetAppConfigFile 函数。
Light bulb  Note: 在 Mac OS X中,大多数情况下,配置文件是首选文件,是以 ".plist"结尾的XML文件,存放在 /Library/Preferences 或 ~/Library/Preferences中,应用程序包所用的名字来自 Info.plist 的 "Bundle identifier" 字段。 使用 Carbon 调用 CFPreference... 是实现这一目标最简单的方法。.config 文件在用户目录违反的设计指导。

数据和资源文件

一个常见的问题是应用程序需要的数据文件在哪里存储,像图像、音乐、XML文件、数据库文件、帮助文件等等。不幸的是,它没有跨平台的功能去取得最佳位置和寻找数据文件。解决办法是,在不同平台上使用 IFDEF。

Windows

在Windows上,数据文件不应该存放在应用程序目录 (如 C:\Program Files\),应该在特定位置(见 [1] 在"对应用程序数据进行分类")。Windows Vista 或较新的将强制 (用户只有 elevation 或 禁用 UAC时才可以写访问这些目录) 使用文件夹重定向机制,以适应旧的,错误的编程应用。只读不写数据,应用程序仍然可以正常工作。

查看 获取特殊文件夹(我的文档、桌面、本地应用程序数据等)

Unix/Linux

在大多数Unix系统(像Linux, FreeBSD, OpenBSD, Solaris),应用程序数据存储在一个固定的地方,它可以在 /usr/share/app_name 或 /opt/app_name。

写入的数据通常存储在 /var/<programname>,具有适当的权限。

用户特定的 读/写、配置/数据文件,将存储的用户主目录下(如存储在~/.myfancyprogram)。

OSX

Mac OS X是一个异常在UNIX系统。部署应用程序的最佳方法是使用一个应用程序包,其中包括你的软件需要的所有文件。你所谓的资源文件应该位于包内,即便被移动,它仍可以正常工作。您需要使用CoreFoundation API调用找到包的位置。将路径映射到 MyBundle.app/Contents/Resources。

示例

本节介绍了一种特定解决方案,其中

  • Windows 下的数据文件与可执行文件存放在一起(或在此基础上的其他目录,像 ResourcesPath + 'data' + PathDelim + 'myfile.dat')
  • 在 Unix系统中将从配置文件中读取一个目录。如果配置文件不存在或不包含信息,将使用 ('/usr/share/myapp/')做为默认目录。
Warning-icon.png

Warning: 基于上面的解释,这段代码在 Windows 上是错误的——除非它是只读访问!

通过 GetAppConfigFile 函数来获得配置文件路径。

下面是一个完整的单元,可以在你的程序上使用。


unit appsettings;

interface

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

uses
  Classes, SysUtils, Forms, IniFiles, constants;

type

 { TConfigurations }

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


var
 vConfigurations: TConfigurations;

implementation

{$IFDEF Win32}
uses
  Windows;
{$ENDIF}
{$ifdef Darwin}
uses
  MacOSAll;
{$endif}

const
  DefaultDirectory = '/usr/share/myapp/';
  BundleResourcesDirectory = '/Contents/Resources/';

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

  IdentResourcesPath = 'ResourcesPath';

{ TConfigurations }

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

  ResourcesPath := GetResourcesPath();

  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, IdentResourcesPath, ResourcesPath);
  finally
    MyFile.Free;
  end;
end;

procedure TConfigurations.ReadFromFile(Sender: TObject);
var
 MyFile: TIniFile;
begin
 MyFile := TIniFile.Create(ConfigFilePath);
 try
  // 在这里你可以从配置文件中读取其他信息

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

function TConfigurations.GetResourcesPath(): string;
begin
{$ifdef Darwin}
var
  pathRef: CFURLRef;
  pathCFStr: CFStringRef;
  pathStr: shortstring;
{$endif}
begin
{$ifdef UNIX}
{$ifdef Darwin}
  pathRef := CFBundleCopyBundleURL(CFBundleGetMainBundle());
  pathCFStr := CFURLCopyFileSystemPath(pathRef, kCFURLPOSIXPathStyle);
  CFStringGetPascalString(pathCFStr, @pathStr, 255, CFStringGetSystemEncoding());
  CFRelease(pathRef);
  CFRelease(pathCFStr);

  Result := pathStr + BundleResourcesDirectory;
{$else}
  Result := DefaultDirectory;
{$endif}
{$endif}

{$ifdef Windows}
  Result := ExtractFilePath(Application.EXEName);
{$endif}
end;

initialization

 vConfigurations := TConfigurations.Create;

finalization

 FreeAndNil(vConfigurations);

end.

这个代码示例,从正确的位置得到一个资源文件:

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

32/64 位

在运行时检测位数

虽然你可以控制定义编译 32位或64位编译器,但有时你想知道程序运行在多少位操作系统上。 例如,你在64位的Windows系统上运行 32位的Lazarus 程序,你想在32位程序文件目录运行外部程序,或者你想给用户提供不同的信息:我需要在我的 LazUpdater 程序中为用户提供 32位或64位编译器的选择,如果合适的话。

对于 Windows,FPC x86 编译器工作在我的 Vista x64上 —— 感谢 Lazarus 德国论坛

program bitness;

{$mode objfpc}{$H+}
{$APPTYPE CONSOLE}
uses {$IFDEF UNIX} {$IFDEF UseCThreads}
  cthreads, {$ENDIF} {$ENDIF}
  Classes,
  SysUtils,
  Windows;

  function IsWindows64: boolean;
  {
  检测如果运行在 32/64 位Windows,
  独立于程序位数。
  原始来源:
  http://www.delphipraxis.net/118485-ermitteln-ob-32-bit-oder-64-bit-betriebssystem.html
  修改自 FreePascal 的Lazarus 德国论坛:
  http://www.lazarusforum.de/viewtopic.php?f=55&t=5287
  }
{$ifdef WIN32} //修改 KpjComp 的64位编译模式
  type
    TIsWow64Process = function( // IsWow64Process API 类型
        Handle: Windows.THandle; var Res: Windows.BOOL): Windows.BOOL; stdcall;
  var
    IsWow64Result: Windows.BOOL; // 返回 IsWow64Process
    IsWow64Process: TIsWow64Process; // IsWow64Process 函数参考
  begin
    // 尝试从 kernel32 加载所需功能
    IsWow64Process := TIsWow64Process(Windows.GetProcAddress(
      Windows.GetModuleHandle('kernel32'), 'IsWow64Process'));
    if Assigned(IsWow64Process) then
    begin
      // 功能实现:调用它
      if not IsWow64Process(Windows.GetCurrentProcess, IsWow64Result) then
        raise SysUtils.Exception.Create('IsWindows64: bad process handle');
      // 返回函数结果
      Result := IsWow64Result;
    end
    else
      // 功能未实现:不能运行在 Wow64
      Result := False;
{$else} //如果正在运行的是 64位代码,则系统必须是 64位 :)
  begin
   Result := True;
{$endif}
  end;

begin
  try
    if IsWindows64 then
    begin
      writeln('This operating system is 64 bit.');
    end
    else
    begin
      writeln('This operating system is 32 bit.');
    end;
  except
    on E: exception do
    begin
      writeln('Could not determine bitness of operating system. Error details: ' + E.ClassName+'/'+E.Message);
    end;
  end;
end.

指针/整数类型转换

指针在64位上需要 8字节,而不是 32位的4字节。The 'Integer' type remains 32bit on all platforms for compatibility. This means you can not typecast pointers into integers and back.('Integer'类型仍是 32位,并兼容所有平台。这意味着你不能在整型与指针间互转。

FPC 为此定义了两种类型: PtrInt 和 PtrUInt。 PtrInt 在32位平台是32位有符号整数,在64位上是64位有符号整数。PtrUInt同上,不同是无符号。

工作在 Delphi 和 FPC 的代码:

{$IFNDEF FPC}
type
  PtrInt = integer;
  PtrUInt = cardinal;
{$ENDIF}

替换所有 integer(指针或对象)PtrInt(指针或对象)

字节顺序

Intel 平台是little endian(小端字节序),这意味着,首位存放低字节。像,$1234,存储方式为 $34 $12在小端字节序系统中。 在 big endian(大端字节序)系统,像powerpc ,$1234 存储为 $12 $34。读取其他系统上的文件时,此不同处很重要。

参见:大端、小端的区别(Big-endian or Little-endian)

下面代码在两种方式下工作:

{$IFDEF ENDIAN_BIG}
...
{$ELSE}
...
{$ENDIF}

此相反的是ENDIAN_LITTLE。

系统单元提供了大量字节序转换功能,像 SwapEndian, BEtoN(大端转换小端)、LEtoN(小端转大端),NtoBE(转换为大端) 和 NtoLE(转换为小端)。

Libc和其他特殊单位

避免使用像"oldlinux" 和 "libc" 的单元,因为其在 linux/i386 以外不被支持。

汇编

避免汇编。

编译器定义

{$ifdef CPU32}
...这里写34位处理器代码
{$ENDIF}
{$ifdef CPU64}
...这里写64位处理器代码
{$ENDIF}

项目、包和搜索路径

Lazarus 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.

Some advice to achieve this

The 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.

Every 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: Creating a package for your common units

Every 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.

特定于平台的单元

For example the unit wintricks.pas should only be used under Windows. In the uses section use:

uses
  Classes, SysUtils
  {$IFDEF Windows}
  ,WinTricks
  {$ENDIF}
  ;

If 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.

See also Platform specific units

特定平台搜索路径

When 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. An example for one include file for each LCL widget set:

Create one file for each widget set you want to support:

win32/example.inc
gtk/example.inc
gtk2/example.inc
carbon/example.inc

You 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.

In your unit use the directive: {$I example.inc}

Here are some useful macros and common values:

  • LCLWidgetType: win32, gtk, gtk2, qt, carbon, fpgui, nogui
  • TargetOS: linux, win32, win64, wince, freebsd, netbsd, openbsd, darwin (many more)
  • TargetCPU: i386, x86_64, arm, powerpc, sparc
  • SrcOS: win, unix

You can use the $Env() macro to use environment variables.

And of course you can use combinations. For example the LCL uses:

$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)

See here the complete list of macros: IDE Macros in paths and filenames

计算机/用户的特定搜索路径

For 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.

If 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.

区域差异

Some functions from Free Pascal, like StrToFloat behave differently depending on the current locale. For example, in the USA the 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.

The next sections explain how to do that.


StrToFloat

A new set of format settings which set a fixed decimal separator can be created with the following code:

// in your .lpr project file
uses
...
{$IFDEF UNIX}
clocale 
{ required on Linux/Unix for formatsettings support. Should be one fo the first (probably after cthreads?}
{$ENDIF}

and:

// in your code:
var
  FPointSeparator, FCommaSeparator: TFormatSettings;
begin
  // Format seetings to convert a string to a float
  FPointSeparator := DefaultFormatSettings;
  FPointSeparator.DecimalSeparator := '.';
  FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
  FCommaSeparator := DefaultFormatSettings;
  FCommaSeparator.DecimalSeparator := ',';
  FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator

Latter on you can use this format settings when calling StrToFloat, like this:

// This function works like StrToFloat, but simply tries two possible decimal separator
// This will avoid an exception when the string format doesn't match the locale
function AnSemantico.StringToFloat(AStr: string): Double;
begin
  if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator)
  else Result := StrToFloat(AStr, FCommaSeparator);
end;

GTK2 和屏蔽 FPU 异常

Gtk2 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.

After this forum topic and answers on this bug report it became clear that nothing can be done about this, so we must know what actually these differences are.

Therefore, let's do a test:

uses
  ..., math,...

{...}

var
  FPUException: TFPUException;
  FPUExceptionMask: TFPUExceptionMask;
begin
  FPUExceptionMask := GetExceptionMask;
  for FPUException := Low(TFPUException) to High(TFPUException) do begin
    write(FPUException, ' - ');
    if not (FPUException in FPUExceptionMask) then
      write('not ');

    writeln('masked!');
  end;
  readln;
end.

Our simple program will get what FPC default is:


exInvalidOp - not masked!
exDenormalized - masked!
exZeroDivide - not masked!
exOverflow - not masked!
exUnderflow - masked!
exPrecision - masked!

However, with Gtk2, only exOverflow is not masked.

The 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:

var
  X, A, B: Double;
// ...

try
  X := A / B;
  // code block 1
except   
  // code block 2
end;
// ...

will 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, NegInfinity, or NaN and "code block 1" will be executed.

We 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:

uses
  ..., math,...

//...
var
  X, A, B: Double;
  Ind: Boolean;
// ...
try
  X := A / B;
  Ind := IsInfinite(X) or IsNan(X); // with gtk2, we fall here
except   
  Ind := True; // in windows, we fall here when B equals zero
end;
if Ind then begin
  // code block 2
end else begin
  // code block 1
end;
// ...

Or:

uses
  ..., math,...

//...
var
  X, A, B: Double;
  FPUExceptionMask: TFPUExceptionMask;
// ...

try
  FPUExceptionMask := GetExceptionMask;
  SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // unmask
  try
    X := A / B;
  finally
    SetExceptionMask(FPUExceptionMask); // return previous masking immediately, we must not let Gtk2 internals to be called without the mask
  end;
  // code block 1
except   
  // code block 2
end;
// ...

Be cautious, do not do something like this (call LCL with still removed mask):

try
  FPUExceptionMask := GetExceptionMask;
  SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
  try
    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!
  finally
    SetExceptionMask(FPUExceptionMask);
  end;
  // code block 1
except   
  // code block 2
end;
// ...

But use an auxiliary variable:

try
  FPUExceptionMask := GetExceptionMask;
  SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
  try
    X := A / B; // First, we set auxiliary variable X
  finally
    SetExceptionMask(FPUExceptionMask);
  end;
  Edit1.Text := FloatToStr(X); // Now we can set Edit's text.
  // code block 1
except   
  // code block 2
end;
// ...

In 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.

从 Windows 迁移到 *nix 的问题

Issues specific to Linux, OSX, Android and other Unixes are described here. Not all subjects may apply to all platforms

在Unix上没有“应用程序目录”

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 than 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 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).

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

Windows COM 没有自动化

With 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.

替代 OSX

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

AppleScript 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):

 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');

Similarly, in OS X you can emulate the Windows shell commands to launch a web browser and launch an email client with:

 fpsystem('open -a safari "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');

and

 fpsystem('open -a mail "mailto:ss4200@invalid.org"');

which assumes, fairly safely, that an OS X 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 OS X to do the right thing and pick the user's default web browser and email client if you instead use these variations:

 fpsystem('open "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');

and

 fpsystem('open "mailto:ss4200@invalid.org"');

Do not forget to include the Unix unit in your uses clause if you use fpsystem or shell (interchangeable).

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 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.

Linux 替代品

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 COM 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.

Windows API 函数的替代品

Many 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} ).

Fortunately 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.

关键代码

Fortunately, detecting key codes (e.g. on KeyUp events) is portable: see LCL Key Handling.

安装跨平台应用

See Deploying Your Application

参见