Sign in Join
Wiki copytemporary

Multiplatform Programming Guide/zh CN: what changed

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

OldNewLine
11大多数 [[Lazarus Documentation/zh CN|LCL]] 可以跨平台使用,几乎不需要做修改。
22
33这是在 Lazarus 和 Free Pascal 上编写跨平台应用程序的教程。涵盖了必要的注意事项,从准备到[[Deploying Your Application|部署]]的相关帮助。
44
55## 跨平台编程简介
66
77### 你需要跨平台吗?
88
99要回答这个问题,你需要先确定哪些用户使用你的应用程序,以便在那里部署。
1010
1111在 2014 年,如果你正在开发通用桌面软件,微软的 Windows 可能是重要的平台。需要注意的是,Mac OS X 或 Linux 正变得流行,他们或许会成为你开发应用的另一个平台。
1212
1313不同的国家流行不同的桌面操作系统,按使用软件类型、人员,没有通用规则,像 Mac OS X在美国和西欧相当受欢迎,而在南美 Mac 大多用于视频和音频工作。
1414
1515在许多签约项目里,只与一个平台相关。Free Pascal 和 Lazarus 完全有能力针对特定平台编写软件。如,访问完整 Windows API 写一个 Windows 程序。
1616
1717If you're developing software that will run on a Web server, a Unix platform in one of its various flavors is commonly used. In this case, perhaps only Linux, Solaris, *BSD and other Unixes make sense as your target platforms, although you may want to add support for Windows for completeness. (~~如果你开发的软件运行在 Web 服务器上,在 Unix 平台,你可以将 Linux、Solaris、*BSD 和其他 Unix 作为目标平台,但你也想增加对 Windows 的完整支持。~~)
1818
1919一旦你解决了跨平台的问题,你可以在很大程度上忽略了其他平台,就像在一个平台开发。然而,在某些时候你需要测试部署在其他平台上的程序;像这样,你不想使用多个物理计算机,可以使用双引导、虚拟机VMware或类似的解决方案。这将有助于不受限制地访问目标操作系统。
2020
2121## 跨平台编程
2222
2323### 处理文件和文件夹
2424
2525在处理文件和文件夹时,使用于非特定平台的路径分隔符和[[End of Line|行结束]]符很重要。这个列表定义了 Lazarus 处理文件或文件夹时的[[Constant/zh CN|常量]]列表。
2626
2727- **PathSep**, **PathSeparator**: 多路径分隔符 (';', ...)
2828- **PathDelim**, **DirectorySeparator**: 各平台路径分隔符 ('/', '\\', ...)
2929- **LineEnding**: 行结束符 (#13#10 - CRLF, \#10 - [[Line feed|LF]], ...)
3030
3131要特别注意的是文件系统问题。
3232
3333在 Windows 中,文件名通常不区分大小写,但在 Linux 和 BSD 平台将区分。但,如果一个EXT2、EXT3等系统文件挂载到 Windows 中,这仍将区分大小写。Linux 挂载 FAT 文件系统却不区分大小写。
3434
3535It 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 系统时是区分大小写的。这将会导致各种问题,其中包括文件丢失。~~)
3636
3737Mac OS X 默认不区分文件名大小写,这可能是恼人错误的原因,所以任何便携式应用程序都应该坚持文件名大小写统一。
3838
3939The 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.
4040
4141(~~RTL(Free Pascal 运行库)文件使用系统编码。在 Windows 中是一代码页,在 Linux, BSD 和 Mac OS X 使用 UTF-8,LCL(Lazarus 组件库) 中的 **FileUtil**单元,提供了文件操作的相关功能。~~)
4242
4343``` pascal
4444// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, MacOSX
4545// but normally these OS use UTF-8 as system encoding so the widestringmanager
4646// 是不需要的。
4747function NeedRTLAnsi: boolean;// 为真,则系统编码不是 UTF-8
4848procedure SetNeedRTLAnsi(NewValue: boolean);
4949function UTF8ToSys(const s: string): string;// 作为 AnsiToUTF8 需要独立的 widestringmanager
5050function SysToUTF8(const s: string): string;// 作为 AnsiToUTF8 需要独立的 widestringmanager
5151function UTF8ToConsole(const s: string): string;//转换 UTF8 编码字符串到控制台编码(使用 Write, WriteLn)
5252
5353// 文件操作
5454function FileExistsUTF8(const Filename: string): boolean;
5555function FileAgeUTF8(const FileName: string): Longint;
5656function DirectoryExistsUTF8(const Directory: string): Boolean;
5757function ExpandFileNameUTF8(const FileName: string): string;
5858function ExpandUNCFileNameUTF8(const FileName: string): string;
5959function ExtractShortPathNameUTF8(Const FileName : String) : String;
6060function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;
6161function FindNextUTF8(var Rslt: TSearchRec): Longint;
6262procedure FindCloseUTF8(var F: TSearchrec);
6363function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;
6464function FileGetAttrUTF8(const FileName: String): Longint;
6565function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;
6666function DeleteFileUTF8(const FileName: String): Boolean;
6767function RenameFileUTF8(const OldName, NewName: String): Boolean;
6868function FileSearchUTF8(const Name, DirList : String): String;
6969function FileIsReadOnlyUTF8(const FileName: String): Boolean;
7070function GetCurrentDirUTF8: String;
7171function SetCurrentDirUTF8(const NewDir: String): Boolean;
7272function CreateDirUTF8(const NewDir: String): Boolean;
7373function RemoveDirUTF8(const Dir: String): Boolean;
7474function ForceDirectoriesUTF8(const Dir: string): Boolean;
7575
7676// 环境
7777function ParamStrUTF8(Param: Integer): string;
7878function GetEnvironmentStringUTF8(Index: Integer): string;
7979function GetEnvironmentVariableUTF8(const EnvVar: string): String;
8080function GetAppConfigDirUTF8(Global: Boolean): string;
8181
8282// 其他
8383function SysErrorMessageUTF8(ErrorCode: Integer): String;
8484```
8585
8686### 空文件名和路径双分隔符
8787
8888Windows 和 Linux/Unix/Unix 等系统在处理文件/文件夹名时有差异。
8989
9090- Windows 允许空文件名。这就是为什么 FileExistsUTF8('..\\') 可以检查空文件名的父目录。
9191- 在 Linux/Unix/类Unix系统空文件映射到目录上将被视为文件。这意味着,FileExistsUTF8('../') 将检查父目录,这会返回 真。
9292
9393文件名的双路径分隔符应该区别对待:
9494
9595- Windows:是 'C:\\' 不是 'C:\\\\'
9696- 类Unix系统:路径 '/usr//' 等同于 '/usr/',如果 '/usr' 是目录,则,也等同于上面。
9797
9898连接文件名时,要注意,如:
9999
100100``` pascal
101101FullFilename:=FilePath+PathDelim+ShortFilename; // 这会返回 2个 PathDelims ,在 Windows和 Linux 将导致不同结果
102102FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // 仅创建一个 PathDelim
103103FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // 仅创建 1个 PathDelim 并做些清理
104104```
105105
106106TrimFilename 函数将替换双路径分隔符为单分隔符,并缩短 '..' 路径,如,/usr//lib/../src 将修整为 /usr/src 。
107107
108108如果想判断一个目录是否可用,使用 **DirectoryExistsUTF8**。
109109
110110使用 ExtractFilePath 可以返回文件名的路径部分,并包含路径定界符。
111111
112112- 在类 Unix 系统上,可以使用 FileExistsUTF8 ,如 FileExistsUTF8('/home/user/') ,如果 /home/user 存在,将返回真。
113113- 在 Windows 下,你必须使用 DirectoryExistsUTF8 函数,但在此之前,你必须删除路径分隔符,如 ChompPathDelim 函数。
114114
115115在类Unix系统的根目录是 '/',使用 ChompPathDelim 将创建一个空字符串。DirPathExists 函数工作方式类同于 DirectoryExistsUTF8,将修剪给定的路径。
116116
117117需要注意的是,Unix/Linux 上使用 '~' (波浪号)代表用户目录,典型的例子,'/home/jim/' 将调用 jim 用户的文件。因此 '~/myapp/myfile' 和 '/home/jim/myapp/myfile' 在命令行和脚本中相同。然则,Lazarus 不会自动扩展波浪号。这需要使用 ExpandFileNameUTF8('~/myapp/myfile') 来获取完整路径。
118118
119119### 文本编码
120120
121121文本文件的编码通常是当前系统的编码。在Windows下,是代码页之一,而在Linux,BSD和Mac OS X,通常是 UTF-8编码。没有 100% 的规则找出文本文件的编码。LCL 的**lconvencoding** 有一个函数可以获得文件编码:
122122
123123``` pascal
124124function GuessEncoding(const s: string): string;
125125function GetDefaultTextEncoding: string;
126126```
127127
128128编码转换:
129129
130130``` pascal
131131function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;
132132
133133function UTF8BOMToUTF8(const s: string): string; // UTF8 与 BOM
134134function ISO_8859_1ToUTF8(const s: string): string; // 中欧
135135function CP1250ToUTF8(const s: string): string; // 中欧
136136function CP1251ToUTF8(const s: string): string; // 西里尔
137137function CP1252ToUTF8(const s: string): string; // 拉丁语1
138138...
139139function UTF8ToUTF8BOM(const s: string): string; // UTF8 与 BOM
140140function UTF8ToISO_8859_1(const s: string): string; // 中欧
141141function UTF8ToCP1250(const s: string): string; // 中欧
142142function UTF8ToCP1251(const s: string): string; // 西里尔
143143function UTF8ToCP1252(const s: string): string; // 拉丁语1
144144...
145145```
146146
147147如,加载一个文本文件并将其转换为 UTF-8 编码,你可以使用:
148148
149149``` pascal
150150var
151151 sl: TStringList;
152152 OriginalText: String;
153153 TextAsUTF8: String;
154154begin
155155 sl:=TStringList.Create;
156156 try
157157 sl.LoadFromFile('sometext.txt'); // 注意:此更改系统行行尾
158158 OriginalText:=sl.Text;
159159 TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);
160160 ...
161161 finally
162162 sl.Free;
163163 end;
164164end;
165165```
166166
167167保存文本文件到系统编码可以使用:
168168
169169``` pascal
170170sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);
171171sl.SaveToFile('sometext.txt');
172172```
173173
174174### 配置文件
175175
176176你可以使用 SysUtils 单元的 [[doc:rtl/sysutils/getappconfigdir.html|GetAppConfigDir]] 函数获取合适的位置存储系统配置文件。该函数有一个参数,称为全局。如果为真,则返回一个全局目录,即适用于系统上的所有用户。如果为参数为假,则为运行该程序的用户配置目录。在不支持多用户环境的系统,这两个目录是相同的。
177177
178178也可以使用 [[doc:rtl/sysutils/getappconfigfile.html|GetAppConfigFile]] 函数,将返回一个合适的配置文件名,你可以这样使用它:
179179
180180ConfigFilePath := GetAppConfigFile(False) + '.conf';
181181
182182以下是不同系统默认路径输出的例子:
183183
184184``` pascal
185185program project1;
186186
187187{$mode objfpc}{$H+}
188188
189189uses
190190 SysUtils;
191191
192192begin
193193 WriteLn(GetAppConfigDir(True));
194194 WriteLn(GetAppConfigDir(False));
195195 WriteLn(GetAppConfigFile(True));
196196 WriteLn(GetAppConfigFile(False));
197197end.
198198```
199199
200200在 GNU/Linux系统上FPC 2.2.2输出,为 TRUE 时会有问题,在 2.2.3 已经修复。
201201
202202 /etc/project1/
203203 /home/user/.config/project1/
204204 /etc/project1.cfg
205205 /home/user/.config/project1.cfg
206206
207207你可以看到,全局配置文件存储在 /etc 目录和本地配置存储在用户的主目录中隐藏文件夹。目录中,其名称以点(.)被隐藏在Linux上。你可以使用 GetAppConfigDir 返回的路径以创建目录,然后保存配置文件。
208208
209209> **Note:** 普通用户不允许写入 /etc 目录。只有拥有超级管理员的用户才可以写入。
210210
211211在 Windows XP 上的 FPC 2.2.4上输出:
212212
213213 C:\Documents and Settings\All Users\Application Data\project1\
214214 C:\Documents and Settings\user\Local Settings\Application Data\project1
215215 C:\Documents and Settings\All Users\Application Data\project1\project1.cfg
216216 C:\Documents and Settings\user\Local Settings\Application Data\project1\project1.cfg
217217
218218请注意,在 FPC 2.2.4 之前,全局配置在 Windows 目录。
219219
220220在 Windows 98 上的 FPC 2.2.0上输出:
221221
222222 C:\Program Files\PROJECT1
223223 C:\Windows\Local Settings\Application Data\PROJECT1
224224 C:\Program Files\PROJECT1\PROJECT1.cfg
225225 C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg
226226
227227在 Mac OS X 上的 FPC 2.2.0上输出:
228228
229229 /etc
230230 /Users/user/.config/project1
231231 /etc/project1.cfg
232232 /Users/user/.config/project1/project1.cfg
233233
234234> **Note:** 使用 UPX 会干扰 GetAppConfigDir 和 GetAppConfigFile 函数。
235235
236236> **Note:** 在 Mac OS X中,大多数情况下,配置文件是首选文件,是以 ".plist"结尾的XML文件,存放在 /Library/Preferences 或 ~/Library/Preferences中,应用程序包所用的名字来自 Info.plist 的 "Bundle identifier" 字段。 使用 Carbon 调用 CFPreference... 是实现这一目标最简单的方法。.config 文件在用户目录违反的设计指导。
237237
238238### 数据和资源文件
239239
240240一个常见的问题是应用程序需要的数据文件在哪里存储,像图像、音乐、XML文件、数据库文件、帮助文件等等。不幸的是,它没有跨平台的功能去取得最佳位置和寻找数据文件。解决办法是,在不同平台上使用 IFDEF。
241241
242242#### Windows
243243
244244在Windows上,数据文件不应该存放在应用程序目录 (如 C:\Program Files\\),应该在特定位置(见 [1](http://support.microsoft.com/kb/310294) 在"对应用程序数据进行分类")。Windows Vista 或较新的将强制 (用户只有 elevation 或 禁用 UAC时才可以写访问这些目录) 使用文件夹重定向机制,以适应旧的,错误的编程应用。只读不写数据,应用程序仍然可以正常工作。
245245
246246查看 [[Windows Programming Tips|获取特殊文件夹(我的文档、桌面、本地应用程序数据等)]]。
247247
248248#### Unix/Linux
249249
250250在大多数Unix系统(像Linux, FreeBSD, OpenBSD, Solaris),应用程序数据存储在一个固定的地方,它可以在 /usr/share/app_name 或 /opt/app_name。
251251
252252写入的数据通常存储在 /var/\<programname\>,具有适当的权限。
253253
254254用户特定的 读/写、配置/数据文件,将存储的用户主目录下(如存储在~/.myfancyprogram)。
255255
256256#### OSX
257257
258258Mac OS X是一个异常在UNIX系统。部署应用程序的最佳方法是使用一个应用程序包,其中包括你的软件需要的所有文件。你所谓的资源文件应该位于包内,即便被移动,它仍可以正常工作。您需要使用CoreFoundation API调用找到包的位置。将路径映射到 MyBundle.app/Contents/Resources。
259259
260260#### 示例
261261
262262本节介绍了一种特定解决方案,其中
263263
264264- Windows 下的数据文件与可执行文件存放在一起(或在此基础上的其他目录,像 ResourcesPath + 'data' + PathDelim + 'myfile.dat')
265265- 在 Unix系统中将从配置文件中读取一个目录。如果配置文件不存在或不包含信息,将使用 ('/usr/share/myapp/')做为默认目录。
266266
267267> **Warning:** 基于上面的解释,这段代码在 Windows 上是错误的——除非它是只读访问!
268268
269269通过 GetAppConfigFile 函数来获得配置文件路径。
270270
271271下面是一个完整的单元,可以在你的程序上使用。
272272
273273``` pascal
274274unit appsettings;
275275
276276interface
277277
278278{$ifdef fpc}
279279 {$mode delphi}{$H+}
280280{$endif}
281281
282282uses
283283 Classes, SysUtils, Forms, IniFiles, constants;
284284
285285type
286286
287287 { TConfigurations }
288288
289289 TConfigurations = class(TObject)
290290 private
291291 function GetResourcesPath: string;
292292 public
293293 {other settings as fields here}
294294 ConfigFilePath: string;
295295 ResourcesPath: string;
296296 constructor Create;
297297 destructor Destroy; override;
298298 procedure ReadFromFile(Sender: TObject);
299299 procedure Save(Sender: TObject);
300300 end;
301301
302302var
303303 vConfigurations: TConfigurations;
304304
305305implementation
306306
307307{$IFDEF Win32}
308308uses
309309 Windows;
310310{$ENDIF}
311311{$ifdef Darwin}
312312uses
313313 MacOSAll;
314314{$endif}
315315
316316const
317317 DefaultDirectory = '/usr/share/myapp/';
318318 BundleResourcesDirectory = '/Contents/Resources/';
319319
320320 SectionGeneral = 'General';
321321 SectionUnix = 'UNIX';
322322
323323 IdentResourcesPath = 'ResourcesPath';
324324
325325{ TConfigurations }
326326
327327constructor TConfigurations.Create;
328328begin
329329{$ifdef win32}
330330 ConfigFilePath := ExtractFilePath(Application.EXEName) + 'myapp.ini';
331331{$endif}
332332{$ifdef Unix}
333333 ConfigFilePath := GetAppConfigFile(False) + '.conf';
334334{$endif}
335335
336336 ResourcesPath := GetResourcesPath();
337337
338338 ReadFromFile(nil);
339339end;
340340
341341destructor TConfigurations.Destroy;
342342begin
343343 Save(nil);
344344
345345 inherited Destroy;
346346end;
347347
348348procedure TConfigurations.Save(Sender: TObject);
349349var
350350 MyFile: TIniFile;
351351begin
352352 MyFile := TIniFile.Create(ConfigFilePath);
353353 try
354354 MyFile.WriteString(SectionUnix, IdentResourcesPath, ResourcesPath);
355355 finally
356356 MyFile.Free;
357357 end;
358358end;
359359
360360procedure TConfigurations.ReadFromFile(Sender: TObject);
361361var
362362 MyFile: TIniFile;
363363begin
364364 MyFile := TIniFile.Create(ConfigFilePath);
365365 try
366366 // 在这里你可以从配置文件中读取其他信息
367367
368368{$ifdef Win32}
369369 ResourcesPath := MyFile.ReadString(SectionUnix, IdentResourcesPath,
370370ExtractFilePath(Application.EXEName));
371371{$else}
372372 {$ifndef darwin}
373373 ResourcesPath := MyFile.ReadString(SectionUnix, IdentResourcesPath,
374374DefaultDirectory);
375375 {$endif}
376376{$endif}
377377 finally
378378 MyFile.Free;
379379 end;
380380end;
381381
382382function TConfigurations.GetResourcesPath(): string;
383383begin
384384{$ifdef Darwin}
385385var
386386 pathRef: CFURLRef;
387387 pathCFStr: CFStringRef;
388388 pathStr: shortstring;
389389{$endif}
390390begin
391391{$ifdef UNIX}
392392{$ifdef Darwin}
393393 pathRef := CFBundleCopyBundleURL(CFBundleGetMainBundle());
394394 pathCFStr := CFURLCopyFileSystemPath(pathRef, kCFURLPOSIXPathStyle);
395395 CFStringGetPascalString(pathCFStr, @pathStr, 255, CFStringGetSystemEncoding());
396396 CFRelease(pathRef);
397397 CFRelease(pathCFStr);
398398
399399 Result := pathStr + BundleResourcesDirectory;
400400{$else}
401401 Result := DefaultDirectory;
402402{$endif}
403403{$endif}
404404
405405{$ifdef Windows}
406406 Result := ExtractFilePath(Application.EXEName);
407407{$endif}
408408end;
409409
410410initialization
411411
412412 vConfigurations := TConfigurations.Create;
413413
414414finalization
415415
416416 FreeAndNil(vConfigurations);
417417
418418end.
419419```
420420
421421这个代码示例,从正确的位置得到一个资源文件:
422422
423423``` pascal
424424bmp := TBitmap.Create
425425try
426426 bmp.LoadFromFile(vConfigurations.ResourcesPath + 'MyBitmap.bmp');
427427finally
428428 bmp.Free;
429429end;
430430```
431431
432432### 32/64 位
433433
434434#### 在运行时检测位数
435435
436436虽然你可以控制定义编译 32位或64位编译器,但有时你想知道程序运行在多少位操作系统上。 例如,你在64位的Windows系统上运行 32位的Lazarus 程序,你想在32位程序文件目录运行外部程序,或者你想给用户提供不同的信息:我需要在我的 LazUpdater 程序中为用户提供 32位或64位编译器的选择,如果合适的话。
437437
438438对于 Windows,FPC x86 编译器工作在我的 Vista x64上 —— 感谢 [Lazarus 德国论坛](http://www.lazarusforum.de/viewtopic.php?f=55&t=5287):
439439
440440``` pascal
441441program bitness;
442442
443443{$mode objfpc}{$H+}
444444{$APPTYPE CONSOLE}
445445uses {$IFDEF UNIX} {$IFDEF UseCThreads}
446446 cthreads, {$ENDIF} {$ENDIF}
447447 Classes,
448448 SysUtils,
449449 Windows;
450450
451451 function IsWindows64: boolean;
452452 {
453453 检测如果运行在 32/64 位Windows,
454454 独立于程序位数。
455455 原始来源:
456456 http://www.delphipraxis.net/118485-ermitteln-ob-32-bit-oder-64-bit-betriebssystem.html
457457 修改自 FreePascal 的Lazarus 德国论坛:
458458 http://www.lazarusforum.de/viewtopic.php?f=55&t=5287
459459 }
460460{$ifdef WIN32} //修改 KpjComp 的64位编译模式
461461 type
462462 TIsWow64Process = function( // IsWow64Process API 类型
463463 Handle: Windows.THandle; var Res: Windows.BOOL): Windows.BOOL; stdcall;
464464 var
465465 IsWow64Result: Windows.BOOL; // 返回 IsWow64Process
466466 IsWow64Process: TIsWow64Process; // IsWow64Process 函数参考
467467 begin
468468 // 尝试从 kernel32 加载所需功能
469469 IsWow64Process := TIsWow64Process(Windows.GetProcAddress(
470470 Windows.GetModuleHandle('kernel32'), 'IsWow64Process'));
471471 if Assigned(IsWow64Process) then
472472 begin
473473 // 功能实现:调用它
474474 if not IsWow64Process(Windows.GetCurrentProcess, IsWow64Result) then
475475 raise SysUtils.Exception.Create('IsWindows64: bad process handle');
476476 // 返回函数结果
477477 Result := IsWow64Result;
478478 end
479479 else
480480 // 功能未实现:不能运行在 Wow64
481481 Result := False;
482482{$else} //如果正在运行的是 64位代码,则系统必须是 64位 :)
483483 begin
484484 Result := True;
485485{$endif}
486486 end;
487487
488488begin
489489 try
490490 if IsWindows64 then
491491 begin
492492 writeln('This operating system is 64 bit.');
493493 end
494494 else
495495 begin
496496 writeln('This operating system is 32 bit.');
497497 end;
498498 except
499499 on E: exception do
500500 begin
501501 writeln('Could not determine bitness of operating system. Error details: ' + E.ClassName+'/'+E.Message);
502502 end;
503503 end;
504504end.
505505```
506506
507507#### 指针/整数类型转换
508508
509509指针在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位,并兼容所有平台。这意味着你不能在整型与指针间互转。~~)
510510
511511FPC 为此定义了两种类型: PtrInt 和 PtrUInt。 PtrInt 在32位平台是32位有符号整数,在64位上是64位有符号整数。PtrUInt同上,不同是无符号。
512512
513513工作在 Delphi 和 FPC 的代码:
514514
515-`{$IFNDEF FPC}`\
516-`type`\
517-`  PtrInt = integer;`\
518-`  PtrUInt = cardinal;`\
519-`{$ENDIF}`
515+``` pascal
516+{$IFNDEF FPC}
517+type
518+ PtrInt = integer;
519+ PtrUInt = cardinal;
520+{$ENDIF}
521+```
520522
521523替换所有 **integer(指针或对象)** 为 **PtrInt(指针或对象)**。
522524
523525### 字节顺序
524526
525527Intel 平台是little endian(小端字节序),这意味着,首位存放低字节。像,\$1234,存储方式为 \$34 \$12在小端字节序系统中。 在 big endian(大端字节序)系统,像powerpc ,\$1234 存储为 \$12 \$34。读取其他系统上的文件时,此不同处很重要。
526528
527529参见:[大端、小端的区别(Big-endian or Little-endian)](http://smilejay.com/2012/01/big-endian_little-endian/)
528530
529531下面代码在两种方式下工作:
530532
531533``` pascal
532534{$IFDEF ENDIAN_BIG}
533535...
534536{$ELSE}
535537...
536538{$ENDIF}
537539```
538540
539541此相反的是ENDIAN_LITTLE。
540542
541543系统单元提供了大量字节序转换功能,像 SwapEndian, BEtoN(大端转换小端)、LEtoN(小端转大端),NtoBE(转换为大端) 和 NtoLE(转换为小端)。
542544
543545#### Libc和其他特殊单位
544546
545547避免使用像"oldlinux" 和 "libc" 的单元,因为其在 linux/i386 以外不被支持。
546548
547549#### 汇编
548550
549551避免汇编。
550552
551553#### 编译器定义
552554
553555``` pascal
554556{$ifdef CPU32}
555557...这里写34位处理器代码
556558{$ENDIF}
557559{$ifdef CPU64}
558560...这里写64位处理器代码
559561{$ENDIF}
560562```
561563
562564### 项目、包和搜索路径
563565
564566Lazarus 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.
565567
566568Some advice to achieve this
567569
568570The 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.
569571
570572Every 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]]
571573
572574Every 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.
573575
574576#### 特定于平台的单元
575577
576578For example the unit wintricks.pas should only be used under Windows. In the uses section use:
577579
578580``` pascal
579581uses
580582 Classes, SysUtils
581583 {$IFDEF Windows}
582584 ,WinTricks
583585 {$ENDIF}
584586 ;
585587```
586588
587589If 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.
588590
589591See also [[Lazarus Packages|Platform specific units]]
590592
591593#### 特定平台搜索路径
592594
593595When 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:
594596
595597Create one file for each widget set you want to support:
596598
597-`win32/example.inc`\
598-`gtk/example.inc`\
599-`gtk2/example.inc`\
600-`carbon/example.inc`
599+```text
600+win32/example.inc
601+gtk/example.inc
602+gtk2/example.inc
603+carbon/example.inc
604+```
601605
602606You 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.
603607
604608In your unit use the directive: {\$I example.inc}
605609
606610Here are some useful macros and common values:
607611
608612- LCLWidgetType: win32, gtk, gtk2, qt, carbon, fpgui, nogui
609613- TargetOS: linux, win32, win64, wince, freebsd, netbsd, openbsd, darwin (many more)
610614- TargetCPU: i386, x86_64, arm, powerpc, sparc
611615- SrcOS: win, unix
612616
613617You can use the \$Env() macro to use environment variables.
614618
615619And of course you can use combinations. For example the LCL uses:
616620
617-`$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)`
621+```text
622+$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)
623+```
618624
619625See here the complete list of macros: [[IDE Macros in paths and filenames]]
620626
621627#### 计算机/用户的特定搜索路径
622628
623629For 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.
624630
625631If 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.
626632
627633### 区域差异
628634
629635Some 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.
630636
631637The next sections explain how to do that.
632638
633639#### StrToFloat
634640
635641A new set of format settings which set a fixed decimal separator can be created with the following code:
636642
637643``` pascal
638644// in your .lpr project file
639645uses
640646...
641647{$IFDEF UNIX}
642648clocale
643649{ required on Linux/Unix for formatsettings support. Should be one fo the first (probably after cthreads?}
644650{$ENDIF}
645651```
646652
647653and:
648654
649655``` pascal
650656// in your code:
651657var
652658 FPointSeparator, FCommaSeparator: TFormatSettings;
653659begin
654660 // Format seetings to convert a string to a float
655661 FPointSeparator := DefaultFormatSettings;
656662 FPointSeparator.DecimalSeparator := '.';
657663 FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
658664 FCommaSeparator := DefaultFormatSettings;
659665 FCommaSeparator.DecimalSeparator := ',';
660666 FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator
661667```
662668
663669Latter on you can use this format settings when calling StrToFloat, like this:
664670
665671``` pascal
666672// This function works like StrToFloat, but simply tries two possible decimal separator
667673// This will avoid an exception when the string format doesn't match the locale
668674function AnSemantico.StringToFloat(AStr: string): Double;
669675begin
670676 if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator)
671677 else Result := StrToFloat(AStr, FCommaSeparator);
672678end;
673679```
674680
675681### GTK2 和屏蔽 FPU 异常
676682
677683Gtk2 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.
678684
679685After [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.
680686
681687Therefore, let's do a test:
682688
683689``` pascal
684690uses
685691 ..., math,...
686692
687693{...}
688694
689695var
690696 FPUException: TFPUException;
691697 FPUExceptionMask: TFPUExceptionMask;
692698begin
693699 FPUExceptionMask := GetExceptionMask;
694700 for FPUException := Low(TFPUException) to High(TFPUException) do begin
695701 write(FPUException, ' - ');
696702 if not (FPUException in FPUExceptionMask) then
697703 write('not ');
698704
699705 writeln('masked!');
700706 end;
701707 readln;
702708end.
703709```
704710
705711Our simple program will get what FPC default is:
706712
707-`exInvalidOp - not masked!`\
708-`exDenormalized - masked!`\
709-`exZeroDivide - not masked!`\
710-`exOverflow - not masked!`\
711-`exUnderflow - masked!`\
712-`exPrecision - masked!`\
713+```text
714+exInvalidOp - not masked!
715+exDenormalized - masked!
716+exZeroDivide - not masked!
717+exOverflow - not masked!
718+exUnderflow - masked!
719+exPrecision - masked!
720+```
713721
714722However, with Gtk2, only exOverflow is not masked.
715723
716724The 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:
717725
718726``` pascal
719727var
720728 X, A, B: Double;
721729// ...
722730
723731try
724732 X := A / B;
725733 // code block 1
726734except
727735 // code block 2
728736end;
729737// ...
730738```
731739
732740will 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.
733741
734742We 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:
735743
736744``` pascal
737745uses
738746 ..., math,...
739747
740748//...
741749var
742750 X, A, B: Double;
743751 Ind: Boolean;
744752// ...
745753try
746754 X := A / B;
747755 Ind := IsInfinite(X) or IsNan(X); // with gtk2, we fall here
748756except
749757 Ind := True; // in windows, we fall here when B equals zero
750758end;
751759if Ind then begin
752760 // code block 2
753761end else begin
754762 // code block 1
755763end;
756764// ...
757765```
758766
759767Or:
760768
761769``` pascal
762770uses
763771 ..., math,...
764772
765773//...
766774var
767775 X, A, B: Double;
768776 FPUExceptionMask: TFPUExceptionMask;
769777// ...
770778
771779try
772780 FPUExceptionMask := GetExceptionMask;
773781 SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // unmask
774782 try
775783 X := A / B;
776784 finally
777785 SetExceptionMask(FPUExceptionMask); // return previous masking immediately, we must not let Gtk2 internals to be called without the mask
778786 end;
779787 // code block 1
780788except
781789 // code block 2
782790end;
783791// ...
784792```
785793
786794Be cautious, do not do something like this (call LCL with still removed mask):
787795
788796``` pascal
789797try
790798 FPUExceptionMask := GetExceptionMask;
791799 SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
792800 try
793801 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!
794802 finally
795803 SetExceptionMask(FPUExceptionMask);
796804 end;
797805 // code block 1
798806except
799807 // code block 2
800808end;
801809// ...
802810```
803811
804812But use an auxiliary variable:
805813
806814``` pascal
807815try
808816 FPUExceptionMask := GetExceptionMask;
809817 SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
810818 try
811819 X := A / B; // First, we set auxiliary variable X
812820 finally
813821 SetExceptionMask(FPUExceptionMask);
814822 end;
815823 Edit1.Text := FloatToStr(X); // Now we can set Edit's text.
816824 // code block 1
817825except
818826 // code block 2
819827end;
820828// ...
821829```
822830
823831In 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.
824832
825833## 从 Windows 迁移到 *nix 的问题
826834
827835Issues specific to Linux, OSX, Android and other Unixes are described here. Not all subjects may apply to all platforms
828836
829837### 在Unix上没有“应用程序目录”
830838
831839Many 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).
832840
833841Even 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).
834842
835843To avoid this read the sections about [[Multiplatform Programming Guide|configuration files]] and [[Multiplatform Programming Guide|data files]].
836844
837845### Windows COM 没有自动化
838846
839847With 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).
840848
841849#### 替代 OSX
842850
843851Unfortunately, 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.
844852
845853AppleScript 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):
846854
847-` tell application "NeoOffice"`\
848-`   launch`\
849-` end tell`
855+``` pascal
856+ tell application "NeoOffice"
857+ launch
858+ end tell
859+```
850860
851861An 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:
852862
853-` Shell('myscript.applescript');`
863+```text
864+ Shell('myscript.applescript');
865+```
854866
855867This 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:
856868
857-` Shell('osascript -e '#39'tell application "NeoOffice"'#39 +`\
858-`       ' -e '#39'launch'#39' -e '#39'end tell'#39);`\
859-`       {Note use of #39 to single-quote the parameters}`
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}
873+```
860874
861875However, these examples are just the equivalent of the following Open command:
862876
863-` Shell('open -a NeoOffice');`
877+```text
878+ Shell('open -a NeoOffice');
879+```
864880
865881Similarly, in OS X you can emulate the Windows shell commands to launch a web browser and launch an email client with:
866882
867-` fpsystem('open -a safari "`[`http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html`](http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html)`"');`
883+```text
884+ fpsystem('open -a safari "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');
885+```
868886
869887and
870888
871-` fpsystem('open -a mail "`[`mailto:ss4200@invalid.org`](mailto:ss4200@invalid.org)`"');`
889+```text
890+ fpsystem('open -a mail "mailto:ss4200@invalid.org"');
891+```
872892
873893which 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:
874894
875-` fpsystem('open "`[`http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html`](http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html)`"');`
895+```text
896+ fpsystem('open "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');
897+```
876898
877899and
878900
879-` fpsystem('open "`[`mailto:ss4200@invalid.org`](mailto:ss4200@invalid.org)`"');`
901+```text
902+ fpsystem('open "mailto:ss4200@invalid.org"');
903+```
880904
881905Do not forget to include the Unix unit in your uses clause if you use `fpsystem` or `shell` (interchangeable).
882906
883907The 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.
884908
885909#### Linux 替代品
886910
887911While 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).
888912
889913As 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.
890914
891915### Windows API 函数的替代品
892916
893917Many 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} ).
894918
895919Fortunately 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.
896920
897921### 关键代码
898922
899923Fortunately, detecting key codes (e.g. on KeyUp events) is portable: see [[LCL Key Handling]].
900924
901925### 安装跨平台应用
902926
903927See [[Deploying Your Application]]
904928
905929## 参见
906930
907931- [[Writing portable code regarding the processor architecture]]
908932- [[Introduction to platform-sensitive development]]
909933- <http://www.midnightbeach.com/KylixForDelphiProgrammers.html> A guide for Windows programmers starting with Kylix. Many of concepts / code snippets apply to Lazarus.
910934- <http://www.stack.nl/~marcov/porting.pdf> A guide for writing portable source code, mainly between different compilers.
911935
912936---
913937*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).*

Back to history