Sign in Join
Wiki copytemporary

Multiplatform Programming Guide/zh CN: what changed

Revision 3 to 4. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
1-大多数 [[Lazarus Documentation/zh CN|LCL]] 可以跨平台使用,几乎不需要做修改。
2-
3-这是在 Lazarus 和 Free Pascal 上编写跨平台应用程序的教程。涵盖了必要的注意事项,从准备到[[Deploying Your Application|部署]]的相关帮助。
1+# 跨平台编程指南
42
5-## 跨平台编程简介
3+本文是用 Lazarus 和 Free Pascal 编写跨平台应用程序的教程,涵盖了必要的注意事项,有助于创建可跨平台[[Deploying Your Application|deploy]]的程序。
64
7-### 你需要跨平台吗?
5+大多数 [[LCL]] 应用程序都可以跨平台,无需任何额外操作。 这一原则名为“[[Write once compile anywhere|一次编写、到处编译]]”。
86
9-要回答这个问题,你需要先确定哪些用户使用你的应用程序,以便在那里部署。
7+
108
11-在 2014 年,如果你正在开发通用桌面软件,微软的 Windows 可能是重要的平台。需要注意的是,Mac OS X 或 Linux 正变得流行,他们或许会成为你开发应用的另一个平台。
9+## 跨平台编程简介
1210
13-不同的国家流行不同的桌面操作系统,按使用软件类型、人员,没有通用规则,像 Mac OS X在美国和西欧相当受欢迎,而在南美 Mac 大多用于视频和音频工作。
11+### 需要多平台运行吗?
1412
15-在许多签约项目里,只与一个平台相关。Free Pascal 和 Lazarus 完全有能力针对特定平台编写软件。如,访问完整 Windows API 写一个 Windows 程序。
13+要回答这个问题,首先应确定应用程序的潜在用户和使用方式。应用程序的部署环境决定了这个问题的答案。
1614
17-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 的完整支持。~~)
15+如果是在 2014 年开发通用桌面软件,微软的 Windows 可能是最重要的平台。请注意,Mac OS X 和 Linux 正越来越受到欢迎,或许会成为应用程序的重要部署目标。
1816
19-一旦你解决了跨平台的问题,你可以在很大程度上忽略了其他平台,就像在一个平台开发。然而,在某些时候你需要测试部署在其他平台上的程序;像这样,你不想使用多个物理计算机,可以使用双引导、虚拟机VMware或类似的解决方案。这将有助于不受限制地访问目标操作系统。
17+因国家/地区、软件类型、受众的不同,各种桌面操作系统的流行程度也各不相同,没有通用的规则。例如 macOS 在北美和西欧相当受欢迎,而在南美则大多只用于处理视频和音频。
2018
21-## 跨平台编程
19+在许多签约项目里,只与一个平台相关。Free Pascal 和 Lazarus 足以编写某个指定平台的软件,比如访问全部 Windows API 编写与 Windows 深度融合的程序。
2220
23-### 处理文件和文件夹
21+若开发的是运行于 Web 服务器种的软件,则通常会采用一种 Unix 平台。这时可能 Linux、Solaris、*BSD 和其他 Unix 才是有效的部署目标,尽管出于完整性考虑可能会加入对 Windows 的支持。
2422
25-在处理文件和文件夹时,使用于非特定平台的路径分隔符和[[End of Line|行结束]]符很重要。这个列表定义了 Lazarus 处理文件或文件夹时的[[Constant/zh CN|常量]]列表。
23+只要解决了设计阶段的跨平台问题,就可以很大程度上忽略其他平台,就像为单个平台开发一样。不过在某些时候,程序还是需要在其他平台上测试、部署和运行。因此,最好是能自由访问目标操作系统。如果不想使用多台物理计算机,请研究一下双系统引导或虚拟机(VM),比如:
2624
27-- **PathSep**, **PathSeparator**: 多路径分隔符 (';', ...)
28-- **PathDelim**, **DirectorySeparator**: 各平台路径分隔符 ('/', '\\', ...)
29-- **LineEnding**: 行结束符 (#13#10 - CRLF, \#10 - [[Line feed|LF]], ...)
25+ * [Mac 系统中的 VMware Fusion](<https://www.vmware.com/products/fusion.html>) \- 仅运行于 Intel macOS 系统的虚拟主机系统(商用/非商用免费)。
26+ * [VMware Workstation Player](<https://www.vmware.com/products/workstation-player.html>) \- 运行于 Windows 64 位 或 Linux 64 位系统的虚拟主机系统(商用/个人免费)。
27+ * [VMware Workstation Pro](<https://www.vmware.com/products/workstation-pro.html>) \- 运行于 Windows 64 位或 Linux 64 位系统的虚拟主机系统(商用/免费试用)。
28+ * [Mac 系统中的 Parallels Desktop](<https://www.parallels.com/>) \- 仅运行于 Intel macOS 的虚拟主机系统(商用/免费试用)。注意:[Technical Preview](<https://b2b.parallels.com/Apple-Silicon>)将能在 Apple M1 ARM64 处理器上运行,操作系统是 ARM 版 Linux 和 [Windows 10 Insider Preview](<https://www.microsoft.com/en-us/software-download/windowsinsiderpreviewARM64>)。
29+ * [VirtualBox](<https://www.virtualbox.org/>) \- 运行于 Intel macOS、Windows、Linux 和 Solaris 系统的虚拟主机系统(开源)。
3030
31-要特别注意的是文件系统问题。
31+有些虚拟主机系统可轻松安装桌面 Linux 操作系统,只需鼠标单击一下即可,例如 Mac 系统中的 Parallels Desktop。
3232
33-在 Windows 中,文件名通常不区分大小写,但在 Linux 和 BSD 平台将区分。但,如果一个EXT2、EXT3等系统文件挂载到 Windows 中,这仍将区分大小写。Linux 挂载 FAT 文件系统却不区分大小写。
33+**参阅** :[[Small Virtual Machines|小型虚拟主机系统]] 、 [[Qemu and other emulators|Qemu 和其他模拟器]]。
3434
35-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 系统时是区分大小写的。这将会导致各种问题,其中包括文件丢失。~~)
35+## 跨平台编程
3636
37-Mac OS X 默认不区分文件名大小写,这可能是恼人错误的原因,所以任何便携式应用程序都应该坚持文件名大小写统一。
37+### 文件和文件夹的处理
3838
39-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.
39+在处理文件和文件夹时,非常重要的一点就是采用平台无关的路径分隔符和[[End of Line|行结束]]符。Lazarus 声明了以下[[Constant/zh CN|常量]],用于处理文件或文件夹。
4040
41-(~~RTL(Free Pascal 运行库)文件使用系统编码。在 Windows 中是一代码页,在 Linux, BSD 和 Mac OS X 使用 UTF-8,LCL(Lazarus 组件库) 中的 **FileUtil**单元,提供了文件操作的相关功能。~~)
41+ * **PathSep** 、**PathSeparator** :多路径并列时的分隔符(';' 等)
42+ * **PathDelim** 、**DirectorySeparator** :各平台的目录分隔符('/'、'\'等)
43+ * **LineEnding** :行结束符(#13#10 是 CRLF、#10 是 [[Line feed|LF]]等)
4244
43-``` pascal
44-// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, MacOSX
45-// but normally these OS use UTF-8 as system encoding so the widestringmanager
46-// 是不需要的。
47-function NeedRTLAnsi: boolean;// 为真,则系统编码不是 UTF-8
48-procedure SetNeedRTLAnsi(NewValue: boolean);
49-function UTF8ToSys(const s: string): string;// 作为 AnsiToUTF8 需要独立的 widestringmanager
50-function SysToUTF8(const s: string): string;// 作为 AnsiToUTF8 需要独立的 widestringmanager
51-function UTF8ToConsole(const s: string): string;//转换 UTF8 编码字符串到控制台编码(使用 Write, WriteLn)
45+另一个需注意的要点是文件系统是否大小写敏感。 Windows 平台的文件名通常不区分大小写,而 Unix 平台(如 Linux、FreeBSD)通常是区分的,不过承自 Unix 的 macOS 却不区分大小写。但请注意,如果在 Windows 中挂载 EXT2、EXT3 之类的文件系统,仍会区分大小写。同理,在 Linux 中挂载 FAT 文件系统也不应区分大小写。
5246
53-// 文件操作
54-function FileExistsUTF8(const Filename: string): boolean;
55-function FileAgeUTF8(const FileName: string): Longint;
56-function DirectoryExistsUTF8(const Directory: string): Boolean;
57-function ExpandFileNameUTF8(const FileName: string): string;
58-function ExpandUNCFileNameUTF8(const FileName: string): string;
59-function ExtractShortPathNameUTF8(Const FileName : String) : String;
60-function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;
61-function FindNextUTF8(var Rslt: TSearchRec): Longint;
62-procedure FindCloseUTF8(var F: TSearchrec);
63-function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;
64-function FileGetAttrUTF8(const FileName: String): Longint;
65-function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;
66-function DeleteFileUTF8(const FileName: String): Boolean;
67-function RenameFileUTF8(const OldName, NewName: String): Boolean;
68-function FileSearchUTF8(const Name, DirList : String): String;
69-function FileIsReadOnlyUTF8(const FileName: String): Boolean;
70-function GetCurrentDirUTF8: String;
71-function SetCurrentDirUTF8(const NewDir: String): Boolean;
72-function CreateDirUTF8(const NewDir: String): Boolean;
73-function RemoveDirUTF8(const Dir: String): Boolean;
74-function ForceDirectoriesUTF8(const Dir: string): Boolean;
47+NTFS 系统应特别引起注意,在 Windows 中使用时不区分大小写,但在 POSIX 操作系统中挂载时却又区分大小写。这可能会导致**各种问题,包括文件丢失** ,如果 NTFS 分区中存在只是大小写不同的同名文件,挂载到 Windows 时就会丢失文件。开发人员应考虑用自定义函数进行检查,防止在 NTFS 上创建多个同名文件。
7548
76-// 环境
77-function ParamStrUTF8(Param: Integer): string;
78-function GetEnvironmentStringUTF8(Index: Integer): string;
79-function GetEnvironmentVariableUTF8(const EnvVar: string): String;
80-function GetAppConfigDirUTF8(Global: Boolean): string;
49+macOS 文件名默认不区分大小写。这可能会导致烦人的错误,因此任何可移植应用程序在使用文件名时都应该保持前后一致。
8150
82-// 其他
83-function SysErrorMessageUTF8(ErrorCode: Integer): String;
51+RTL 文件函数采用与文件名相同的操作系统字符编码。在 Windows 中就是某个 Windows 代码页,而在 Linux、BSD 和 macOS 中通常采用 UTF-8。LCL 的 **FileUtil** 单元提供了一些文件操作函数,与其他 LCL 代码一样使用 UTF-8 字符串。如下所示:
8452```
53+// 在 Linux、BSD、macOS 中,AnsiToUTF8 和 UTF8ToAnsi 需要 widestring manager 支持。
54+ // 不过这些操作系统通常采用 UTF-8 作为系统编码, 由此也无需用到 widestringmanager。
55+ function NeedRTLAnsi: boolean;// 如果系统编码不是 UTF-8 则返回 true
56+ procedure SetNeedRTLAnsi(NewValue: boolean);
57+ function UTF8ToSys(const s: string): string; // 类似 UTF8ToAnsi 但对 widestringmanager 的依赖较少
58+ function SysToUTF8(const s: string): string; // 类似 AnsiToUTF8 但对 widestringmanager 的依赖较少
59+ function UTF8ToConsole(const s: string): string; // 将 UTF8 字符串转换为控制台编码(Write、WriteLn 会用到)
60+
61+ // 文件操作
62+ function FileExistsUTF8(const Filename: string): boolean;
63+ function FileAgeUTF8(const FileName: string): Longint;
64+ function DirectoryExistsUTF8(const Directory: string): Boolean;
65+ function ExpandFileNameUTF8(const FileName: string): string;
66+ function ExpandUNCFileNameUTF8(const FileName: string): string;
67+ function ExtractShortPathNameUTF8(Const FileName : String) : String;
68+ function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;
69+ function FindNextUTF8(var Rslt: TSearchRec): Longint;
70+ procedure FindCloseUTF8(var F: TSearchrec);
71+ function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;
72+ function FileGetAttrUTF8(const FileName: String): Longint;
73+ function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;
74+ function DeleteFileUTF8(const FileName: String): Boolean;
75+ function RenameFileUTF8(const OldName, NewName: String): Boolean;
76+ function FileSearchUTF8(const Name, DirList : String): String;
77+ function FileIsReadOnlyUTF8(const FileName: String): Boolean;
78+ function GetCurrentDirUTF8: String;
79+ function SetCurrentDirUTF8(const NewDir: String): Boolean;
80+ function CreateDirUTF8(const NewDir: String): Boolean;
81+ function RemoveDirUTF8(const Dir: String): Boolean;
82+ function ForceDirectoriesUTF8(const Dir: string): Boolean;
83+
84+ // 运行环境
85+ function ParamStrUTF8(Param: Integer): string;
86+ function GetEnvironmentStringUTF8(Index: Integer): string;
87+ function GetEnvironmentVariableUTF8(const EnvVar: string): String;
88+ function GetAppConfigDirUTF8(Global: Boolean): string;
89+
90+ // 其他
91+ function SysErrorMessageUTF8(ErrorCode: Integer): String;
92+```
8593
8694### 空文件名和路径双分隔符
8795
88-Windows 和 Linux/Unix/Unix 等系统在处理文件/文件夹名时有差异。
89-
90-- Windows 允许空文件名。这就是为什么 FileExistsUTF8('..\\') 可以检查空文件名的父目录。
91-- 在 Linux/Unix/类Unix系统空文件映射到目录上将被视为文件。这意味着,FileExistsUTF8('../') 将检查父目录,这会返回 真。
96+在处理文件/目录名时,Windows 与 Linux/Unix/类 Unix 系统有不同的做法。
9297
93-文件名的双路径分隔符应该区别对待:
98+ * Windows 允许文件名为空。这就是为什么在 Windows 中 FileExistsUTF8('..\') 可用于检测父目录中是否存在空名文件。
99+ * 在 Linux/Unix/类 Unix 系统中,空文件映射为目录,目录视作文件处理。这意味着在 Unix 中,FileExistsUTF8('../') 将检测父目录是否存在,通常结果都是 true。
94100
95-- Windows:是 'C:\\' 不是 'C:\\\\'
96-- 类Unix系统:路径 '/usr//' 等同于 '/usr/',如果 '/usr' 是目录,则,也等同于上面。
101+对于文件名中的双路径分隔符,处理方式也不相同:
97102
98-连接文件名时,要注意,如:
103+ * Windows:'C:\' 与 'C:\\\' 不同
104+ * 类 Unix 系统:路径 '/usr//' 等同于 '/usr/',如果 '/usr' 是个目录,则也等同于 '/usr//' 和 '/usr/'。
99105
100-``` pascal
101-FullFilename:=FilePath+PathDelim+ShortFilename; // 这会返回 2个 PathDelims ,在 Windows和 Linux 将导致不同结果
102-FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // 仅创建一个 PathDelim
103-FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // 仅创建 1个 PathDelim 并做些清理
106+在拼接文件名时,这一点就很重要了。例如:
104107```
108+FullFilename:=FilePath+PathDelim+ShortFilename; // 可能会生成两个 PathDelims,在 Windows 和 Linux 中会
109+ 给出不同结果
110+ FullFilename:=AppendPathDelim(FilePath) + ShortFilename; // 只会生成一个 PathDelim
111+ FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // 只会生成一个 PathDelim,会再作些清理
112+```
105113
106-TrimFilename 函数将替换双路径分隔符为单分隔符,并缩短 '..' 路径,如,/usr//lib/../src 将修整为 /usr/src 。
114+TrimFilename 函数会将双路径分隔符替换为单个,并缩短 '..' 路径,比如会把 /usr//lib/../src 修整为 /usr/src 。
107115
108-如果想判断一个目录是否可用,使用 **DirectoryExistsUTF8**。
116+若要判断某个目录是否存在,请使用 **DirectoryExistsUTF8** 。
109117
110-使用 ExtractFilePath 可以返回文件名的路径部分,并包含路径定界符。
118+另一常见任务是检查文件名的 path 部分是否存在。ExtractFilePath 可用于获取路径,但会包含路径分隔符。
111119
112-- 在类 Unix 系统上,可以使用 FileExistsUTF8 ,如 FileExistsUTF8('/home/user/') ,如果 /home/user 存在,将返回真。
113-- 在 Windows 下,你必须使用 DirectoryExistsUTF8 函数,但在此之前,你必须删除路径分隔符,如 ChompPathDelim 函数。
120+ * 在类 Unix 系统中,只要用 FileExistsUTF8 即可。比如 FileExistsUTF8('/home/user/') 在 /home/user 存在时会返回 true。
121+ * 在 Windows 中,必须用 DirectoryExistsUTF8 函数,还必须预先删除路径分隔符,比如用 ChompPathDelim 函数。
114122
115-在类Unix系统的根目录是 '/',使用 ChompPathDelim 将创建一个空字符串。DirPathExists 函数工作方式类同于 DirectoryExistsUTF8,将修剪给定的路径。
123+类 Unix 系统的根目录是 '/',用 ChompPathDelim 函数会创建一个空字符串。DirPathExists 函数的工作方式类似于 DirectoryExistsUTF8,只是会对给出的路径作裁剪。
116124
117-需要注意的是,Unix/Linux 上使用 '~' (波浪号)代表用户目录,典型的例子,'/home/jim/' 将调用 jim 用户的文件。因此 '~/myapp/myfile' 和 '/home/jim/myapp/myfile' 在命令行和脚本中相同。然则,Lazarus 不会自动扩展波浪号。这需要使用 ExpandFileNameUTF8('~/myapp/myfile') 来获取完整路径。
125+请注意,Unix/Linux 使用 '~'(波浪号)表示 home 目录,通常用 '/home/jim/'表示名为 jim 的用户。所以在命令行和脚本代码中,'~/myapp/myfile' 和 '/home/jim/myapp/myfile' 是同一个意思。但 Lazarus 不会自动扩展波浪号。因此必须用 ExpandFileNameUTF8('~/myapp/myfile') 获取完整路径。
118126
119127### 文本编码
120128
121-文本文件的编码通常是当前系统的编码。在Windows下,是代码页之一,而在Linux,BSD和Mac OS X,通常是 UTF-8编码。没有 100% 的规则找出文本文件的编码。LCL 的**lconvencoding** 有一个函数可以获得文件编码:
122-
123-``` pascal
129+文本文件通常采用当前系统的编码。在 Windows 中通常是某个代码页,而在 Linux、BSD 和 macOS 中通常采用 UTF-8。没有100%准确的规则能够找出文本文件的编码。LCL 的 **lconvencoding** 单元中有一个猜测文本编码的函数:
130+```
124131function GuessEncoding(const s: string): string;
125-function GetDefaultTextEncoding: string;
132+ function GetDefaultTextEncoding: string;
126133```
127134
128-编码转换:
129-
130-``` pascal
135+其中还包含了一些编码转换函数:
136+```
131137function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;
132-
133-function UTF8BOMToUTF8(const s: string): string; // UTF8 与 BOM
134-function ISO_8859_1ToUTF8(const s: string): string; // 中欧
135-function CP1250ToUTF8(const s: string): string; // 中欧
136-function CP1251ToUTF8(const s: string): string; // 西里尔
137-function CP1252ToUTF8(const s: string): string; // 拉丁语1
138-...
139-function UTF8ToUTF8BOM(const s: string): string; // UTF8 与 BOM
140-function UTF8ToISO_8859_1(const s: string): string; // 中欧
141-function UTF8ToCP1250(const s: string): string; // 中欧
142-function UTF8ToCP1251(const s: string): string; // 西里尔
143-function UTF8ToCP1252(const s: string): string; // 拉丁语1
144-...
138+
139+ function UTF8BOMToUTF8(const s: string): string; // 带 BOM 的 UTF8
140+ function ISO_8859_1ToUTF8(const s: string): string; // 中欧
141+ function CP1250ToUTF8(const s: string): string; // 中欧
142+ function CP1251ToUTF8(const s: string): string; // 西里尔
143+ function CP1252ToUTF8(const s: string): string; // 拉丁语1
144+ ...
145+ function UTF8ToUTF8BOM(const s: string): string; // 带 BOM 的 UTF8
146+ function UTF8ToISO_8859_1(const s: string): string; // 中欧
147+ function UTF8ToCP1250(const s: string): string; // 中欧
148+ function UTF8ToCP1251(const s: string): string; // 西里尔
149+ function UTF8ToCP1252(const s: string): string; // 拉丁语1
150+ ...
145151```
146152
147-如,加载一个文本文件并将其转换为 UTF-8 编码,你可以使用:
148-
149-``` pascal
153+比如要读入文本文件并转换为 UTF-8 编码,可用如下代码:
154+```
150155var
151- sl: TStringList;
152- OriginalText: String;
153- TextAsUTF8: String;
154-begin
155- sl:=TStringList.Create;
156- try
157- sl.LoadFromFile('sometext.txt'); // 注意:此更改系统行行尾
158- OriginalText:=sl.Text;
159- TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);
160- ...
161- finally
162- sl.Free;
163- end;
164-end;
156+ sl: TStringList;
157+ OriginalText: String;
158+ TextAsUTF8: String;
159+ begin
160+ sl:=TStringList.Create;
161+ try
162+ sl.LoadFromFile('sometext.txt'); // 注意:这会把文件的行结束符修改为系统当前的行结束符
163+ OriginalText:=sl.Text;
164+ TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);
165+ ...
166+ finally
167+ sl.Free;
168+ end;
169+ end;
165170```
166171
167-保存文本文件到系统编码可以使用:
168-
169-``` pascal
172+若要以系统编码保存文本文件,可用以下代码:
173+```
170174sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);
171-sl.SaveToFile('sometext.txt');
175+ sl.SaveToFile('sometext.txt');
172176```
173177
174178### 配置文件
175179
176-你可以使用 SysUtils 单元的 [[doc:rtl/sysutils/getappconfigdir.html|GetAppConfigDir]] 函数获取合适的位置存储系统配置文件。该函数有一个参数,称为全局。如果为真,则返回一个全局目录,即适用于系统上的所有用户。如果为参数为假,则为运行该程序的用户配置目录。在不支持多用户环境的系统,这两个目录是相同的。
177-
178-也可以使用 [[doc:rtl/sysutils/getappconfigfile.html|GetAppConfigFile]] 函数,将返回一个合适的配置文件名,你可以这样使用它:
179-
180-ConfigFilePath := GetAppConfigFile(False) + '.conf';
180+SysUtils 单元的 [GetAppConfigDir](<http://lazarus-ccr.sourceforge.net/docs/rtl/sysutils/getappconfigdir.html> "doc:rtl/sysutils/getappconfigdir.html") 函数可获取各平台中的配置文件存放位置。该函数有一个 Global 参数,为 true 则返回全局目录,即适用于系统中的所有用户。如果参数 Global 为 false,则配置文件目录仅对运行该程序的用户有效。在不支持多用户的系统中,上述两个目录可能相同。
181181
182-以下是不同系统默认路径输出的例子:
182+还有一个 [GetAppConfigFile](<http://lazarus-ccr.sourceforge.net/docs/rtl/sysutils/getappconfigfile.html> "doc:rtl/sysutils/getappconfigfile.html") 函数,将返回一个配置文件名,用法如下:
183+```
184+ConfigFilePath := GetAppConfigFile(False);
185+```
183186
184-``` pascal
187+下面是在不同系统中用默认路径输出的示例:
188+```
185189program project1;
186-
187-{$mode objfpc}{$H+}
188-
189-uses
190- SysUtils;
191-
192-begin
193- WriteLn(GetAppConfigDir(True));
194- WriteLn(GetAppConfigDir(False));
195- WriteLn(GetAppConfigFile(True));
196- WriteLn(GetAppConfigFile(False));
197-end.
190+
191+ {$mode objfpc}{$H+}
192+
193+ uses
194+ SysUtils;
195+
196+ begin
197+ WriteLn(GetAppConfigDir(True));
198+ WriteLn(GetAppConfigDir(False));
199+ WriteLn(GetAppConfigFile(True));
200+ WriteLn(GetAppConfigFile(False));
201+ end.
198202```
199203
200-在 GNU/Linux系统上FPC 2.2.2输出,为 TRUE 时会有问题,在 2.2.3 已经修复。
201-
202- /etc/project1/
203- /home/user/.config/project1/
204- /etc/project1.cfg
205- /home/user/.config/project1.cfg
206-
207-你可以看到,全局配置文件存储在 /etc 目录和本地配置存储在用户的主目录中隐藏文件夹。目录中,其名称以点(.)被隐藏在Linux上。你可以使用 GetAppConfigDir 返回的路径以创建目录,然后保存配置文件。
208-
209-> **Note:** 普通用户不允许写入 /etc 目录。只有拥有超级管理员的用户才可以写入。
210-
211-在 Windows XP 上的 FPC 2.2.4上输出:
212-
213- C:\Documents and Settings\All Users\Application Data\project1\
214- C:\Documents and Settings\user\Local Settings\Application Data\project1
215- C:\Documents and Settings\All Users\Application Data\project1\project1.cfg
216- C:\Documents and Settings\user\Local Settings\Application Data\project1\project1.cfg
204+可见全局配置文件存于 /etc 目录中,而当前配置存于用户 home 目录中的隐藏目录。在 UNIX 和类 UNIX 系统中,目录名称以句点(.)开头的是隐藏目录。可在 GetAppConfigDir 返回的目录中创建一个目录,然后将配置文件存于其中。
217205
218-请注意,在 FPC 2.2.4 之前,全局配置在 Windows 目录。
206+![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** 普通用户对 /etc 目录没有写入权限。只有具备管理员权限的用户才行。
219207
220-在 Windows 98 上的 FPC 2.2.0上输出:
208+请注意,在 FPC 2.2.4 以下版本中,GetAppConfigDir 函数用程序所在的目录存储 Windows 全局配置。
221209
222- C:\Program Files\PROJECT1
210+Windows 98 下 FPC 2.2.0 的输出:
211+```
212+C:\Program Files\PROJECT1
223213 C:\Windows\Local Settings\Application Data\PROJECT1
224214 C:\Program Files\PROJECT1\PROJECT1.cfg
225215 C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg
226-
227-在 Mac OS X 上的 FPC 2.2.0上输出:
228-
229- /etc
230- /Users/user/.config/project1
231- /etc/project1.cfg
232- /Users/user/.config/project1/project1.cfg
233-
234-> **Note:** 使用 UPX 会干扰 GetAppConfigDir 和 GetAppConfigFile 函数。
235-
236-> **Note:** 在 Mac OS X中,大多数情况下,配置文件是首选文件,是以 ".plist"结尾的XML文件,存放在 /Library/Preferences 或 ~/Library/Preferences中,应用程序包所用的名字来自 Info.plist 的 "Bundle identifier" 字段。 使用 Carbon 调用 CFPreference... 是实现这一目标最简单的方法。.config 文件在用户目录违反的设计指导。
237-
238-### 数据和资源文件
239-
240-一个常见的问题是应用程序需要的数据文件在哪里存储,像图像、音乐、XML文件、数据库文件、帮助文件等等。不幸的是,它没有跨平台的功能去取得最佳位置和寻找数据文件。解决办法是,在不同平台上使用 IFDEF。
241-
242-#### Windows
243-
244-在Windows上,数据文件不应该存放在应用程序目录 (如 C:\Program Files\\),应该在特定位置(见 [1](http://support.microsoft.com/kb/310294) 在"对应用程序数据进行分类")。Windows Vista 或较新的将强制 (用户只有 elevation 或 禁用 UAC时才可以写访问这些目录) 使用文件夹重定向机制,以适应旧的,错误的编程应用。只读不写数据,应用程序仍然可以正常工作。
245-
246-查看 [[Windows Programming Tips|获取特殊文件夹(我的文档、桌面、本地应用程序数据等)]]。
247-
248-#### Unix/Linux
249-
250-在大多数Unix系统(像Linux, FreeBSD, OpenBSD, Solaris),应用程序数据存储在一个固定的地方,它可以在 /usr/share/app_name 或 /opt/app_name。
251-
252-写入的数据通常存储在 /var/\<programname\>,具有适当的权限。
253-
254-用户特定的 读/写、配置/数据文件,将存储的用户主目录下(如存储在~/.myfancyprogram)。
255-
256-#### OSX
257-
258-Mac OS X是一个异常在UNIX系统。部署应用程序的最佳方法是使用一个应用程序包,其中包括你的软件需要的所有文件。你所谓的资源文件应该位于包内,即便被移动,它仍可以正常工作。您需要使用CoreFoundation API调用找到包的位置。将路径映射到 MyBundle.app/Contents/Resources。
259-
260-#### 示例
261-
262-本节介绍了一种特定解决方案,其中
263-
264-- Windows 下的数据文件与可执行文件存放在一起(或在此基础上的其他目录,像 ResourcesPath + 'data' + PathDelim + 'myfile.dat')
265-- 在 Unix系统中将从配置文件中读取一个目录。如果配置文件不存在或不包含信息,将使用 ('/usr/share/myapp/')做为默认目录。
266-
267-> **Warning:** 基于上面的解释,这段代码在 Windows 上是错误的——除非它是只读访问!
268-
269-通过 GetAppConfigFile 函数来获得配置文件路径。
270-
271-下面是一个完整的单元,可以在你的程序上使用。
272-
273-``` pascal
274-unit appsettings;
275-
276-interface
277-
278-{$ifdef fpc}
279- {$mode delphi}{$H+}
280-{$endif}
281-
282-uses
283- Classes, SysUtils, Forms, IniFiles, constants;
216+```
284217
285-type
218+Windows XP 下 FPC 3.0.4 的输出:
219+```
220+C:\Documents and Settings\All Users\Application Data\project1\
221+ C:\Documents and Settings\user\Local Settings\Application Data\project1\
222+ C:\Documents and Settings\All Users\Application Data\project1\project1.cfg
223+ C:\Documents and Settings\user\Local Settings\Application Data\project1\project1.cfg
224+```
286225
287- { TConfigurations }
226+Windows 7 和 Windows 10 下 FPC 3.0.4 的输出:
227+```
228+C:\ProgramData\project1\
229+ C:\Users\user\AppData\Local\project1\
230+ C:\ProgramData\project1\project1.cfg
231+ C:\Users\user\AppData\Local\project1\project1.cfg
232+```
288233
289- TConfigurations = class(TObject)
290- private
291- function GetResourcesPath: string;
292- public
293- {other settings as fields here}
294- ConfigFilePath: string;
295- ResourcesPath: string;
296- constructor Create;
297- destructor Destroy; override;
298- procedure ReadFromFile(Sender: TObject);
299- procedure Save(Sender: TObject);
300- end;
234+macOS 10.14.5 下 FPC 3.0.4 的输出(不符合 Apple 规则 - 正确的 macOS 配置文件位置请参阅below):
235+```
236+/etc/project1/
237+ /Users/user/.config/project1/
238+ /etc/project1.cfg
239+ /Users/user/.config/project1.cfg
240+```
301241
302-var
303- vConfigurations: TConfigurations;
242+FreeBSD 12.1 下 FPC 3.0.4 的输出:
243+```
244+/etc/project1/
245+ /home/user/.config/project1/
246+ /etc/project1.cfg
247+ /home/user/.config/project1.cfg
248+```
304249
305-implementation
250+请注意 Windows 和非 Windows 系统的输出差异,Windows 系统中
251+```
252+WriteLn(GetAppConfigFile(True));
253+```
306254
307-{$IFDEF Win32}
308-uses
309- Windows;
310-{$ENDIF}
311-{$ifdef Darwin}
312-uses
313- MacOSAll;
314-{$endif}
255+输出的全局配置包含一个子目录,而其他操作系统则没有。若要获得与非 Windows 系统相同的结果,需要多加一个 [[Boolean]]参数:
256+```
257+WriteLn(GetAppConfigFile(True,True));。
258+```
315259
316-const
317- DefaultDirectory = '/usr/share/myapp/';
318- BundleResourcesDirectory = '/Contents/Resources/';
260+![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** UPX 会影响 GetAppConfigDir 和 GetAppConfigFile 函数的使用效果。
319261
320- SectionGeneral = 'General';
321- SectionUnix = 'UNIX';
262+#### macOS
322263
323- IdentResourcesPath = 'ResourcesPath';
264+配置文件大多数情况下就是首选项文件,在 macOS 中则应为“.plist”扩展名结尾的 XML 文件,存于 /Library/Preferences 或 ~/Library/Preferences 中,文件名则取自程序包 [[macOS property list files|Info.plist]] 文件中的“Bundle identifier”字段。 采用用户目录中的 .config 文件违反了 Apple 编程规则。 请参阅[[Locating macOS app support, preferences folders|定位 macOS 应用程序的支持库、首选项文件夹]]一文,了解以 Apple 方式处理配置文件的代码。
324265
325-{ TConfigurations }
266+### 数据和资源文件
326267
327-constructor TConfigurations.Create;
328-begin
329-{$ifdef win32}
330- ConfigFilePath := ExtractFilePath(Application.EXEName) + 'myapp.ini';
331-{$endif}
332-{$ifdef Unix}
333- ConfigFilePath := GetAppConfigFile(False) + '.conf';
334-{$endif}
268+应用程序需用到的数据文件应该存在何处,比如图像、音乐、XML 文件、数据库文件、帮助文件等,这是个十分常见的问题。不幸的是,获取数据文件最优位置的跨平台函数根本不存在。解决方案就是用 IFDEF 为各种平台实现不同的代码。
335269
336- ResourcesPath := GetResourcesPath();
270+#### Windows
337271
338- ReadFromFile(nil);
339-end;
272+在 Windows 中,由程序修改的数据不应存于应用程序目录中(如 C:\Program Files\),而应放在特定位置(可参阅["对应用程序数据进行分类"](<http://support.microsoft.com/kb/310294>)( _链接已失效_ )。Windows Vista 及以上版本会强制如此(用户只能在提权或禁用 UAC 时,才对这些目录具有写入权限),不过也会用文件夹重定向机制来兼容老程序或编码错误程序。对应用程序目录中的数据可以只读(不写),但不推荐。
340273
341-destructor TConfigurations.Destroy;
342-begin
343- Save(nil);
274+简单的用法可以如下所示:
275+```
276+OpDirLocal:= GetEnvironmentVariableUTF8('appdata')+'\MyAppName';
277+```
344278
345- inherited Destroy;
346-end;
279+请参阅[[Windows Programming Tips|Windows 编程技巧 - 获取特殊文件夹]]。
347280
348-procedure TConfigurations.Save(Sender: TObject);
349-var
350- MyFile: TIniFile;
351-begin
352- MyFile := TIniFile.Create(ConfigFilePath);
353- try
354- MyFile.WriteString(SectionUnix, IdentResourcesPath, ResourcesPath);
355- finally
356- MyFile.Free;
357- end;
358-end;
281+#### Unix/Linux
359282
360-procedure TConfigurations.ReadFromFile(Sender: TObject);
361-var
362- MyFile: TIniFile;
363-begin
364- MyFile := TIniFile.Create(ConfigFilePath);
365- try
366- // 在这里你可以从配置文件中读取其他信息
283+在大多数 Unix 系统中(如 Linux、FreeBSD、OpenBSD、Solaris,但 **macOS 除外** ),应用程序的数据文件存于固定的位置,可能是 /usr/share/app_name 或 /opt/app_name。
367284
368-{$ifdef Win32}
369- ResourcesPath := MyFile.ReadString(SectionUnix, IdentResourcesPath,
370-ExtractFilePath(Application.EXEName));
371-{$else}
372- {$ifndef darwin}
373- ResourcesPath := MyFile.ReadString(SectionUnix, IdentResourcesPath,
374-DefaultDirectory);
375- {$endif}
376-{$endif}
377- finally
378- MyFile.Free;
379- end;
380-end;
285+由应用程序写入的数据,通常存于 /usr/local/var/app_name、/usr/local/share/app_name 或 /usr/local/app_name,访问权限会自动设好。
381286
382-function TConfigurations.GetResourcesPath(): string;
383-begin
384-{$ifdef Darwin}
385-var
386- pathRef: CFURLRef;
387- pathCFStr: CFStringRef;
388- pathStr: shortstring;
389-{$endif}
390-begin
391-{$ifdef UNIX}
392-{$ifdef Darwin}
393- pathRef := CFBundleCopyBundleURL(CFBundleGetMainBundle());
394- pathCFStr := CFURLCopyFileSystemPath(pathRef, kCFURLPOSIXPathStyle);
395- CFStringGetPascalString(pathCFStr, @pathStr, 255, CFStringGetSystemEncoding());
396- CFRelease(pathRef);
397- CFRelease(pathCFStr);
287+帮助文件(又名 man)应存于 /usr/local/man/man[手册章节号]/app_name>,访问权限会自动设好。请注意,某些 FreeBSD 和 Linux 手册的章节位置会有不同,或者干脆缺失。
398288
399- Result := pathStr + BundleResourcesDirectory;
400-{$else}
401- Result := DefaultDirectory;
402-{$endif}
403-{$endif}
289+由用户读写的配置文件/数据,将存于用户 home 目录中的某处(比如 ~/.config/<programname>)。请参阅上文[[Multiplatform Programming Guide|配置文件]]。
404290
405-{$ifdef Windows}
406- Result := ExtractFilePath(Application.EXEName);
407-{$endif}
408-end;
291+#### macOS
409292
410-initialization
293+macOS 是 UNIX 操作系统的一个例外。应用程序以 bundle 的形式发布(扩展名为“.app”的目录),文件管理器 Finder 将其视作单个文件(在 Terminal 中可执行“cd path/myapp.app”,或在 Finder 中右键单击应用程序并选择“显示包内容”)。应用程序的资源文件应存于 bundle 中。假定 bundle 为“path/MyApp.app”,则:
411294
412- vConfigurations := TConfigurations.Create;
295+ * 可执行文件为“path/MyApp.app/Contents/MacOS/myapp”
296+ * 资源目录为“path/MyApp.app/Contents/Resources”
413297
414-finalization
298+应用程序的数据和资源文件通常应存于应用程序 bundle 的 Resources 目录中。有关如何以 Apple 兼容方式处理应用程序数据的代码,请参阅文章 [[Locating the macOS application resources directory|定位 macOS 应用程序资源目录]] 。
415299
416- FreeAndNil(vConfigurations);
300+用户生成的数据文件应于用户的“~/Library/Application Support”目录中。有关如何以 Apple 兼容方式处理用户数据的代码,请参阅文章[[Locating macOS app support, preferences folders|定位 macOS 应用程序的支持库、首选项文件夹]]。
417301
418-end.
302+{{Warning|**请勿** 在任何 UNIX 平台中用 `paramStr(0)`
419303```
420-
421-这个代码示例,从正确的位置得到一个资源文件:
422-
423-``` pascal
424-bmp := TBitmap.Create
425-try
426- bmp.LoadFromFile(vConfigurations.ResourcesPath + 'MyBitmap.bmp');
427-finally
428- bmp.Free;
429-end;
304+或任何用到它的函数来 _确定_ 可执行文件的位置,因为这是 DOS-Windows-OS/2 系统的约定,在概念上存在一些问题,且在其他平台上无法模拟解决。在 UNIX 平台中,
430305```
431306
432-### 32/64 位
433-
434-#### 在运行时检测位数
307+`paramStr(0)`
308+```
309+唯一能保证返回的是程序启动时的名称。
310+```
435311
436-虽然你可以控制定义编译 32位或64位编译器,但有时你想知道程序运行在多少位操作系统上。 例如,你在64位的Windows系统上运行 32位的Lazarus 程序,你想在32位程序文件目录运行外部程序,或者你想给用户提供不同的信息:我需要在我的 LazUpdater 程序中为用户提供 32位或64位编译器的选择,如果合适的话。
312+程序所在的目录和二进制文件的真实名称(若以符号链接启动),用 `paramStr(0)` 均无法可靠获得。对于 macOS 而言,可用[[Locating the macOS application resources directory|此文]]中的原生代码**可靠地** 得到应用程序 bundle 目录。
437313
438-对于 Windows,FPC x86 编译器工作在我的 Vista x64上 —— 感谢 [Lazarus 德国论坛](http://www.lazarusforum.de/viewtopic.php?f=55&t=5287):
314+### 32/64 bit
439315
440-``` pascal
441-program bitness;
316+#### 在运行时检测系统位数
442317
443-{$mode objfpc}{$H+}
444-{$APPTYPE CONSOLE}
445-uses {$IFDEF UNIX} {$IFDEF UseCThreads}
446- cthreads, {$ENDIF} {$ENDIF}
447- Classes,
448- SysUtils,
449- Windows;
318+虽然用编译指令 define 可以控制 32 位或 64 位编译,但有时会想知道操作系统的位数情况。 比如在 64 位 Windows 中运行 32 位 Lazarus 程序,可能需要运行一个位于 32 位程序的文件目录中的外部程序,或者可能需向用户显示信息:在 LazUpdater Lazarus 安装程序中,需要让用户选择 32 位和 64 位编译器。这里是代码: [[Detect Windows x32-x64 example|检测 Windows x32-x64 示例]]。
450319
451- function IsWindows64: boolean;
452- {
453- 检测如果运行在 32/64 位Windows,
454- 独立于程序位数。
455- 原始来源:
456- http://www.delphipraxis.net/118485-ermitteln-ob-32-bit-oder-64-bit-betriebssystem.html
457- 修改自 FreePascal 的Lazarus 德国论坛:
458- http://www.lazarusforum.de/viewtopic.php?f=55&t=5287
459- }
460-{$ifdef WIN32} //修改 KpjComp 的64位编译模式
461- type
462- TIsWow64Process = function( // IsWow64Process API 类型
463- Handle: Windows.THandle; var Res: Windows.BOOL): Windows.BOOL; stdcall;
464- var
465- IsWow64Result: Windows.BOOL; // 返回 IsWow64Process
466- IsWow64Process: TIsWow64Process; // IsWow64Process 函数参考
467- begin
468- // 尝试从 kernel32 加载所需功能
469- IsWow64Process := TIsWow64Process(Windows.GetProcAddress(
470- Windows.GetModuleHandle('kernel32'), 'IsWow64Process'));
471- if Assigned(IsWow64Process) then
472- begin
473- // 功能实现:调用它
474- if not IsWow64Process(Windows.GetCurrentProcess, IsWow64Result) then
475- raise SysUtils.Exception.Create('IsWindows64: bad process handle');
476- // 返回函数结果
477- Result := IsWow64Result;
478- end
479- else
480- // 功能未实现:不能运行在 Wow64
481- Result := False;
482-{$else} //如果正在运行的是 64位代码,则系统必须是 64位 :)
483- begin
484- Result := True;
485-{$endif}
486- end;
320+#### 在加载外部库之前检测位数
487321
488-begin
489- try
490- if IsWindows64 then
491- begin
492- writeln('This operating system is 64 bit.');
493- end
494- else
322+若要从动态库加载函数,则动态库必须与应用程序位数相同。在 64 位 Windows 中,应用程序可能是 32 位或 64 位,并且系统中可能同时存在 32 位和 64 位的动态库。因此,在动态加载 dll 之前,可能需要检测 dll 的位数是否与应用程序相同。以下是检测 dll 位数的函数:([由 GetMem 贡献于论坛](<http://forum.lazarus.freepascal.org/index.php/topic,36834.msg245859.html#msg245859>))
323+```
324+uses {..., } JwaWindows;
325+
326+ function GetPEType(const APath: WideString): Byte;
327+ const
328+ PE_UNKNOWN = 0; //if the file is not a valid dll, 0 is returned
329+ // PE_16BIT = 1; // not supported by this function
330+ PE_32BIT = 2;
331+ PE_64BIT = 3;
332+ var
333+ hFile, hFileMap: THandle;
334+ PMapView: Pointer;
335+ PIDH: PImageDosHeader;
336+ PINTH: PImageNtHeaders;
337+ Base: Pointer;
495338 begin
496- writeln('This operating system is 32 bit.');
339+ Result := PE_UNKNOWN;
340+
341+ hFile := CreateFileW(PWideChar(APath), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
342+ if hFile = INVALID_HANDLE_VALUE then
343+ begin
344+ CloseHandle(hFile);
345+ Exit;
346+ end;
347+
348+ hFileMap := CreateFileMapping(hFile, nil, PAGE_READONLY, 0, 0, nil);
349+ if hFileMap = 0 then
350+ begin
351+ CloseHandle(hFile);
352+ CloseHandle(hFileMap);
353+ Exit;
354+ end;
355+
356+ PMapView := MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
357+ if PMapView = nil then
358+ begin
359+ CloseHandle(hFile);
360+ CloseHandle(hFileMap);
361+ Exit;
362+ end;
363+
364+ PIDH := PImageDosHeader(PMapView);
365+ if PIDH^.e_magic <> IMAGE_DOS_SIGNATURE then
366+ begin
367+ CloseHandle(hFile);
368+ CloseHandle(hFileMap);
369+ UnmapViewOfFile(PMapView);
370+ Exit;
371+ end;
372+
373+ Base := PIDH;
374+ PINTH := PIMAGENTHEADERS(Base + LongWord(PIDH^.e_lfanew));
375+ if PINTH^.Signature = IMAGE_NT_SIGNATURE then
376+ begin
377+ case PINTH^.OptionalHeader.Magic of
378+ $10b: Result := PE_32BIT;
379+ $20b: Result := PE_64BIT
380+ end;
381+ end;
382+
383+ CloseHandle(hFile);
384+ CloseHandle(hFileMap);
385+ UnmapViewOfFile(PMapView);
497386 end;
498- except
499- on E: exception do
500- begin
501- writeln('Could not determine bitness of operating system. Error details: ' + E.ClassName+'/'+E.Message);
387+
388+ //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:
389+ function IsCorrectBitness(const APath: WideString): Boolean;
390+ begin
391+ {$ifdef CPU32}
392+ Result := GetPEType(APath) = 2; //the application is compiled as 32-bit, we ask if GetPeType returns 2
393+ {$endif}
394+ {$ifdef CPU64}
395+ Result := GetPEType(APath) = 3; //the application is compiled as 64-bit, we ask if GetPeType returns 3
396+ {$endif}
502397 end;
503- end;
504-end.
505398```
506399
507-#### 指针/整数类型转换
508-
509-指针在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位,并兼容所有平台。这意味着你不能在整型与指针间互转。~~)
400+#### 指针/整数 类型转换
510401
511-FPC 为此定义了两种类型: PtrInt 和 PtrUInt。 PtrInt 在32位平台是32位有符号整数,在64位上是64位有符号整数。PtrUInt同上,不同是无符号。
402+64 位指针要占用 8 个字节,而不是 32 位指针的 4 字节。而为了保持兼容性,Integer 类型在所有平台都保持 32 位。这就意味着,指针类型无法转换为整数,反之亦然。
512403
513-工作在 Delphi 和 FPC 的代码:
404+为此 FPC 定义了两个类型:PtrInt 和 PtrUInt。PtrInt 在 32 位平台上是 32 位有符号整数,在 64 位平台上是 64 位有符号整数。PtrUInt 同理,只是换成无符号整数。
514405
515-``` pascal
406+若要对 Delphi 和 FPC 都能有效,请使用以下代码:
407+```
516408{$IFNDEF FPC}
517-type
518- PtrInt = integer;
519- PtrUInt = cardinal;
520-{$ENDIF}
409+ type
410+ PtrInt = integer;
411+ PtrUInt = cardinal;
412+ {$ENDIF}
521413```
522414
523-替换所有 **integer(指针或对象)** 为 **PtrInt(指针或对象)**。
524-
525-### 字节顺序
526-
527-Intel 平台是little endian(小端字节序),这意味着,首位存放低字节。像,\$1234,存储方式为 \$34 \$12在小端字节序系统中。 在 big endian(大端字节序)系统,像powerpc ,\$1234 存储为 \$12 \$34。读取其他系统上的文件时,此不同处很重要。
415+请将所有 **integer(SomePointerOrObject)** 都替换为 **PtrInt(SomePointerOrObject)** 。
528416
529-参见:[大端、小端的区别(Big-endian or Little-endian)](http://smilejay.com/2012/01/big-endian_little-endian/)
417+### 字节序
530418
531-下面代码在两种方式下工作:
419+Intel 平台是低位优先字节序,这意味着低位字节在前(低地址)。如 $1234 的两个字节会存为 $34 $12。而在 powerpc 这种高位优先的系统中,$1234 的两个字节会存为 $12 $34。在读取其他系统创建的文件时,这是一个重要区别。
532420
533-``` pascal
421+若要适应两种字节序,请使用以下代码:
422+```
534423{$IFDEF ENDIAN_BIG}
535-...
536-{$ELSE}
537-...
538-{$ENDIF}
424+ ...
425+ {$ELSE}
426+ ...
427+ {$ENDIF}
539428```
540429
541-此相反的是ENDIAN_LITTLE。
542-
543-系统单元提供了大量字节序转换功能,像 SwapEndian, BEtoN(大端转换小端)、LEtoN(小端转大端),NtoBE(转换为大端) 和 NtoLE(转换为小端)。
430+而低位优先字节序的编译符则为 ENDIAN_LITTLE。
544431
545-#### Libc和其他特殊单位
432+system 单元中提供了大量的字节序转换函数,如 SwapEndian、BEtoN(高位优先到当前字节序)、LEtoN(低位优先到当前字节序)、NtoBE(当前字节序到高位优先)和 NtoLE(当前字节序到低位优先)。
546433
547-避免使用像"oldlinux" 和 "libc" 的单元,因为其在 linux/i386 以外不被支持。
434+#### Libc 和其他特殊单元
548435
549-#### 汇编
436+请避免使用“oldlinux” 和 “libc” 之类在 linux/i386 之外不受支持的过时单元。
550437
551-避免汇编。
438+#### Assembler
552439
553-#### 编译器定义
440+请避免使用 [[Assembly language|汇编语言]]。
554441
555-``` pascal
442+#### 编译器 define 指令
443+```
556444{$ifdef CPU32}
557-...这里写34位处理器代码
558-{$ENDIF}
559-{$ifdef CPU64}
560-...这里写64位处理器代码
561-{$ENDIF}
445+ ...编写运行于 32 位处理器下的代码
446+ {$ENDIF}
447+ {$ifdef CPU64}
448+ ...编写运行于 64 位处理器下的代码
449+ {$ENDIF}
562450```
563451
564452### 项目、包和搜索路径
565453
566-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.
567-
568-Some advice to achieve this
454+Lazarus 项目和软件包已设计为可跨平台编译。通常只要将项目和所需软件包复制到别的机器上,即可进行编译了。无需为每个平台分别创建项目。
569455
570-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.
456+下面给出一些跨平台编译的建议。
571457
572-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: [[Lazarus Packages|Creating a package for your common units]]
458+编译器会为每个单元创建同名 ppu 文件,以供其他项目和软件包使用。单元的源码文件(如 unit1.pas)不应在多个项目和软件包之间共用。只要向编译器给出创建 ppu 文件的单元输出目录即可。IDE 默认会执行此操作,因此无需执行任何操作。
573459
574-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.
460+每个单元文件都必须属于 **某个** 项目或软件包。如果单元文件只有一个项目用到了,请将其加入项目中。否则,请将其加入某个软件包。如果尚未为单元建立软件包,请参阅:[[Lazarus Packages|为公共单元创建软件包]]。
575461
576-#### 特定于平台的单元
462+每个项目和软件包都应位于**独立** 的目录 - 相互之间不应共用目录。不然,您就得吃透编译器的搜索路径。如果您不够熟练,或者使用项目/软件包的人不够熟练,请不要在项目/软件包之间共享目录。
577463
578-For example the unit wintricks.pas should only be used under Windows. In the uses section use:
464+#### 平台依赖的单元
579465
580-``` pascal
466+如单元 wintricks.pas 只能在 Windows 下使用。请在 uses 部分采用以下方式声明:
467+```
581468uses
582- Classes, SysUtils
583- {$IFDEF Windows}
584- ,WinTricks
585- {$ENDIF}
586- ;
469+ Classes, SysUtils
470+ {$IFDEF Windows}
471+ ,WinTricks
472+ {$ENDIF}
473+ ;
587474```
588475
589-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.
476+如果单元属于某个软件包,则还得在包编辑器中选中单元并不要勾选 _Use unit_ 。
590477
591-See also [[Lazarus Packages|Platform specific units]]
478+请参阅[[Lazarus Packages|平台依赖的单元]]
592479
593-#### 特定平台搜索路径
480+#### 平台依赖的搜索路径
594481
595-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:
482+若跨平台应用需直接访问操作系统,没完没了的 IFDEF 结构很快会让人厌倦不堪。FPC 和 Lazarus 源代码常用的一种解决方案,就是使用包含文件。先为每种平台都创建一个子目录,如 win32、linux、bsd、darwin。再在每个目录中放入文件名相同的包含文件。然后在 include 路径中使用宏。单元代码中则可以使用常规的 include 指令。
596483
597-Create one file for each widget set you want to support:
484+以下是某 LCL 部件的 include 示例:
598485
599-```text
486+为每种 LCL 部件创建 include 文件:
487+```
600488win32/example.inc
601-gtk/example.inc
602-gtk2/example.inc
603-carbon/example.inc
489+ gtk/example.inc
490+ gtk2/example.inc
491+ carbon/example.inc
604492```
605493
606-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.
607-
608-In your unit use the directive: {\$I example.inc}
494+这些 include 文件不用加入软件包或项目中。 在软件包或项目的编译器选项中,加入包含文件搜索路径 _$(LCLWidgetType)_ 。
609495
610-Here are some useful macros and common values:
496+在单元代码中采用如下指令即可:{$I example.inc}
611497
612-- LCLWidgetType: win32, gtk, gtk2, qt, carbon, fpgui, nogui
613-- TargetOS: linux, win32, win64, wince, freebsd, netbsd, openbsd, darwin (many more)
614-- TargetCPU: i386, x86_64, arm, powerpc, sparc
615-- SrcOS: win, unix
498+以下给出一些常用的宏及常见值:
616499
617-You can use the \$Env() macro to use environment variables.
500+ * LCLWidgetType:win32、 gtk、gtk2、qt、carbon、fpgui、nogui
501+ * TargetOS:linux、win32、win64、wince、freebsd、netbsd、openbsd、darwin(等等)
502+ * TargetCPU:i386、x86_64、arm、powerpc、sparc
503+ * SrcOS:win、unix
618504
619-And of course you can use combinations. For example the LCL uses:
505+$Env() 可用于引用环境变量。
620506
621-```text
507+当然可以组合使用这些宏。比如 LCL 就用到了:
508+```
622509$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)
623510```
624511
625-See here the complete list of macros: [[IDE Macros in paths and filenames]]
512+完整的宏清单请参阅: [[IDE Macros in paths and filenames|IDE 中的路径和文件名宏]]
626513
627-#### 计算机/用户的特定搜索路径
514+#### 依赖于计算机/用户的搜索路径
628515
629-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.
516+假设现有两台 Windows 计算机 stan 和 oliver。stan 中的单元文件位于 _C:\units_ ,而 oliver 中的单位文件位于 _D:\path_ 。这些单元文件都属于软件包 SharedStuff,在 stan 中是 _C:\units\sharedstuff.lpk_ ,而在 oliver 中则是 _D:\path\sharedstuff.lpk_ 。 只要 IDE 或 lazbuild 打开过 lpk 之后,其路径就会自动存于配置文件(packagefiles.xml) 中。当编译用到 _SharedStuff_ 包的项目时,IDE 和 lazbuild 就能找到这个包。这时就无需什么配置文件。
630517
631-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.
518+若要在多台机器或为一台机器中的多个用户(如多个学生)部署软件包,那么可在 lazarus 源代码目录中加入一个 lpl 文件。相关示例请参阅 packager/globallinks。
632519
633-### 区域差异
520+### 地区差异
634521
635-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.
522+在 Free Pascal 中,有些函数会根据当前地区的不同产生不同的结果,例如 StrToFloat。比如在美国,小数分隔符通常是“.”,但在很多欧洲和南美国家/地区则是“,”。这可能会是个问题,因为有时希望这些函数能以固定的方式运行,不要依赖于地区。例如,包含小数点的文件格式就应始终以同一方式进行解析。
636523
637-The next sections explain how to do that.
524+下面几节内容给出了具体做法。
638525
639-#### StrToFloat
526+#### macOS
640527
641-A new set of format settings which set a fixed decimal separator can be created with the following code:
528+关于 macOS 系统中地区设置的细节,请参阅文章 [[Locale settings for macOS]]。
642529
643-``` pascal
530+#### StrToFloat
531+
532+以下代码可创建将小数分隔符固定的格式设置:
533+```
644534// in your .lpr project file
645-uses
646-...
647-{$IFDEF UNIX}
648-clocale
649-{ required on Linux/Unix for formatsettings support. Should be one fo the first (probably after cthreads?}
650-{$ENDIF}
535+ uses
536+ ...
537+ {$IFDEF UNIX}
538+ clocale
539+ { required on Linux/Unix for formatsettings support. Should be one of the first (probably after cthreads?}
540+ {$ENDIF}
651541```
652542
653-and:
654-
655-``` pascal
543+和:
544+```
656545// in your code:
657-var
658- FPointSeparator, FCommaSeparator: TFormatSettings;
659-begin
660- // Format seetings to convert a string to a float
661- FPointSeparator := DefaultFormatSettings;
662- FPointSeparator.DecimalSeparator := '.';
663- FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
664- FCommaSeparator := DefaultFormatSettings;
665- FCommaSeparator.DecimalSeparator := ',';
666- FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator
546+ var
547+ FPointSeparator, FCommaSeparator: TFormatSettings;
548+ begin
549+ // Format settings to convert a string to a float
550+ FPointSeparator := DefaultFormatSettings;
551+ FPointSeparator.DecimalSeparator := '.';
552+ FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
553+ FCommaSeparator := DefaultFormatSettings;
554+ FCommaSeparator.DecimalSeparator := ',';
555+ FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator
667556```
668557
669-Latter on you can use this format settings when calling StrToFloat, like this:
670-
671-``` pascal
558+后续在调用 StrToFloat 时即可使用上述格式设置,如下所示:
559+```
672560// This function works like StrToFloat, but simply tries two possible decimal separator
673-// This will avoid an exception when the string format doesn't match the locale
674-function AnSemantico.StringToFloat(AStr: string): Double;
675-begin
676- if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator)
677- else Result := StrToFloat(AStr, FCommaSeparator);
678-end;
561+ // This will avoid an exception when the string format doesn't match the locale
562+ function AnSemantico.StringToFloat(AStr: string): Double;
563+ begin
564+ if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator)
565+ else Result := StrToFloat(AStr, FCommaSeparator);
566+ end;
679567```
680568
681-### GTK2 和屏蔽 FPU 异常
682-
683-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.
569+### GTK2 和 FPU 异常的屏蔽
684570
685-After [this forum topic](http://www.lazarus.freepascal.org/index.php/topic,13460.0.html) and answers on [this bug report](http://bugs.freepascal.org/view.php?id=19674) it became clear that nothing can be done about this, so we must know what actually these differences are.
571+Gtk2 库更改了 FPU (浮点单元) 对异常的默认屏蔽。因此用到 Gtk2 库的应用程序会无法触发一些浮点数类的异常。假如在 Windows 中开发采用了 win32/64 控件(Windows 默认)的 LCL 应用程序,并打算在 Linux 下也要能编译(默认控件是 Gtk2),就应牢记这些不兼容的地方。
686572
687-Therefore, let's do a test:
573+在 [此论坛主题](<http://www.lazarus.freepascal.org/index.php/topic,13460.0.html>)和[缺陷报告](<http://bugs.freepascal.org/view.php?id=19674>)中有所答复,但显然这是无解的,因此我们必须清楚真正的差别是什么。
688574
689-``` pascal
575+那就做个测试吧:
576+```
690577uses
691- ..., math,...
692-
693-{...}
694-
695-var
696- FPUException: TFPUException;
697- FPUExceptionMask: TFPUExceptionMask;
698-begin
699- FPUExceptionMask := GetExceptionMask;
700- for FPUException := Low(TFPUException) to High(TFPUException) do begin
701- write(FPUException, ' - ');
702- if not (FPUException in FPUExceptionMask) then
703- write('not ');
704-
705- writeln('masked!');
706- end;
707- readln;
708-end.
578+ ..., math,...
579+
580+ {...}
581+
582+ var
583+ FPUException: TFPUException;
584+ FPUExceptionMask: TFPUExceptionMask;
585+ begin
586+ FPUExceptionMask := GetExceptionMask;
587+ for FPUException := Low(TFPUException) to High(TFPUException) do begin
588+ write(FPUException, ' - ');
589+ if not (FPUException in FPUExceptionMask) then
590+ write('not ');
591+
592+ writeln('masked!');
593+ end;
594+ readln;
595+ end.
709596```
710597
711-Our simple program will get what FPC default is:
712-
713-```text
598+这个简单的程序会返回 FPC 默认的屏蔽设置:
599+```
714600exInvalidOp - not masked!
715-exDenormalized - masked!
716-exZeroDivide - not masked!
717-exOverflow - not masked!
718-exUnderflow - masked!
719-exPrecision - masked!
601+ exDenormalized - masked!
602+ exZeroDivide - not masked!
603+ exOverflow - not masked!
604+ exUnderflow - masked!
605+ exPrecision - masked!
720606```
721607
722-However, with Gtk2, only exOverflow is not masked.
723-
724-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:
608+但使用 Gtk2 时只有 exOverflow 未被屏蔽。
725609
726-``` pascal
610+因此,如果应用程序链接了 Gtk2 库,就不会触发 EInvalidOp 和 EZeroDivide 异常!通常把非零值除以零就会引发 EZeroDivide 异常,让零除以零则会引发 EInvalidOp 异常。比如以下代码:
611+```
727612var
728- X, A, B: Double;
729-// ...
730-
731-try
732- X := A / B;
733- // code block 1
734-except
735- // code block 2
736-end;
737-// ...
613+ X, A, B: Double;
614+ // ...
615+
616+ try
617+ X := A / B;
618+ // 代码块 1
619+ except
620+ // 代码块 2
621+ end;
622+ // ...
738623```
739624
740-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](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.
741-
742-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:
625+在采用 Gtk2 控件进行编译时,程序的走向将会不一样。采用 Windows 控件时,若 B 等于零会触发异常(EZeroDivide 或 EInvalidOp,取决于 A 是否为零),于是会执行“代码块 2”。在用 Gtk2 时,X 变为 [Infinity](<http://www.freepascal.org/docs-html/rtl/math/infinity.html>)、[NegInfinity](<http://www.freepascal.org/docs-html/rtl/math/neginfinity.html>) 或 [NaN](<http://www.freepascal.org/docs-html/rtl/math/nan.html>),并且会执行“代码块 1”。
743626
744-``` pascal
627+这种不一致的情况可用各种方法克服。大多数情况下可以先检测一下 B,等于零就不再继续作除法了。但有时还需要采用一些其他方法。请看以下示例:
628+```
745629uses
746- ..., math,...
747-
748-//...
749-var
750- X, A, B: Double;
751- Ind: Boolean;
752-// ...
753-try
754- X := A / B;
755- Ind := IsInfinite(X) or IsNan(X); // with gtk2, we fall here
756-except
757- Ind := True; // in windows, we fall here when B equals zero
758-end;
759-if Ind then begin
760- // code block 2
761-end else begin
762- // code block 1
763-end;
764-// ...
630+ ..., math,...
631+
632+ //...
633+ var
634+ X, A, B: Double;
635+ Ind: Boolean;
636+ // ...
637+ try
638+ X := A / B;
639+ Ind := IsInfinite(X) or IsNan(X); // 用 gtk2 时会执行至此
640+ except
641+ Ind := True; // 用 windows 控件,B 为零时会执行至此
642+ end;
643+ if Ind then begin
644+ // 代码块 2
645+ end else begin
646+ // 代码块 1
647+ end;
648+ // ...
765649```
766650
767-Or:
768-
769-``` pascal
651+或者可以:
652+```
770653uses
771- ..., math,...
772-
773-//...
774-var
775- X, A, B: Double;
776- FPUExceptionMask: TFPUExceptionMask;
777-// ...
778-
779-try
780- FPUExceptionMask := GetExceptionMask;
781- SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // unmask
782- try
783- X := A / B;
784- finally
785- SetExceptionMask(FPUExceptionMask); // return previous masking immediately, we must not let Gtk2 internals to be called without the mask
786- end;
787- // code block 1
788-except
789- // code block 2
790-end;
791-// ...
654+ ..., math,...
655+
656+ //...
657+ var
658+ X, A, B: Double;
659+ FPUExceptionMask: TFPUExceptionMask;
660+ // ...
661+
662+ try
663+ FPUExceptionMask := GetExceptionMask;
664+ SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // 去除屏蔽
665+ try
666+ X := A / B;
667+ finally
668+ SetExceptionMask(FPUExceptionMask); // 立即返回之前的屏蔽设置,不允许在异常屏蔽未设置的情况下调用 Gtk2 内部函数
669+ end;
670+ // 代码块 1
671+ except
672+ // 代码块 2
673+ end;
674+ // ...
792675```
793676
794-Be cautious, do not do something like this (call LCL with still removed mask):
795-
796-``` pascal
677+请小心,不要做类似以下的操作(移除异常屏蔽的情况下调用 LCL):
678+```
797679try
798- FPUExceptionMask := GetExceptionMask;
799- SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
800- try
801- 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!
802- finally
803- SetExceptionMask(FPUExceptionMask);
804- end;
805- // code block 1
806-except
807- // code block 2
808-end;
809-// ...
680+ FPUExceptionMask := GetExceptionMask;
681+ SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
682+ try
683+ Edit1.Text := FloatToStr(A / B); // 不行!设置 Edit 的文本会深入调用控件的内部代码,不能在异常屏蔽没有设置的情况下调用 Gtk2 API!
684+ finally
685+ SetExceptionMask(FPUExceptionMask);
686+ end;
687+ // 代码块 1
688+ except
689+ // 代码块 2
690+ end;
691+ // ...
810692```
811693
812-But use an auxiliary variable:
813-
814-``` pascal
694+而应使用辅助变量:
695+```
815696try
816- FPUExceptionMask := GetExceptionMask;
817- SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
818- try
819- X := A / B; // First, we set auxiliary variable X
820- finally
821- SetExceptionMask(FPUExceptionMask);
822- end;
823- Edit1.Text := FloatToStr(X); // Now we can set Edit's text.
824- // code block 1
825-except
826- // code block 2
827-end;
828-// ...
697+ FPUExceptionMask := GetExceptionMask;
698+ SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
699+ try
700+ X := A / B; // First, we set auxiliary variable X
701+ finally
702+ SetExceptionMask(FPUExceptionMask);
703+ end;
704+ Edit1.Text := FloatToStr(X); // Now we can set Edit's text.
705+ // code block 1
706+ except
707+ // code block 2
708+ end;
709+ // ...
829710```
830711
831-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.
832-
833-## 从 Windows 迁移到 *nix 的问题
712+在开发 LCL 应用程序时,任何时候最要紧的就是要清楚上述差异,牢记不同的控件可能会以不同方式执行某些浮点运算。当然可以考虑采用某种合适的方案来解决,但这个问题不应被忽视。
834713
835-Issues specific to Linux, OSX, Android and other Unixes are described here. Not all subjects may apply to all platforms
714+## 从 Windows 迁移到 *nix 等系统时的问题
836715
837-### 在Unix上没有“应用程序目录”
716+本节讨论了与 Linux、macOS、Android 和其他 Unix 相关的一些主题。不一定适用于所有平台。
838717
839-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).
718+### Unix 中没有“应用程序目录”的概念
840719
841-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).
720+许多程序员习惯调用 ExtractFilePath(ParamStr(0)) 或 Application.ExeName 来获取可执行文件的位置,然后根据可执行文件的位置搜索程序执行所需的文件(图像、XML 文件、数据库文件等)。这在 unix 系统中是不行的。ParamStr(0) 返回的可能不是程序执行时所在的目录,不同的 shell (sh、bash 等)还会返回不同的结果。
842721
843-To avoid this read the sections about [[Multiplatform Programming Guide|configuration files]] and [[Multiplatform Programming Guide|data files]].
722+即便 Application.ExeName 实际反映了可执行文件所在的目录,这个可执行文件也可能只是个符号链接,因此得到的可能只是符号链接所在的目录(取决于 Linux 内核的版本,可能是符号链接的目录或程序二进制文件的目录)。
844723
845-### Windows COM 没有自动化
724+若要避免上述问题,请参阅[[Multiplatform Programming Guide|配置文件]]和 [[Multiplatform Programming Guide|数据文件]]。
846725
847-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](http://wiki.lazarus.freepascal.org/Office_Automation#Using_COM_Automation_to_interact_with_OpenOffice_and_Microsoft_Office).
726+### 平替 Windows COM 自动化
848727
849-#### 替代 OSX
728+Windows 中的 COM 自动化是个强大的功能,不仅可以远程操控其他程序,还允许被其他程序操控。在 Delphi 中可以让应用程序既是 COM 自动化客户端又是服务端,这意味着可以与其他程序实现双向操控。 请参阅使用 COM 自动化与 OpenOffice 和 Microsoft Office 交互。示例请参阅[[Office Automation|利用 COM 自动化实现与 OpenOffice 和 Microsoft Office 的交互]]。
850729
851-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.
730+#### macOS 中的替代方案
852731
853-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):
732+遗憾的是,macOS 和 Linux 不支持 COM 自动化。不过可用 AppleScript 在 macOS 中模拟 COM 自动化的某些功能。
854733
855-``` pascal
856- tell application "NeoOffice"
857- launch
858- end tell
734+AppleScript 在某些方面类似于 COM 自动化。比如可以编写操纵其他程序的脚本。下面是一个非常简单的 AppleScript 示例,启动 NeoOffice(OpenOffice.org 的 Mac 版本):
859735```
860-
861-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:
862-
863-```text
864- Shell('myscript.applescript');
736+tell application "NeoOffice"
737+ launch
738+ end tell
865739```
866740
867-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:
868-
869-``` pascal
870- Shell('osascript -e '#39'tell application "NeoOffice"'#39 +
871- ' -e '#39'launch'#39' -e '#39'end tell'#39);
872- {Note use of #39 to single-quote the parameters}
741+应用程序若支持 AppleScript 操纵,就提供了可供调用的类和命令“字典”,类似于 Windows 自动化服务程序的类。即便是 NeoOffice 这类不提供字典的应用程序,仍然会响应命令 “launch”、“activate” 和 “quit”。AppleScript 可由 macOS 脚本编辑器或 Finder 运行 ,甚至还能转换为与普通 App 一样可拖放至 Dock 的 App。从程序中也可以运行 AppleScript,如下例所示:
873742```
874-
875-However, these examples are just the equivalent of the following Open command:
876-
877-```text
878- Shell('open -a NeoOffice');
743+Shell('myscript.applescript');
879744```
880745
881-Similarly, in OS X you can emulate the Windows shell commands to launch a web browser and launch an email client with:
882-
883-```text
884- fpsystem('open -a safari "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');
746+这里假定脚本位于给出的文件中。还可以在应用程序中用 macOS OsaScript 命令动态执行脚本:
885747```
886-
887-and
748+Shell('osascript -e '#39'tell application "NeoOffice"'#39 +
749+ ' -e '#39'launch'#39' -e '#39'end tell'#39);
750+ {注意用 #39 为参数裹上引号}
751+```
888752
889-```text
890- fpsystem('open -a mail "mailto:ss4200@invalid.org"');
753+当然上述例子相当于以下 Open 命令:
891754```
755+Shell('open -a NeoOffice');
756+```
892757
893-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:
758+同理,在 macOS 中可以模拟 Windows shell 命令,以启动 Web 浏览器和电子邮件客户端:
759+```
760+fpsystem('open -a safari "<http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html>"');
761+```
894762
895-```text
896- fpsystem('open "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');
763+和
897764```
765+fpsystem('open -a mail "[mailto:ss4200@invalid.org](<mailto:ss4200@invalid.org>)"');
766+```
898767
899-and
768+这里想当然假设 macOS 系统安装了 Safari 和 Mail 应用。当然,永远不应做出这种假设,其实上述两个示例可以靠 macOS 来完成,可以换成以下方式来调用当前用户的默认 Web 浏览器和电子邮件客户端:
769+```
770+fpsystem('open "<http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html>"');
771+```
900772
901-```text
902- fpsystem('open "mailto:ss4200@invalid.org"');
773+和
903774```
775+fpsystem('open "[mailto:ss4200@invalid.org](<mailto:ss4200@invalid.org>)"');
776+```
904777
905-Do not forget to include the Unix unit in your uses clause if you use `fpsystem` or `shell` (interchangeable).
778+如果用了 `fpsystem` 或 `shell`(可互替),请不要忘记在 uses 中包含 Unix 单元。
906779
907-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.
780+AppleScript 的真正强大之处在于,能够远程操纵程序创建和打开文档,并自动执行其他一些功能。到底能对程序执行多少操纵,取决于其 AppleScript 词典(若存在的话)。比如用 AppleScript 就不大好操纵 Microsoft 的 Office X 程序,而较新的 Office 2004 已全部重写了 AppleScript 字典,很多方面都可与 Windows Office Automation 服务的字典相提并论了。
908781
909-#### Linux 替代品
782+#### Linux 中的替代方案
910783
911-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).
784+虽然 Linux shell 支持复杂的命令行脚本,但仅限于可传给命令行程序的类型。在 Linux 中,没有一种统一的方式可像 Windows COM 自动化 和 macOS AppleScript 那样访问程序的内部类和命令。但各种桌面环境(GNOME/KDE)和应用程序框架,常会提供这种进程间通信的方法。在 GNOME 上,请参阅 Bonobo 组件。KDE 拥有 KParts 框架 DCOP。OpenOffice 有一个平台无关的 API 用于远程控制(google OpenOffice SDK)- 尽管可能得用另一种可作绑定的语言(如 Python)编写胶水代码。此外,某些应用程序还具备由特殊命令行选项激活的“服务器模式”,以便接受其他进程的控制。还有一种可能,也可以用 XReparentWindow(我认为是)将一个顶层 X 应用窗口“嵌入”另一个窗口中(Borland 就是如此调用 Kylix 文档浏览器的)。
912785
913-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.
786+与 Windows 一样,很多 macOS 和 Linux 程序都包含多个运行库文件(.dylib 和 .so 扩展名)。有时这些库会设计成可供其他程序调用。虽然这可以是在程序中加入外部程序某些功能的一种方式,但与运行及操纵外部程序并不完全相同。而只是链接并调用外部程序的库,与调用其他编程库无异。
914787
915-### Windows API 函数的替代品
788+### Windows API 函数的替代方案
916789
917-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} ).
790+许多 Windows 程序大量用到了 Windows API。在跨平台应用程序中,不应再调用 Windows 单元的 Win API 函数了,或者应该用条件编译指令(如 {$IFDEF MSWINDOWS})包裹起来。
918791
919-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.
792+幸运的是,很多常用 Windows API 函数已在 [[lclintf]] 单元中以跨平台方式实现了。对于严重依赖 Windows API 的程序而言,这可能会是一种解决方案,尽管最好的解决方案是替换为 LCL 提供的真正跨平台的组件。比如可将 GDI 绘制函数替换为 TCanvas 对象方法。
920793
921-### 关键代码
794+### 键盘码
922795
923-Fortunately, detecting key codes (e.g. on KeyUp events) is portable: see [[LCL Key Handling]].
796+幸运的是,键盘按键编码检测方法(如 KeyUp 事件中的)是可移植的:请参阅 [[LCL Key Handling]]。
924797
925-### 安装跨平台应用
798+### 安装跨平台应用程序
926799
927-See [[Deploying Your Application]]
800+请参阅 [[Deploying Your Application|发布应用程序]]。
928801
929802## 参见
930803
931-- [[Writing portable code regarding the processor architecture]]
932-- [[Introduction to platform-sensitive development]]
933-- <http://www.midnightbeach.com/KylixForDelphiProgrammers.html> A guide for Windows programmers starting with Kylix. Many of concepts / code snippets apply to Lazarus.
934-- <http://www.stack.nl/~marcov/porting.pdf> A guide for writing portable source code, mainly between different compilers.
804+ * [[Writing portable code regarding the processor architecture|编写处理器架构无关的可移植代码]]
805+ * [[Introduction to platform-sensitive development|简介平台敏感型开发]]
806+ * [[Apple-specific UI elements|Apple 特有的 UI 元素]]
935807
808+## 外部链接
809+
810+ * <http://www.midnightbeach.com/KylixForDelphiProgrammers.html> Windows 程序员的 Kylix 入门,很多概念和代码都适用于 Lazarus。
811+ * <http://www.stack.nl/~marcov/porting.pdf> 可移植代码编写指南,主要关于各种编译器。
812+
936813---
937-*From the Free Pascal wiki, page "Multiplatform Programming Guide/zh CN", as it stood on 2020-02-20 after a change by Trev, 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).*
814+*From the Free Pascal wiki, page "Multiplatform Programming Guide/zh CN", as the Internet Archive kept it on 2025-02-11, last changed on 2024-09-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