Multiplatform Programming Guide/zh CN: what changed
Revision 3 to 4. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | - | ||
| 2 | - | ||
| 3 | - | ||
| 1 | + | # 跨平台编程指南 | |
| 4 | 2 | ||
| 5 | - | ||
| 3 | + | 本文是用 Lazarus 和 Free Pascal 编写跨平台应用程序的教程,涵盖了必要的注意事项,有助于创建可跨平台[[Deploying Your Application|deploy]]的程序。 | |
| 6 | 4 | ||
| 7 | - | ||
| 5 | + | 大多数 [[LCL]] 应用程序都可以跨平台,无需任何额外操作。 这一原则名为“[[Write once compile anywhere|一次编写、到处编译]]”。 | |
| 8 | 6 | ||
| 9 | - | ||
| 7 | + | ||
| 10 | 8 | ||
| 11 | - | ||
| 9 | + | ## 跨平台编程简介 | |
| 12 | 10 | ||
| 13 | - | ||
| 11 | + | ### 需要多平台运行吗? | |
| 14 | 12 | ||
| 15 | - | ||
| 13 | + | 要回答这个问题,首先应确定应用程序的潜在用户和使用方式。应用程序的部署环境决定了这个问题的答案。 | |
| 16 | 14 | ||
| 17 | - | ||
| 15 | + | 如果是在 2014 年开发通用桌面软件,微软的 Windows 可能是最重要的平台。请注意,Mac OS X 和 Linux 正越来越受到欢迎,或许会成为应用程序的重要部署目标。 | |
| 18 | 16 | ||
| 19 | - | ||
| 17 | + | 因国家/地区、软件类型、受众的不同,各种桌面操作系统的流行程度也各不相同,没有通用的规则。例如 macOS 在北美和西欧相当受欢迎,而在南美则大多只用于处理视频和音频。 | |
| 20 | 18 | ||
| 21 | - | ||
| 19 | + | 在许多签约项目里,只与一个平台相关。Free Pascal 和 Lazarus 足以编写某个指定平台的软件,比如访问全部 Windows API 编写与 Windows 深度融合的程序。 | |
| 22 | 20 | ||
| 23 | - | ||
| 21 | + | 若开发的是运行于 Web 服务器种的软件,则通常会采用一种 Unix 平台。这时可能 Linux、Solaris、*BSD 和其他 Unix 才是有效的部署目标,尽管出于完整性考虑可能会加入对 Windows 的支持。 | |
| 24 | 22 | ||
| 25 | - | ||
| 23 | + | 只要解决了设计阶段的跨平台问题,就可以很大程度上忽略其他平台,就像为单个平台开发一样。不过在某些时候,程序还是需要在其他平台上测试、部署和运行。因此,最好是能自由访问目标操作系统。如果不想使用多台物理计算机,请研究一下双系统引导或虚拟机(VM),比如: | |
| 26 | 24 | ||
| 27 | - | ||
| 28 | - | ||
| 29 | - | ||
| 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 系统的虚拟主机系统(开源)。 | |
| 30 | 30 | ||
| 31 | - | ||
| 31 | + | 有些虚拟主机系统可轻松安装桌面 Linux 操作系统,只需鼠标单击一下即可,例如 Mac 系统中的 Parallels Desktop。 | |
| 32 | 32 | ||
| 33 | - | ||
| 33 | + | **参阅** :[[Small Virtual Machines|小型虚拟主机系统]] 、 [[Qemu and other emulators|Qemu 和其他模拟器]]。 | |
| 34 | 34 | ||
| 35 | - | ||
| 35 | + | ## 跨平台编程 | |
| 36 | 36 | ||
| 37 | - | ||
| 37 | + | ### 文件和文件夹的处理 | |
| 38 | 38 | ||
| 39 | - | ||
| 39 | + | 在处理文件和文件夹时,非常重要的一点就是采用平台无关的路径分隔符和[[End of Line|行结束]]符。Lazarus 声明了以下[[Constant/zh CN|常量]],用于处理文件或文件夹。 | |
| 40 | 40 | ||
| 41 | - | ||
| 41 | + | * **PathSep** 、**PathSeparator** :多路径并列时的分隔符(';' 等) | |
| 42 | + | * **PathDelim** 、**DirectorySeparator** :各平台的目录分隔符('/'、'\'等) | |
| 43 | + | * **LineEnding** :行结束符(#13#10 是 CRLF、#10 是 [[Line feed|LF]]等) | |
| 42 | 44 | ||
| 43 | - | ||
| 44 | - | ||
| 45 | - | ||
| 46 | - | ||
| 47 | - | ||
| 48 | - | ||
| 49 | - | ||
| 50 | - | ||
| 51 | - | ||
| 45 | + | 另一个需注意的要点是文件系统是否大小写敏感。 Windows 平台的文件名通常不区分大小写,而 Unix 平台(如 Linux、FreeBSD)通常是区分的,不过承自 Unix 的 macOS 却不区分大小写。但请注意,如果在 Windows 中挂载 EXT2、EXT3 之类的文件系统,仍会区分大小写。同理,在 Linux 中挂载 FAT 文件系统也不应区分大小写。 | |
| 52 | 46 | ||
| 53 | - | ||
| 54 | - | ||
| 55 | - | ||
| 56 | - | ||
| 57 | - | ||
| 58 | - | ||
| 59 | - | ||
| 60 | - | ||
| 61 | - | ||
| 62 | - | ||
| 63 | - | ||
| 64 | - | ||
| 65 | - | ||
| 66 | - | ||
| 67 | - | ||
| 68 | - | ||
| 69 | - | ||
| 70 | - | ||
| 71 | - | ||
| 72 | - | ||
| 73 | - | ||
| 74 | - | ||
| 47 | + | NTFS 系统应特别引起注意,在 Windows 中使用时不区分大小写,但在 POSIX 操作系统中挂载时却又区分大小写。这可能会导致**各种问题,包括文件丢失** ,如果 NTFS 分区中存在只是大小写不同的同名文件,挂载到 Windows 时就会丢失文件。开发人员应考虑用自定义函数进行检查,防止在 NTFS 上创建多个同名文件。 | |
| 75 | 48 | ||
| 76 | - | ||
| 77 | - | ||
| 78 | - | ||
| 79 | - | ||
| 80 | - | ||
| 49 | + | macOS 文件名默认不区分大小写。这可能会导致烦人的错误,因此任何可移植应用程序在使用文件名时都应该保持前后一致。 | |
| 81 | 50 | ||
| 82 | - | ||
| 83 | - | ||
| 51 | + | RTL 文件函数采用与文件名相同的操作系统字符编码。在 Windows 中就是某个 Windows 代码页,而在 Linux、BSD 和 macOS 中通常采用 UTF-8。LCL 的 **FileUtil** 单元提供了一些文件操作函数,与其他 LCL 代码一样使用 UTF-8 字符串。如下所示: | |
| 84 | 52 | ``` | |
| 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 | + | ``` | |
| 85 | 93 | ||
| 86 | 94 | ### 空文件名和路径双分隔符 | |
| 87 | 95 | ||
| 88 | - | ||
| 89 | - | ||
| 90 | - | ||
| 91 | - | ||
| 96 | + | 在处理文件/目录名时,Windows 与 Linux/Unix/类 Unix 系统有不同的做法。 | |
| 92 | 97 | ||
| 93 | - | ||
| 98 | + | * Windows 允许文件名为空。这就是为什么在 Windows 中 FileExistsUTF8('..\') 可用于检测父目录中是否存在空名文件。 | |
| 99 | + | * 在 Linux/Unix/类 Unix 系统中,空文件映射为目录,目录视作文件处理。这意味着在 Unix 中,FileExistsUTF8('../') 将检测父目录是否存在,通常结果都是 true。 | |
| 94 | 100 | ||
| 95 | - | ||
| 96 | - | ||
| 101 | + | 对于文件名中的双路径分隔符,处理方式也不相同: | |
| 97 | 102 | ||
| 98 | - | ||
| 103 | + | * Windows:'C:\' 与 'C:\\\' 不同 | |
| 104 | + | * 类 Unix 系统:路径 '/usr//' 等同于 '/usr/',如果 '/usr' 是个目录,则也等同于 '/usr//' 和 '/usr/'。 | |
| 99 | 105 | ||
| 100 | - | ||
| 101 | - | ||
| 102 | - | ||
| 103 | - | ||
| 106 | + | 在拼接文件名时,这一点就很重要了。例如: | |
| 104 | 107 | ``` | |
| 108 | + | FullFilename:=FilePath+PathDelim+ShortFilename; // 可能会生成两个 PathDelims,在 Windows 和 Linux 中会 | |
| 109 | + | 给出不同结果 | |
| 110 | + | FullFilename:=AppendPathDelim(FilePath) + ShortFilename; // 只会生成一个 PathDelim | |
| 111 | + | FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // 只会生成一个 PathDelim,会再作些清理 | |
| 112 | + | ``` | |
| 105 | 113 | ||
| 106 | - | ||
| 114 | + | TrimFilename 函数会将双路径分隔符替换为单个,并缩短 '..' 路径,比如会把 /usr//lib/../src 修整为 /usr/src 。 | |
| 107 | 115 | ||
| 108 | - | ||
| 116 | + | 若要判断某个目录是否存在,请使用 **DirectoryExistsUTF8** 。 | |
| 109 | 117 | ||
| 110 | - | ||
| 118 | + | 另一常见任务是检查文件名的 path 部分是否存在。ExtractFilePath 可用于获取路径,但会包含路径分隔符。 | |
| 111 | 119 | ||
| 112 | - | ||
| 113 | - | ||
| 120 | + | * 在类 Unix 系统中,只要用 FileExistsUTF8 即可。比如 FileExistsUTF8('/home/user/') 在 /home/user 存在时会返回 true。 | |
| 121 | + | * 在 Windows 中,必须用 DirectoryExistsUTF8 函数,还必须预先删除路径分隔符,比如用 ChompPathDelim 函数。 | |
| 114 | 122 | ||
| 115 | - | ||
| 123 | + | 类 Unix 系统的根目录是 '/',用 ChompPathDelim 函数会创建一个空字符串。DirPathExists 函数的工作方式类似于 DirectoryExistsUTF8,只是会对给出的路径作裁剪。 | |
| 116 | 124 | ||
| 117 | - | ||
| 125 | + | 请注意,Unix/Linux 使用 '~'(波浪号)表示 home 目录,通常用 '/home/jim/'表示名为 jim 的用户。所以在命令行和脚本代码中,'~/myapp/myfile' 和 '/home/jim/myapp/myfile' 是同一个意思。但 Lazarus 不会自动扩展波浪号。因此必须用 ExpandFileNameUTF8('~/myapp/myfile') 获取完整路径。 | |
| 118 | 126 | ||
| 119 | 127 | ### 文本编码 | |
| 120 | 128 | ||
| 121 | - | ||
| 122 | - | ||
| 123 | - | ||
| 129 | + | 文本文件通常采用当前系统的编码。在 Windows 中通常是某个代码页,而在 Linux、BSD 和 macOS 中通常采用 UTF-8。没有100%准确的规则能够找出文本文件的编码。LCL 的 **lconvencoding** 单元中有一个猜测文本编码的函数: | |
| 130 | + | ``` | |
| 124 | 131 | function GuessEncoding(const s: string): string; | |
| 125 | - | ||
| 132 | + | function GetDefaultTextEncoding: string; | |
| 126 | 133 | ``` | |
| 127 | 134 | ||
| 128 | - | ||
| 129 | - | ||
| 130 | - | ||
| 135 | + | 其中还包含了一些编码转换函数: | |
| 136 | + | ``` | |
| 131 | 137 | function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string; | |
| 132 | - | ||
| 133 | - | ||
| 134 | - | ||
| 135 | - | ||
| 136 | - | ||
| 137 | - | ||
| 138 | - | ||
| 139 | - | ||
| 140 | - | ||
| 141 | - | ||
| 142 | - | ||
| 143 | - | ||
| 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 | + | ... | |
| 145 | 151 | ``` | |
| 146 | 152 | ||
| 147 | - | ||
| 148 | - | ||
| 149 | - | ||
| 153 | + | 比如要读入文本文件并转换为 UTF-8 编码,可用如下代码: | |
| 154 | + | ``` | |
| 150 | 155 | var | |
| 151 | - | ||
| 152 | - | ||
| 153 | - | ||
| 154 | - | ||
| 155 | - | ||
| 156 | - | ||
| 157 | - | ||
| 158 | - | ||
| 159 | - | ||
| 160 | - | ||
| 161 | - | ||
| 162 | - | ||
| 163 | - | ||
| 164 | - | ||
| 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; | |
| 165 | 170 | ``` | |
| 166 | 171 | ||
| 167 | - | ||
| 168 | - | ||
| 169 | - | ||
| 172 | + | 若要以系统编码保存文本文件,可用以下代码: | |
| 173 | + | ``` | |
| 170 | 174 | sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding); | |
| 171 | - | ||
| 175 | + | sl.SaveToFile('sometext.txt'); | |
| 172 | 176 | ``` | |
| 173 | 177 | ||
| 174 | 178 | ### 配置文件 | |
| 175 | 179 | ||
| 176 | - | ||
| 177 | - | ||
| 178 | - | ||
| 179 | - | ||
| 180 | - | ||
| 180 | + | SysUtils 单元的 [GetAppConfigDir](<http://lazarus-ccr.sourceforge.net/docs/rtl/sysutils/getappconfigdir.html> "doc:rtl/sysutils/getappconfigdir.html") 函数可获取各平台中的配置文件存放位置。该函数有一个 Global 参数,为 true 则返回全局目录,即适用于系统中的所有用户。如果参数 Global 为 false,则配置文件目录仅对运行该程序的用户有效。在不支持多用户的系统中,上述两个目录可能相同。 | |
| 181 | 181 | ||
| 182 | - | ||
| 182 | + | 还有一个 [GetAppConfigFile](<http://lazarus-ccr.sourceforge.net/docs/rtl/sysutils/getappconfigfile.html> "doc:rtl/sysutils/getappconfigfile.html") 函数,将返回一个配置文件名,用法如下: | |
| 183 | + | ``` | |
| 184 | + | ConfigFilePath := GetAppConfigFile(False); | |
| 185 | + | ``` | |
| 183 | 186 | ||
| 184 | - | ||
| 187 | + | 下面是在不同系统中用默认路径输出的示例: | |
| 188 | + | ``` | |
| 185 | 189 | program project1; | |
| 186 | - | ||
| 187 | - | ||
| 188 | - | ||
| 189 | - | ||
| 190 | - | ||
| 191 | - | ||
| 192 | - | ||
| 193 | - | ||
| 194 | - | ||
| 195 | - | ||
| 196 | - | ||
| 197 | - | ||
| 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. | |
| 198 | 202 | ``` | |
| 199 | 203 | ||
| 200 | - | ||
| 201 | - | ||
| 202 | - | ||
| 203 | - | ||
| 204 | - | ||
| 205 | - | ||
| 206 | - | ||
| 207 | - | ||
| 208 | - | ||
| 209 | - | ||
| 210 | - | ||
| 211 | - | ||
| 212 | - | ||
| 213 | - | ||
| 214 | - | ||
| 215 | - | ||
| 216 | - | ||
| 204 | + | 可见全局配置文件存于 /etc 目录中,而当前配置存于用户 home 目录中的隐藏目录。在 UNIX 和类 UNIX 系统中,目录名称以句点(.)开头的是隐藏目录。可在 GetAppConfigDir 返回的目录中创建一个目录,然后将配置文件存于其中。 | |
| 217 | 205 | ||
| 218 | - | ||
| 206 | + |  **Note:** 普通用户对 /etc 目录没有写入权限。只有具备管理员权限的用户才行。 | |
| 219 | 207 | ||
| 220 | - | ||
| 208 | + | 请注意,在 FPC 2.2.4 以下版本中,GetAppConfigDir 函数用程序所在的目录存储 Windows 全局配置。 | |
| 221 | 209 | ||
| 222 | - | ||
| 210 | + | Windows 98 下 FPC 2.2.0 的输出: | |
| 211 | + | ``` | |
| 212 | + | C:\Program Files\PROJECT1 | |
| 223 | 213 | C:\Windows\Local Settings\Application Data\PROJECT1 | |
| 224 | 214 | C:\Program Files\PROJECT1\PROJECT1.cfg | |
| 225 | 215 | C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg | |
| 226 | - | ||
| 227 | - | ||
| 228 | - | ||
| 229 | - | ||
| 230 | - | ||
| 231 | - | ||
| 232 | - | ||
| 233 | - | ||
| 234 | - | ||
| 235 | - | ||
| 236 | - | ||
| 237 | - | ||
| 238 | - | ||
| 239 | - | ||
| 240 | - | ||
| 241 | - | ||
| 242 | - | ||
| 243 | - | ||
| 244 | - | ||
| 245 | - | ||
| 246 | - | ||
| 247 | - | ||
| 248 | - | ||
| 249 | - | ||
| 250 | - | ||
| 251 | - | ||
| 252 | - | ||
| 253 | - | ||
| 254 | - | ||
| 255 | - | ||
| 256 | - | ||
| 257 | - | ||
| 258 | - | ||
| 259 | - | ||
| 260 | - | ||
| 261 | - | ||
| 262 | - | ||
| 263 | - | ||
| 264 | - | ||
| 265 | - | ||
| 266 | - | ||
| 267 | - | ||
| 268 | - | ||
| 269 | - | ||
| 270 | - | ||
| 271 | - | ||
| 272 | - | ||
| 273 | - | ||
| 274 | - | ||
| 275 | - | ||
| 276 | - | ||
| 277 | - | ||
| 278 | - | ||
| 279 | - | ||
| 280 | - | ||
| 281 | - | ||
| 282 | - | ||
| 283 | - | ||
| 216 | + | ``` | |
| 284 | 217 | ||
| 285 | - | ||
| 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 | + | ``` | |
| 286 | 225 | ||
| 287 | - | ||
| 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 | + | ``` | |
| 288 | 233 | ||
| 289 | - | ||
| 290 | - | ||
| 291 | - | ||
| 292 | - | ||
| 293 | - | ||
| 294 | - | ||
| 295 | - | ||
| 296 | - | ||
| 297 | - | ||
| 298 | - | ||
| 299 | - | ||
| 300 | - | ||
| 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 | + | ``` | |
| 301 | 241 | ||
| 302 | - | ||
| 303 | - | ||
| 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 | + | ``` | |
| 304 | 249 | ||
| 305 | - | ||
| 250 | + | 请注意 Windows 和非 Windows 系统的输出差异,Windows 系统中 | |
| 251 | + | ``` | |
| 252 | + | WriteLn(GetAppConfigFile(True)); | |
| 253 | + | ``` | |
| 306 | 254 | ||
| 307 | - | ||
| 308 | - | ||
| 309 | - | ||
| 310 | - | ||
| 311 | - | ||
| 312 | - | ||
| 313 | - | ||
| 314 | - | ||
| 255 | + | 输出的全局配置包含一个子目录,而其他操作系统则没有。若要获得与非 Windows 系统相同的结果,需要多加一个 [[Boolean]]参数: | |
| 256 | + | ``` | |
| 257 | + | WriteLn(GetAppConfigFile(True,True));。 | |
| 258 | + | ``` | |
| 315 | 259 | ||
| 316 | - | ||
| 317 | - | ||
| 318 | - | ||
| 260 | + |  **Note:** UPX 会影响 GetAppConfigDir 和 GetAppConfigFile 函数的使用效果。 | |
| 319 | 261 | ||
| 320 | - | ||
| 321 | - | ||
| 262 | + | #### macOS | |
| 322 | 263 | ||
| 323 | - | ||
| 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 方式处理配置文件的代码。 | |
| 324 | 265 | ||
| 325 | - | ||
| 266 | + | ### 数据和资源文件 | |
| 326 | 267 | ||
| 327 | - | ||
| 328 | - | ||
| 329 | - | ||
| 330 | - | ||
| 331 | - | ||
| 332 | - | ||
| 333 | - | ||
| 334 | - | ||
| 268 | + | 应用程序需用到的数据文件应该存在何处,比如图像、音乐、XML 文件、数据库文件、帮助文件等,这是个十分常见的问题。不幸的是,获取数据文件最优位置的跨平台函数根本不存在。解决方案就是用 IFDEF 为各种平台实现不同的代码。 | |
| 335 | 269 | ||
| 336 | - | ||
| 270 | + | #### Windows | |
| 337 | 271 | ||
| 338 | - | ||
| 339 | - | ||
| 272 | + | 在 Windows 中,由程序修改的数据不应存于应用程序目录中(如 C:\Program Files\),而应放在特定位置(可参阅["对应用程序数据进行分类"](<http://support.microsoft.com/kb/310294>)( _链接已失效_ )。Windows Vista 及以上版本会强制如此(用户只能在提权或禁用 UAC 时,才对这些目录具有写入权限),不过也会用文件夹重定向机制来兼容老程序或编码错误程序。对应用程序目录中的数据可以只读(不写),但不推荐。 | |
| 340 | 273 | ||
| 341 | - | ||
| 342 | - | ||
| 343 | - | ||
| 274 | + | 简单的用法可以如下所示: | |
| 275 | + | ``` | |
| 276 | + | OpDirLocal:= GetEnvironmentVariableUTF8('appdata')+'\MyAppName'; | |
| 277 | + | ``` | |
| 344 | 278 | ||
| 345 | - | ||
| 346 | - | ||
| 279 | + | 请参阅[[Windows Programming Tips|Windows 编程技巧 - 获取特殊文件夹]]。 | |
| 347 | 280 | ||
| 348 | - | ||
| 349 | - | ||
| 350 | - | ||
| 351 | - | ||
| 352 | - | ||
| 353 | - | ||
| 354 | - | ||
| 355 | - | ||
| 356 | - | ||
| 357 | - | ||
| 358 | - | ||
| 281 | + | #### Unix/Linux | |
| 359 | 282 | ||
| 360 | - | ||
| 361 | - | ||
| 362 | - | ||
| 363 | - | ||
| 364 | - | ||
| 365 | - | ||
| 366 | - | ||
| 283 | + | 在大多数 Unix 系统中(如 Linux、FreeBSD、OpenBSD、Solaris,但 **macOS 除外** ),应用程序的数据文件存于固定的位置,可能是 /usr/share/app_name 或 /opt/app_name。 | |
| 367 | 284 | ||
| 368 | - | ||
| 369 | - | ||
| 370 | - | ||
| 371 | - | ||
| 372 | - | ||
| 373 | - | ||
| 374 | - | ||
| 375 | - | ||
| 376 | - | ||
| 377 | - | ||
| 378 | - | ||
| 379 | - | ||
| 380 | - | ||
| 285 | + | 由应用程序写入的数据,通常存于 /usr/local/var/app_name、/usr/local/share/app_name 或 /usr/local/app_name,访问权限会自动设好。 | |
| 381 | 286 | ||
| 382 | - | ||
| 383 | - | ||
| 384 | - | ||
| 385 | - | ||
| 386 | - | ||
| 387 | - | ||
| 388 | - | ||
| 389 | - | ||
| 390 | - | ||
| 391 | - | ||
| 392 | - | ||
| 393 | - | ||
| 394 | - | ||
| 395 | - | ||
| 396 | - | ||
| 397 | - | ||
| 287 | + | 帮助文件(又名 man)应存于 /usr/local/man/man[手册章节号]/app_name>,访问权限会自动设好。请注意,某些 FreeBSD 和 Linux 手册的章节位置会有不同,或者干脆缺失。 | |
| 398 | 288 | ||
| 399 | - | ||
| 400 | - | ||
| 401 | - | ||
| 402 | - | ||
| 403 | - | ||
| 289 | + | 由用户读写的配置文件/数据,将存于用户 home 目录中的某处(比如 ~/.config/<programname>)。请参阅上文[[Multiplatform Programming Guide|配置文件]]。 | |
| 404 | 290 | ||
| 405 | - | ||
| 406 | - | ||
| 407 | - | ||
| 408 | - | ||
| 291 | + | #### macOS | |
| 409 | 292 | ||
| 410 | - | ||
| 293 | + | macOS 是 UNIX 操作系统的一个例外。应用程序以 bundle 的形式发布(扩展名为“.app”的目录),文件管理器 Finder 将其视作单个文件(在 Terminal 中可执行“cd path/myapp.app”,或在 Finder 中右键单击应用程序并选择“显示包内容”)。应用程序的资源文件应存于 bundle 中。假定 bundle 为“path/MyApp.app”,则: | |
| 411 | 294 | ||
| 412 | - | ||
| 295 | + | * 可执行文件为“path/MyApp.app/Contents/MacOS/myapp” | |
| 296 | + | * 资源目录为“path/MyApp.app/Contents/Resources” | |
| 413 | 297 | ||
| 414 | - | ||
| 298 | + | 应用程序的数据和资源文件通常应存于应用程序 bundle 的 Resources 目录中。有关如何以 Apple 兼容方式处理应用程序数据的代码,请参阅文章 [[Locating the macOS application resources directory|定位 macOS 应用程序资源目录]] 。 | |
| 415 | 299 | ||
| 416 | - | ||
| 300 | + | 用户生成的数据文件应于用户的“~/Library/Application Support”目录中。有关如何以 Apple 兼容方式处理用户数据的代码,请参阅文章[[Locating macOS app support, preferences folders|定位 macOS 应用程序的支持库、首选项文件夹]]。 | |
| 417 | 301 | ||
| 418 | - | ||
| 302 | + | {{Warning|**请勿** 在任何 UNIX 平台中用 `paramStr(0)` | |
| 419 | 303 | ``` | |
| 420 | - | ||
| 421 | - | ||
| 422 | - | ||
| 423 | - | ||
| 424 | - | ||
| 425 | - | ||
| 426 | - | ||
| 427 | - | ||
| 428 | - | ||
| 429 | - | ||
| 304 | + | 或任何用到它的函数来 _确定_ 可执行文件的位置,因为这是 DOS-Windows-OS/2 系统的约定,在概念上存在一些问题,且在其他平台上无法模拟解决。在 UNIX 平台中, | |
| 430 | 305 | ``` | |
| 431 | 306 | ||
| 432 | - | ||
| 433 | - | ||
| 434 | - | ||
| 307 | + | `paramStr(0)` | |
| 308 | + | ``` | |
| 309 | + | 唯一能保证返回的是程序启动时的名称。 | |
| 310 | + | ``` | |
| 435 | 311 | ||
| 436 | - | ||
| 312 | + | 程序所在的目录和二进制文件的真实名称(若以符号链接启动),用 `paramStr(0)` 均无法可靠获得。对于 macOS 而言,可用[[Locating the macOS application resources directory|此文]]中的原生代码**可靠地** 得到应用程序 bundle 目录。 | |
| 437 | 313 | ||
| 438 | - | ||
| 314 | + | ### 32/64 bit | |
| 439 | 315 | ||
| 440 | - | ||
| 441 | - | ||
| 316 | + | #### 在运行时检测系统位数 | |
| 442 | 317 | ||
| 443 | - | ||
| 444 | - | ||
| 445 | - | ||
| 446 | - | ||
| 447 | - | ||
| 448 | - | ||
| 449 | - | ||
| 318 | + | 虽然用编译指令 define 可以控制 32 位或 64 位编译,但有时会想知道操作系统的位数情况。 比如在 64 位 Windows 中运行 32 位 Lazarus 程序,可能需要运行一个位于 32 位程序的文件目录中的外部程序,或者可能需向用户显示信息:在 LazUpdater Lazarus 安装程序中,需要让用户选择 32 位和 64 位编译器。这里是代码: [[Detect Windows x32-x64 example|检测 Windows x32-x64 示例]]。 | |
| 450 | 319 | ||
| 451 | - | ||
| 452 | - | ||
| 453 | - | ||
| 454 | - | ||
| 455 | - | ||
| 456 | - | ||
| 457 | - | ||
| 458 | - | ||
| 459 | - | ||
| 460 | - | ||
| 461 | - | ||
| 462 | - | ||
| 463 | - | ||
| 464 | - | ||
| 465 | - | ||
| 466 | - | ||
| 467 | - | ||
| 468 | - | ||
| 469 | - | ||
| 470 | - | ||
| 471 | - | ||
| 472 | - | ||
| 473 | - | ||
| 474 | - | ||
| 475 | - | ||
| 476 | - | ||
| 477 | - | ||
| 478 | - | ||
| 479 | - | ||
| 480 | - | ||
| 481 | - | ||
| 482 | - | ||
| 483 | - | ||
| 484 | - | ||
| 485 | - | ||
| 486 | - | ||
| 320 | + | #### 在加载外部库之前检测位数 | |
| 487 | 321 | ||
| 488 | - | ||
| 489 | - | ||
| 490 | - | ||
| 491 | - | ||
| 492 | - | ||
| 493 | - | ||
| 494 | - | ||
| 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; | |
| 495 | 338 | begin | |
| 496 | - | ||
| 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); | |
| 497 | 386 | end; | |
| 498 | - | ||
| 499 | - | ||
| 500 | - | ||
| 501 | - | ||
| 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} | |
| 502 | 397 | end; | |
| 503 | - | ||
| 504 | - | ||
| 505 | 398 | ``` | |
| 506 | 399 | ||
| 507 | - | ||
| 508 | - | ||
| 509 | - | ||
| 400 | + | #### 指针/整数 类型转换 | |
| 510 | 401 | ||
| 511 | - | ||
| 402 | + | 64 位指针要占用 8 个字节,而不是 32 位指针的 4 字节。而为了保持兼容性,Integer 类型在所有平台都保持 32 位。这就意味着,指针类型无法转换为整数,反之亦然。 | |
| 512 | 403 | ||
| 513 | - | ||
| 404 | + | 为此 FPC 定义了两个类型:PtrInt 和 PtrUInt。PtrInt 在 32 位平台上是 32 位有符号整数,在 64 位平台上是 64 位有符号整数。PtrUInt 同理,只是换成无符号整数。 | |
| 514 | 405 | ||
| 515 | - | ||
| 406 | + | 若要对 Delphi 和 FPC 都能有效,请使用以下代码: | |
| 407 | + | ``` | |
| 516 | 408 | {$IFNDEF FPC} | |
| 517 | - | ||
| 518 | - | ||
| 519 | - | ||
| 520 | - | ||
| 409 | + | type | |
| 410 | + | PtrInt = integer; | |
| 411 | + | PtrUInt = cardinal; | |
| 412 | + | {$ENDIF} | |
| 521 | 413 | ``` | |
| 522 | 414 | ||
| 523 | - | ||
| 524 | - | ||
| 525 | - | ||
| 526 | - | ||
| 527 | - | ||
| 415 | + | 请将所有 **integer(SomePointerOrObject)** 都替换为 **PtrInt(SomePointerOrObject)** 。 | |
| 528 | 416 | ||
| 529 | - | ||
| 417 | + | ### 字节序 | |
| 530 | 418 | ||
| 531 | - | ||
| 419 | + | Intel 平台是低位优先字节序,这意味着低位字节在前(低地址)。如 $1234 的两个字节会存为 $34 $12。而在 powerpc 这种高位优先的系统中,$1234 的两个字节会存为 $12 $34。在读取其他系统创建的文件时,这是一个重要区别。 | |
| 532 | 420 | ||
| 533 | - | ||
| 421 | + | 若要适应两种字节序,请使用以下代码: | |
| 422 | + | ``` | |
| 534 | 423 | {$IFDEF ENDIAN_BIG} | |
| 535 | - | ||
| 536 | - | ||
| 537 | - | ||
| 538 | - | ||
| 424 | + | ... | |
| 425 | + | {$ELSE} | |
| 426 | + | ... | |
| 427 | + | {$ENDIF} | |
| 539 | 428 | ``` | |
| 540 | 429 | ||
| 541 | - | ||
| 542 | - | ||
| 543 | - | ||
| 430 | + | 而低位优先字节序的编译符则为 ENDIAN_LITTLE。 | |
| 544 | 431 | ||
| 545 | - | ||
| 432 | + | system 单元中提供了大量的字节序转换函数,如 SwapEndian、BEtoN(高位优先到当前字节序)、LEtoN(低位优先到当前字节序)、NtoBE(当前字节序到高位优先)和 NtoLE(当前字节序到低位优先)。 | |
| 546 | 433 | ||
| 547 | - | ||
| 434 | + | #### Libc 和其他特殊单元 | |
| 548 | 435 | ||
| 549 | - | ||
| 436 | + | 请避免使用“oldlinux” 和 “libc” 之类在 linux/i386 之外不受支持的过时单元。 | |
| 550 | 437 | ||
| 551 | - | ||
| 438 | + | #### Assembler | |
| 552 | 439 | ||
| 553 | - | ||
| 440 | + | 请避免使用 [[Assembly language|汇编语言]]。 | |
| 554 | 441 | ||
| 555 | - | ||
| 442 | + | #### 编译器 define 指令 | |
| 443 | + | ``` | |
| 556 | 444 | {$ifdef CPU32} | |
| 557 | - | ||
| 558 | - | ||
| 559 | - | ||
| 560 | - | ||
| 561 | - | ||
| 445 | + | ...编写运行于 32 位处理器下的代码 | |
| 446 | + | {$ENDIF} | |
| 447 | + | {$ifdef CPU64} | |
| 448 | + | ...编写运行于 64 位处理器下的代码 | |
| 449 | + | {$ENDIF} | |
| 562 | 450 | ``` | |
| 563 | 451 | ||
| 564 | 452 | ### 项目、包和搜索路径 | |
| 565 | 453 | ||
| 566 | - | ||
| 567 | - | ||
| 568 | - | ||
| 454 | + | Lazarus 项目和软件包已设计为可跨平台编译。通常只要将项目和所需软件包复制到别的机器上,即可进行编译了。无需为每个平台分别创建项目。 | |
| 569 | 455 | ||
| 570 | - | ||
| 456 | + | 下面给出一些跨平台编译的建议。 | |
| 571 | 457 | ||
| 572 | - | ||
| 458 | + | 编译器会为每个单元创建同名 ppu 文件,以供其他项目和软件包使用。单元的源码文件(如 unit1.pas)不应在多个项目和软件包之间共用。只要向编译器给出创建 ppu 文件的单元输出目录即可。IDE 默认会执行此操作,因此无需执行任何操作。 | |
| 573 | 459 | ||
| 574 | - | ||
| 460 | + | 每个单元文件都必须属于 **某个** 项目或软件包。如果单元文件只有一个项目用到了,请将其加入项目中。否则,请将其加入某个软件包。如果尚未为单元建立软件包,请参阅:[[Lazarus Packages|为公共单元创建软件包]]。 | |
| 575 | 461 | ||
| 576 | - | ||
| 462 | + | 每个项目和软件包都应位于**独立** 的目录 - 相互之间不应共用目录。不然,您就得吃透编译器的搜索路径。如果您不够熟练,或者使用项目/软件包的人不够熟练,请不要在项目/软件包之间共享目录。 | |
| 577 | 463 | ||
| 578 | - | ||
| 464 | + | #### 平台依赖的单元 | |
| 579 | 465 | ||
| 580 | - | ||
| 466 | + | 如单元 wintricks.pas 只能在 Windows 下使用。请在 uses 部分采用以下方式声明: | |
| 467 | + | ``` | |
| 581 | 468 | uses | |
| 582 | - | ||
| 583 | - | ||
| 584 | - | ||
| 585 | - | ||
| 586 | - | ||
| 469 | + | Classes, SysUtils | |
| 470 | + | {$IFDEF Windows} | |
| 471 | + | ,WinTricks | |
| 472 | + | {$ENDIF} | |
| 473 | + | ; | |
| 587 | 474 | ``` | |
| 588 | 475 | ||
| 589 | - | ||
| 476 | + | 如果单元属于某个软件包,则还得在包编辑器中选中单元并不要勾选 _Use unit_ 。 | |
| 590 | 477 | ||
| 591 | - | ||
| 478 | + | 请参阅[[Lazarus Packages|平台依赖的单元]] | |
| 592 | 479 | ||
| 593 | - | ||
| 480 | + | #### 平台依赖的搜索路径 | |
| 594 | 481 | ||
| 595 | - | ||
| 482 | + | 若跨平台应用需直接访问操作系统,没完没了的 IFDEF 结构很快会让人厌倦不堪。FPC 和 Lazarus 源代码常用的一种解决方案,就是使用包含文件。先为每种平台都创建一个子目录,如 win32、linux、bsd、darwin。再在每个目录中放入文件名相同的包含文件。然后在 include 路径中使用宏。单元代码中则可以使用常规的 include 指令。 | |
| 596 | 483 | ||
| 597 | - | ||
| 484 | + | 以下是某 LCL 部件的 include 示例: | |
| 598 | 485 | ||
| 599 | - | ||
| 486 | + | 为每种 LCL 部件创建 include 文件: | |
| 487 | + | ``` | |
| 600 | 488 | win32/example.inc | |
| 601 | - | ||
| 602 | - | ||
| 603 | - | ||
| 489 | + | gtk/example.inc | |
| 490 | + | gtk2/example.inc | |
| 491 | + | carbon/example.inc | |
| 604 | 492 | ``` | |
| 605 | 493 | ||
| 606 | - | ||
| 607 | - | ||
| 608 | - | ||
| 494 | + | 这些 include 文件不用加入软件包或项目中。 在软件包或项目的编译器选项中,加入包含文件搜索路径 _$(LCLWidgetType)_ 。 | |
| 609 | 495 | ||
| 610 | - | ||
| 496 | + | 在单元代码中采用如下指令即可:{$I example.inc} | |
| 611 | 497 | ||
| 612 | - | ||
| 613 | - | ||
| 614 | - | ||
| 615 | - | ||
| 498 | + | 以下给出一些常用的宏及常见值: | |
| 616 | 499 | ||
| 617 | - | ||
| 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 | |
| 618 | 504 | ||
| 619 | - | ||
| 505 | + | $Env() 可用于引用环境变量。 | |
| 620 | 506 | ||
| 621 | - | ||
| 507 | + | 当然可以组合使用这些宏。比如 LCL 就用到了: | |
| 508 | + | ``` | |
| 622 | 509 | $(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType) | |
| 623 | 510 | ``` | |
| 624 | 511 | ||
| 625 | - | ||
| 512 | + | 完整的宏清单请参阅: [[IDE Macros in paths and filenames|IDE 中的路径和文件名宏]] | |
| 626 | 513 | ||
| 627 | - | ||
| 514 | + | #### 依赖于计算机/用户的搜索路径 | |
| 628 | 515 | ||
| 629 | - | ||
| 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 就能找到这个包。这时就无需什么配置文件。 | |
| 630 | 517 | ||
| 631 | - | ||
| 518 | + | 若要在多台机器或为一台机器中的多个用户(如多个学生)部署软件包,那么可在 lazarus 源代码目录中加入一个 lpl 文件。相关示例请参阅 packager/globallinks。 | |
| 632 | 519 | ||
| 633 | - | ||
| 520 | + | ### 地区差异 | |
| 634 | 521 | ||
| 635 | - | ||
| 522 | + | 在 Free Pascal 中,有些函数会根据当前地区的不同产生不同的结果,例如 StrToFloat。比如在美国,小数分隔符通常是“.”,但在很多欧洲和南美国家/地区则是“,”。这可能会是个问题,因为有时希望这些函数能以固定的方式运行,不要依赖于地区。例如,包含小数点的文件格式就应始终以同一方式进行解析。 | |
| 636 | 523 | ||
| 637 | - | ||
| 524 | + | 下面几节内容给出了具体做法。 | |
| 638 | 525 | ||
| 639 | - | ||
| 526 | + | #### macOS | |
| 640 | 527 | ||
| 641 | - | ||
| 528 | + | 关于 macOS 系统中地区设置的细节,请参阅文章 [[Locale settings for macOS]]。 | |
| 642 | 529 | ||
| 643 | - | ||
| 530 | + | #### StrToFloat | |
| 531 | + | ||
| 532 | + | 以下代码可创建将小数分隔符固定的格式设置: | |
| 533 | + | ``` | |
| 644 | 534 | // in your .lpr project file | |
| 645 | - | ||
| 646 | - | ||
| 647 | - | ||
| 648 | - | ||
| 649 | - | ||
| 650 | - | ||
| 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} | |
| 651 | 541 | ``` | |
| 652 | 542 | ||
| 653 | - | ||
| 654 | - | ||
| 655 | - | ||
| 543 | + | 和: | |
| 544 | + | ``` | |
| 656 | 545 | // in your code: | |
| 657 | - | ||
| 658 | - | ||
| 659 | - | ||
| 660 | - | ||
| 661 | - | ||
| 662 | - | ||
| 663 | - | ||
| 664 | - | ||
| 665 | - | ||
| 666 | - | ||
| 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 | |
| 667 | 556 | ``` | |
| 668 | 557 | ||
| 669 | - | ||
| 670 | - | ||
| 671 | - | ||
| 558 | + | 后续在调用 StrToFloat 时即可使用上述格式设置,如下所示: | |
| 559 | + | ``` | |
| 672 | 560 | // This function works like StrToFloat, but simply tries two possible decimal separator | |
| 673 | - | ||
| 674 | - | ||
| 675 | - | ||
| 676 | - | ||
| 677 | - | ||
| 678 | - | ||
| 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; | |
| 679 | 567 | ``` | |
| 680 | 568 | ||
| 681 | - | ||
| 682 | - | ||
| 683 | - | ||
| 569 | + | ### GTK2 和 FPU 异常的屏蔽 | |
| 684 | 570 | ||
| 685 | - | ||
| 571 | + | Gtk2 库更改了 FPU (浮点单元) 对异常的默认屏蔽。因此用到 Gtk2 库的应用程序会无法触发一些浮点数类的异常。假如在 Windows 中开发采用了 win32/64 控件(Windows 默认)的 LCL 应用程序,并打算在 Linux 下也要能编译(默认控件是 Gtk2),就应牢记这些不兼容的地方。 | |
| 686 | 572 | ||
| 687 | - | ||
| 573 | + | 在 [此论坛主题](<http://www.lazarus.freepascal.org/index.php/topic,13460.0.html>)和[缺陷报告](<http://bugs.freepascal.org/view.php?id=19674>)中有所答复,但显然这是无解的,因此我们必须清楚真正的差别是什么。 | |
| 688 | 574 | ||
| 689 | - | ||
| 575 | + | 那就做个测试吧: | |
| 576 | + | ``` | |
| 690 | 577 | uses | |
| 691 | - | ||
| 692 | - | ||
| 693 | - | ||
| 694 | - | ||
| 695 | - | ||
| 696 | - | ||
| 697 | - | ||
| 698 | - | ||
| 699 | - | ||
| 700 | - | ||
| 701 | - | ||
| 702 | - | ||
| 703 | - | ||
| 704 | - | ||
| 705 | - | ||
| 706 | - | ||
| 707 | - | ||
| 708 | - | ||
| 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. | |
| 709 | 596 | ``` | |
| 710 | 597 | ||
| 711 | - | ||
| 712 | - | ||
| 713 | - | ||
| 598 | + | 这个简单的程序会返回 FPC 默认的屏蔽设置: | |
| 599 | + | ``` | |
| 714 | 600 | exInvalidOp - not masked! | |
| 715 | - | ||
| 716 | - | ||
| 717 | - | ||
| 718 | - | ||
| 719 | - | ||
| 601 | + | exDenormalized - masked! | |
| 602 | + | exZeroDivide - not masked! | |
| 603 | + | exOverflow - not masked! | |
| 604 | + | exUnderflow - masked! | |
| 605 | + | exPrecision - masked! | |
| 720 | 606 | ``` | |
| 721 | 607 | ||
| 722 | - | ||
| 723 | - | ||
| 724 | - | ||
| 608 | + | 但使用 Gtk2 时只有 exOverflow 未被屏蔽。 | |
| 725 | 609 | ||
| 726 | - | ||
| 610 | + | 因此,如果应用程序链接了 Gtk2 库,就不会触发 EInvalidOp 和 EZeroDivide 异常!通常把非零值除以零就会引发 EZeroDivide 异常,让零除以零则会引发 EInvalidOp 异常。比如以下代码: | |
| 611 | + | ``` | |
| 727 | 612 | var | |
| 728 | - | ||
| 729 | - | ||
| 730 | - | ||
| 731 | - | ||
| 732 | - | ||
| 733 | - | ||
| 734 | - | ||
| 735 | - | ||
| 736 | - | ||
| 737 | - | ||
| 613 | + | X, A, B: Double; | |
| 614 | + | // ... | |
| 615 | + | ||
| 616 | + | try | |
| 617 | + | X := A / B; | |
| 618 | + | // 代码块 1 | |
| 619 | + | except | |
| 620 | + | // 代码块 2 | |
| 621 | + | end; | |
| 622 | + | // ... | |
| 738 | 623 | ``` | |
| 739 | 624 | ||
| 740 | - | ||
| 741 | - | ||
| 742 | - | ||
| 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”。 | |
| 743 | 626 | ||
| 744 | - | ||
| 627 | + | 这种不一致的情况可用各种方法克服。大多数情况下可以先检测一下 B,等于零就不再继续作除法了。但有时还需要采用一些其他方法。请看以下示例: | |
| 628 | + | ``` | |
| 745 | 629 | uses | |
| 746 | - | ||
| 747 | - | ||
| 748 | - | ||
| 749 | - | ||
| 750 | - | ||
| 751 | - | ||
| 752 | - | ||
| 753 | - | ||
| 754 | - | ||
| 755 | - | ||
| 756 | - | ||
| 757 | - | ||
| 758 | - | ||
| 759 | - | ||
| 760 | - | ||
| 761 | - | ||
| 762 | - | ||
| 763 | - | ||
| 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 | + | // ... | |
| 765 | 649 | ``` | |
| 766 | 650 | ||
| 767 | - | ||
| 768 | - | ||
| 769 | - | ||
| 651 | + | 或者可以: | |
| 652 | + | ``` | |
| 770 | 653 | uses | |
| 771 | - | ||
| 772 | - | ||
| 773 | - | ||
| 774 | - | ||
| 775 | - | ||
| 776 | - | ||
| 777 | - | ||
| 778 | - | ||
| 779 | - | ||
| 780 | - | ||
| 781 | - | ||
| 782 | - | ||
| 783 | - | ||
| 784 | - | ||
| 785 | - | ||
| 786 | - | ||
| 787 | - | ||
| 788 | - | ||
| 789 | - | ||
| 790 | - | ||
| 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 | + | // ... | |
| 792 | 675 | ``` | |
| 793 | 676 | ||
| 794 | - | ||
| 795 | - | ||
| 796 | - | ||
| 677 | + | 请小心,不要做类似以下的操作(移除异常屏蔽的情况下调用 LCL): | |
| 678 | + | ``` | |
| 797 | 679 | try | |
| 798 | - | ||
| 799 | - | ||
| 800 | - | ||
| 801 | - | ||
| 802 | - | ||
| 803 | - | ||
| 804 | - | ||
| 805 | - | ||
| 806 | - | ||
| 807 | - | ||
| 808 | - | ||
| 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 | + | // ... | |
| 810 | 692 | ``` | |
| 811 | 693 | ||
| 812 | - | ||
| 813 | - | ||
| 814 | - | ||
| 694 | + | 而应使用辅助变量: | |
| 695 | + | ``` | |
| 815 | 696 | try | |
| 816 | - | ||
| 817 | - | ||
| 818 | - | ||
| 819 | - | ||
| 820 | - | ||
| 821 | - | ||
| 822 | - | ||
| 823 | - | ||
| 824 | - | ||
| 825 | - | ||
| 826 | - | ||
| 827 | - | ||
| 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 | + | // ... | |
| 829 | 710 | ``` | |
| 830 | 711 | ||
| 831 | - | ||
| 832 | - | ||
| 833 | - | ||
| 712 | + | 在开发 LCL 应用程序时,任何时候最要紧的就是要清楚上述差异,牢记不同的控件可能会以不同方式执行某些浮点运算。当然可以考虑采用某种合适的方案来解决,但这个问题不应被忽视。 | |
| 834 | 713 | ||
| 835 | - | ||
| 714 | + | ## 从 Windows 迁移到 *nix 等系统时的问题 | |
| 836 | 715 | ||
| 837 | - | ||
| 716 | + | 本节讨论了与 Linux、macOS、Android 和其他 Unix 相关的一些主题。不一定适用于所有平台。 | |
| 838 | 717 | ||
| 839 | - | ||
| 718 | + | ### Unix 中没有“应用程序目录”的概念 | |
| 840 | 719 | ||
| 841 | - | ||
| 720 | + | 许多程序员习惯调用 ExtractFilePath(ParamStr(0)) 或 Application.ExeName 来获取可执行文件的位置,然后根据可执行文件的位置搜索程序执行所需的文件(图像、XML 文件、数据库文件等)。这在 unix 系统中是不行的。ParamStr(0) 返回的可能不是程序执行时所在的目录,不同的 shell (sh、bash 等)还会返回不同的结果。 | |
| 842 | 721 | ||
| 843 | - | ||
| 722 | + | 即便 Application.ExeName 实际反映了可执行文件所在的目录,这个可执行文件也可能只是个符号链接,因此得到的可能只是符号链接所在的目录(取决于 Linux 内核的版本,可能是符号链接的目录或程序二进制文件的目录)。 | |
| 844 | 723 | ||
| 845 | - | ||
| 724 | + | 若要避免上述问题,请参阅[[Multiplatform Programming Guide|配置文件]]和 [[Multiplatform Programming Guide|数据文件]]。 | |
| 846 | 725 | ||
| 847 | - | ||
| 726 | + | ### 平替 Windows COM 自动化 | |
| 848 | 727 | ||
| 849 | - | ||
| 728 | + | Windows 中的 COM 自动化是个强大的功能,不仅可以远程操控其他程序,还允许被其他程序操控。在 Delphi 中可以让应用程序既是 COM 自动化客户端又是服务端,这意味着可以与其他程序实现双向操控。 请参阅使用 COM 自动化与 OpenOffice 和 Microsoft Office 交互。示例请参阅[[Office Automation|利用 COM 自动化实现与 OpenOffice 和 Microsoft Office 的交互]]。 | |
| 850 | 729 | ||
| 851 | - | ||
| 730 | + | #### macOS 中的替代方案 | |
| 852 | 731 | ||
| 853 | - | ||
| 732 | + | 遗憾的是,macOS 和 Linux 不支持 COM 自动化。不过可用 AppleScript 在 macOS 中模拟 COM 自动化的某些功能。 | |
| 854 | 733 | ||
| 855 | - | ||
| 856 | - | ||
| 857 | - | ||
| 858 | - | ||
| 734 | + | AppleScript 在某些方面类似于 COM 自动化。比如可以编写操纵其他程序的脚本。下面是一个非常简单的 AppleScript 示例,启动 NeoOffice(OpenOffice.org 的 Mac 版本): | |
| 859 | 735 | ``` | |
| 860 | - | ||
| 861 | - | ||
| 862 | - | ||
| 863 | - | ||
| 864 | - | ||
| 736 | + | tell application "NeoOffice" | |
| 737 | + | launch | |
| 738 | + | end tell | |
| 865 | 739 | ``` | |
| 866 | 740 | ||
| 867 | - | ||
| 868 | - | ||
| 869 | - | ||
| 870 | - | ||
| 871 | - | ||
| 872 | - | ||
| 741 | + | 应用程序若支持 AppleScript 操纵,就提供了可供调用的类和命令“字典”,类似于 Windows 自动化服务程序的类。即便是 NeoOffice 这类不提供字典的应用程序,仍然会响应命令 “launch”、“activate” 和 “quit”。AppleScript 可由 macOS 脚本编辑器或 Finder 运行 ,甚至还能转换为与普通 App 一样可拖放至 Dock 的 App。从程序中也可以运行 AppleScript,如下例所示: | |
| 873 | 742 | ``` | |
| 874 | - | ||
| 875 | - | ||
| 876 | - | ||
| 877 | - | ||
| 878 | - | ||
| 743 | + | Shell('myscript.applescript'); | |
| 879 | 744 | ``` | |
| 880 | 745 | ||
| 881 | - | ||
| 882 | - | ||
| 883 | - | ||
| 884 | - | ||
| 746 | + | 这里假定脚本位于给出的文件中。还可以在应用程序中用 macOS OsaScript 命令动态执行脚本: | |
| 885 | 747 | ``` | |
| 886 | - | ||
| 887 | - | ||
| 748 | + | Shell('osascript -e '#39'tell application "NeoOffice"'#39 + | |
| 749 | + | ' -e '#39'launch'#39' -e '#39'end tell'#39); | |
| 750 | + | {注意用 #39 为参数裹上引号} | |
| 751 | + | ``` | |
| 888 | 752 | ||
| 889 | - | ||
| 890 | - | ||
| 753 | + | 当然上述例子相当于以下 Open 命令: | |
| 891 | 754 | ``` | |
| 755 | + | Shell('open -a NeoOffice'); | |
| 756 | + | ``` | |
| 892 | 757 | ||
| 893 | - | ||
| 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 | + | ``` | |
| 894 | 762 | ||
| 895 | - | ||
| 896 | - | ||
| 763 | + | 和 | |
| 897 | 764 | ``` | |
| 765 | + | fpsystem('open -a mail "[mailto:ss4200@invalid.org](<mailto:ss4200@invalid.org>)"'); | |
| 766 | + | ``` | |
| 898 | 767 | ||
| 899 | - | ||
| 768 | + | 这里想当然假设 macOS 系统安装了 Safari 和 Mail 应用。当然,永远不应做出这种假设,其实上述两个示例可以靠 macOS 来完成,可以换成以下方式来调用当前用户的默认 Web 浏览器和电子邮件客户端: | |
| 769 | + | ``` | |
| 770 | + | fpsystem('open "<http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html>"'); | |
| 771 | + | ``` | |
| 900 | 772 | ||
| 901 | - | ||
| 902 | - | ||
| 773 | + | 和 | |
| 903 | 774 | ``` | |
| 775 | + | fpsystem('open "[mailto:ss4200@invalid.org](<mailto:ss4200@invalid.org>)"'); | |
| 776 | + | ``` | |
| 904 | 777 | ||
| 905 | - | ||
| 778 | + | 如果用了 `fpsystem` 或 `shell`(可互替),请不要忘记在 uses 中包含 Unix 单元。 | |
| 906 | 779 | ||
| 907 | - | ||
| 780 | + | AppleScript 的真正强大之处在于,能够远程操纵程序创建和打开文档,并自动执行其他一些功能。到底能对程序执行多少操纵,取决于其 AppleScript 词典(若存在的话)。比如用 AppleScript 就不大好操纵 Microsoft 的 Office X 程序,而较新的 Office 2004 已全部重写了 AppleScript 字典,很多方面都可与 Windows Office Automation 服务的字典相提并论了。 | |
| 908 | 781 | ||
| 909 | - | ||
| 782 | + | #### Linux 中的替代方案 | |
| 910 | 783 | ||
| 911 | - | ||
| 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 文档浏览器的)。 | |
| 912 | 785 | ||
| 913 | - | ||
| 786 | + | 与 Windows 一样,很多 macOS 和 Linux 程序都包含多个运行库文件(.dylib 和 .so 扩展名)。有时这些库会设计成可供其他程序调用。虽然这可以是在程序中加入外部程序某些功能的一种方式,但与运行及操纵外部程序并不完全相同。而只是链接并调用外部程序的库,与调用其他编程库无异。 | |
| 914 | 787 | ||
| 915 | - | ||
| 788 | + | ### Windows API 函数的替代方案 | |
| 916 | 789 | ||
| 917 | - | ||
| 790 | + | 许多 Windows 程序大量用到了 Windows API。在跨平台应用程序中,不应再调用 Windows 单元的 Win API 函数了,或者应该用条件编译指令(如 {$IFDEF MSWINDOWS})包裹起来。 | |
| 918 | 791 | ||
| 919 | - | ||
| 792 | + | 幸运的是,很多常用 Windows API 函数已在 [[lclintf]] 单元中以跨平台方式实现了。对于严重依赖 Windows API 的程序而言,这可能会是一种解决方案,尽管最好的解决方案是替换为 LCL 提供的真正跨平台的组件。比如可将 GDI 绘制函数替换为 TCanvas 对象方法。 | |
| 920 | 793 | ||
| 921 | - | ||
| 794 | + | ### 键盘码 | |
| 922 | 795 | ||
| 923 | - | ||
| 796 | + | 幸运的是,键盘按键编码检测方法(如 KeyUp 事件中的)是可移植的:请参阅 [[LCL Key Handling]]。 | |
| 924 | 797 | ||
| 925 | - | ||
| 798 | + | ### 安装跨平台应用程序 | |
| 926 | 799 | ||
| 927 | - | ||
| 800 | + | 请参阅 [[Deploying Your Application|发布应用程序]]。 | |
| 928 | 801 | ||
| 929 | 802 | ## 参见 | |
| 930 | 803 | ||
| 931 | - | ||
| 932 | - | ||
| 933 | - | ||
| 934 | - | ||
| 804 | + | * [[Writing portable code regarding the processor architecture|编写处理器架构无关的可移植代码]] | |
| 805 | + | * [[Introduction to platform-sensitive development|简介平台敏感型开发]] | |
| 806 | + | * [[Apple-specific UI elements|Apple 特有的 UI 元素]] | |
| 935 | 807 | ||
| 808 | + | ## 外部链接 | |
| 809 | + | ||
| 810 | + | * <http://www.midnightbeach.com/KylixForDelphiProgrammers.html> Windows 程序员的 Kylix 入门,很多概念和代码都适用于 Lazarus。 | |
| 811 | + | * <http://www.stack.nl/~marcov/porting.pdf> 可移植代码编写指南,主要关于各种编译器。 | |
| 812 | + | ||
| 936 | 813 | --- | |
| 937 | - | ||
| 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).* |
