Sign in Join
Wiki copytemporary

Multiplatform Programming Guide/ru: what changed

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

Title: Multiplatform Programming Guide/ru

OldNewLine
1+Большинство [[LCL]] приложений работают в кроссплатформенном режиме без каких-либо дополнительных усилий.
2+
3+Это руководство по написанию кроссплатформенных приложений с использованием Lazarus и Free Pascal. Он будет охватывать необходимые меры предосторожности, чтобы помочь в создании кроссплатформенной программы, готовой к [[Deploying Your Application|Развертывание вашего приложения]].
4+
5+## Введение в мультиплатформенное (кроссплатформенное) программирование
6+
7+### Сколько платформ вам нужно?
8+
9+Чтобы ответить на этот вопрос, вы должны сначала определить, кто ваши потенциальные пользователи и как ваша программа будет использоваться. Этот вопрос зависит от того, где вы развертываете свое приложение.
10+
11+Если вы разрабатываете стандартное настольное программное обеспечение в 2014 году, Microsoft Windows может быть самой важной платформой. Обратите внимание, что macOS и/или Linux набирают популярность и могут стать важной целью для вашего приложения.
12+
13+Популярность различных операционных систем для настольных компьютеров отличается в зависимости от страны, типа используемого программного обеспечения и целевой аудитории; тут нет общего правила. Например, macOS довольно популярна в Северной Америке и Западной Европе, в то время как в Южной Америке компьютеры Mac в основном ограничены работой с видео и звуком.
14+
15+Для многих контрактных проектов важна только одна платформа. Free Pascal и Lazarus вполне способны писать программы, ориентированные на конкретную платформу. Вы можете, например, получить доступ ко всему API Windows, чтобы написать хорошо интегрированную программу Windows.
16+
17+Если вы разрабатываете программное обеспечение, которое будет работать на веб-сервере, обычно используется платформа Unix в одном из ее различных вариантов. В этом случае, возможно, только Linux, Solaris, * BSD и другие Unix-системы имеют смысл в качестве целевых платформ, хотя вы можете добавить поддержку Windows для полноты \[картины\].
18+
19+Решив любые кросс-платформенные проблемы в своем дизайне, вы можете в значительной степени игнорировать другие платформы, так же, как и при разработке для одной платформы. Однако в какой-то момент вам нужно будет протестировать развертывание и запуск вашей программы на других платформах. Для этого будет полезно иметь неограниченный доступ к машинам с целевыми операционными системами. Если вам не нужны несколько физических компьютеров, попробуйте решения с двойной загрузкой или виртуальные машины, такие как VMware или Parallels.
20+
21+## Кроссплатформенное программирование
22+
23+### Работа с файлами и папками
24+
25+При работе с файлами и папками важно использовать платформо-независимые разделители путей и \[маркеры\] [[End of Line/ru|конца строки]]. Вот список объявленных [[Constant/ru|констант]] в Lazarus, которые будут использоваться при работе с файлами и папками.
26+
27+- **PathSep**, **PathSeparator**: разделитель пути при добавлении нескольких путей вместе (';', ...)
28+- **PathDelim**, **DirectorySeparator**: разделитель каталогов для каждой платформы ('/', '\\', ...)
29+- **LineEnding**: правильная последовательность символов окончания строки (#13#10 - CRLF, \#10 - [[Line feed/ru|LF]], ...)
30+
31+Еще одна важная вещь, которую следует отметить, - это чувствительность к регистру \[имен файлов и каталогов\] файловой системы. В Windows имена файлов обычно не чувствительны к регистру, в то время как они обычно \[регистрозависимы\] на платформах Linux и BSD. Но если файловая система EXT2, EXT3 и т.д. смонтирована в Windows, она будет чувствительна к регистру. Соответственно, файловая система FAT, смонтированная в Linux, не должна учитывать регистр символов.
32+
33+Следует обратить особое внимание, что NTFS не чувствительна к регистру при использовании в Windows, но она чувствительна к регистру при монтировании ОС POSIX. Это может вызвать различные проблемы, в том числе потерю файлов, если файлы с одинаковыми именами файлов в разных случаях существуют в разделе NTFS, смонтированном в Windows. Разработчики должны рассмотреть возможность использования пользовательских функций для проверки и предотвращения создания нескольких файлов с одинаковыми именами в NTFS.
34+
35+macOS по умолчанию использует имена файлов без учета регистра. Это может быть причиной досадных ошибок, поэтому любое переносимое приложение должно постоянно использовать имена файлов.
36+
37+RTL-функции файлов используют системную кодировку для имен файлов. Под Windows это одна из кодовых страниц Windows, в то время как Linux, BSD и macOS обычно используют UTF-8. Модуль **FileUtil** \[библиотеки\] LCL предоставляет файловые функции, которые принимают строки UTF-8, как и остальная часть LCL.
38+
39+``` pascal
40+// [функциям] AnsiToUTF8 и UTF8ToAnsi нужен менеджер широких строк (widestringmanager) под Linux, BSD, macOS,
41+// но обычно эти ОС используют UTF-8 в качестве системной кодировки, поэтому [там] менеджер широких строк
42+// не нужен.
43+function NeedRTLAnsi: boolean;// true, если системная кодировка не UTF-8
44+procedure SetNeedRTLAnsi(NewValue: boolean);
45+function UTF8ToSys(const s: string): string;// как UTF8ToAnsi, но более не зависим от widestringmanager
46+function SysToUTF8(const s: string): string;// как AnsiToUTF8, но более не зависим от widestringmanager
47+function UTF8ToConsole(const s: string): string;// преобразовывает строку UTF8 в консольную кодировку (используется Write, WriteLn)
48+
49+// файловые операции
50+function FileExistsUTF8(const Filename: string): boolean;
51+function FileAgeUTF8(const FileName: string): Longint;
52+function DirectoryExistsUTF8(const Directory: string): Boolean;
53+function ExpandFileNameUTF8(const FileName: string): string;
54+function ExpandUNCFileNameUTF8(const FileName: string): string;
55+function ExtractShortPathNameUTF8(Const FileName : String) : String;
56+function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;
57+function FindNextUTF8(var Rslt: TSearchRec): Longint;
58+procedure FindCloseUTF8(var F: TSearchrec);
59+function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;
60+function FileGetAttrUTF8(const FileName: String): Longint;
61+function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;
62+function DeleteFileUTF8(const FileName: String): Boolean;
63+function RenameFileUTF8(const OldName, NewName: String): Boolean;
64+function FileSearchUTF8(const Name, DirList : String): String;
65+function FileIsReadOnlyUTF8(const FileName: String): Boolean;
66+function GetCurrentDirUTF8: String;
67+function SetCurrentDirUTF8(const NewDir: String): Boolean;
68+function CreateDirUTF8(const NewDir: String): Boolean;
69+function RemoveDirUTF8(const Dir: String): Boolean;
70+function ForceDirectoriesUTF8(const Dir: string): Boolean;
71+
72+// окружение
73+function ParamStrUTF8(Param: Integer): string;
74+function GetEnvironmentStringUTF8(Index: Integer): string;
75+function GetEnvironmentVariableUTF8(const EnvVar: string): String;
76+function GetAppConfigDirUTF8(Global: Boolean): string;
77+
78+// другое
79+function SysErrorMessageUTF8(ErrorCode: Integer): String;
80+```
81+
82+------------------------------------------------------------------------
83+
84+Прим.перев.: после появления поддержки юникода на уровне компилятора (FPC 2.7.1) вместо AnsiToUTF8, UTF8ToAnsi, SysToUTF8, UTF8ToAnsi для работы с WinAPI рекомендуется использовать функции **UTF8ToWinCP** и **WinCPToUTF8**. Подробнее здесь: [[LCL Unicode Support/ru|RTL с кодовой страницей UTF-8 по умолчанию]]
85+
86+------------------------------------------------------------------------
87+
88+### Пустые имена файлов и двойные разделители путей
89+
90+Существуют различия в обработке имен файлов/каталогов в Windows по сравнению с Linux/Unix/Unix-подобными системами.
91+
92+- Windows позволяет пустые имена файлов. Вот почему FileExistsUTF8 ('..\\') проверяет в Windows в родительском каталоге наличие файла без имени.
93+- В Linux/Unix/Unix-подобных системах пустой файл сопоставляется с каталогом, а каталоги рассматриваются как файлы. Это означает, что FileExistsUTF8 ('../') в Unix проверяет наличие родительского каталога, что обычно приводит к значению true.
94+
95+Двойные разделители пути в именах файлов также обрабатываются по-разному:
96+
97+- Windows: 'C:\\' не то же самое, что 'C:\\\\'
98+- Unix-подобные OS: путь '/usr//' совпадает с '/usr/'. Если '/usr' является каталогом, то даже все три равнозначны.
99+
100+Это важно при объединении имен файлов. Например:
101+
102+``` pascal
103+FullFilename:=FilePath+PathDelim+ShortFilename; // может привести к двум PathDelims, которые дают разные результаты под Windows и Linux
104+FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // создает только один PathDelim
105+FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // создает только один PathDelim и делает еще несколько чисток
106+```
107+
108+------------------------------------------------------------------------
109+
110+Прим.перев.: для "нормализации" количества и вида разделителей в путях папок и файлов можно использовать следующие функции из модуля `LazFileUtils`
111+
112+``` pascal
113+function CleanAndExpandDirectory(const Filename: string): string;
114+function CleanAndExpandFilename(const Filename: string): string;
115+```
116+
117+------------------------------------------------------------------------
118+
119+Функция TrimFilename заменяет разделители двойных путей одиночными и сокращает пути '..'. Например /usr//lib/../src обрезается до /usr/src.
120+
121+Если вы хотите узнать, существует ли каталог, используйте **DirectoryExistsUTF8**.
122+
123+Другая распространенная задача - проверить, существует ли часть пути имени файла. Вы можете получить путь с помощью ExtractFilePath, но он будет содержать разделитель пути.
124+
125+- Под Unix-подобной системой вы можете просто использовать FileExistsUTF8 в пути. Например, FileExistsUTF8('/home/user/') вернет true, если каталог /home/user существует.
126+- В Windows вы должны использовать функцию DirectoryExistsUTF8, но перед этим вы должны удалить разделитель пути, например, с помощью функции ChompPathDelim.
127+
128+В Unix-подобных системах корневым каталогом является '/', а использование функции ChompPathDelim создаст пустую строку. Функция DirPathExists работает как функция DirectoryExistsUTF8, но обрезает заданный путь.
129+
130+Обратите внимание, что Unix/Linux использует символ '~' (тильда) для обозначения домашнего каталога, обычно '/home/jim/' для пользователя с именем jim. Так что '~/myapp/myfile' и '/home/jim/myapp/myfile' идентичны в командной строке и в скриптах. Тем не менее, тильда не будет автоматически расширяться Lazarus'ом. Необходимо использовать ExpandFileNameUTF8('~/myapp/myfile'), чтобы получить полный путь.
131+
132+------------------------------------------------------------------------
133+
134+Прим.перев.: Если code completion "не находит" выше описанные функции, добавьте в секцию uses модули **LazUTF8** и **LazFileUtils**.
135+
136+------------------------------------------------------------------------
137+
138+### Кодировка текста
139+
140+Текстовые файлы часто кодируются в текущей кодировке системы. В Windows это обычно одна из кодовых страниц Windows, в то время как Linux, BSD и macOS обычно используют UTF-8. Не существует 100%-го правила, чтобы узнать, какую кодировку использует текстовый файл. Модуль LCL **lconvencoding** имеет функцию для угадывания кодировки:
141+
142+``` pascal
143+function GuessEncoding(const s: string): string;
144+function GetDefaultTextEncoding: string;
145+```
146+
147+И он содержит функции для преобразования из одной кодировки в другую:
148+
149+``` pascal
150+function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;
151+
152+function UTF8BOMToUTF8(const s: string): string; // UTF8 с BOM
153+function ISO_8859_1ToUTF8(const s: string): string; // Центральная Европа
154+function CP1250ToUTF8(const s: string): string; // Центральная Европа
155+function CP1251ToUTF8(const s: string): string; // кириллица
156+function CP1252ToUTF8(const s: string): string; // latin 1
157+...
158+function UTF8ToUTF8BOM(const s: string): string; // UTF8 с BOM
159+function UTF8ToISO_8859_1(const s: string): string; // Центральная Европа
160+function UTF8ToCP1250(const s: string): string; // Центральная Европа
161+function UTF8ToCP1251(const s: string): string; // кириллица
162+function UTF8ToCP1252(const s: string): string; // latin 1
163+...
164+```
165+
166+Например, чтобы загрузить текстовый файл и преобразовать его в UTF-8, вы можете использовать:
167+
168+``` pascal
169+var
170+ sl: TStringList;
171+ OriginalText: String;
172+ TextAsUTF8: String;
173+begin
174+ sl:=TStringList.Create;
175+ try
176+ sl.LoadFromFile('sometext.txt'); // осторожно: это изменяет конец строки на конец строки OCи
177+ OriginalText:=sl.Text;
178+ TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);
179+ ...
180+ finally
181+ sl.Free;
182+ end;
183+end;
184+```
185+
186+А для сохранения текстового файла в системной кодировке вы можете использовать:
187+
188+``` pascal
189+sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);
190+sl.SaveToFile('sometext.txt');
191+```
192+
193+### Конфигурационные файлы
194+
195+Вы можете использовать функцию [[doc:rtl/sysutils/getappconfigdir.html|GetAppConfigDir]] из модуля SysUtils, чтобы найти подходящее место для хранения файлов конфигурации в другой системе. Функция имеет один параметр, называемый Global. Если \[его значение\] - True, то возвращаемый каталог является глобальным каталогом, то есть действительным для всех пользователей в системе. Если параметр Global имеет значение false, тогда каталог является специфическим для пользователя, выполняющего программу. В системах, которые не поддерживают многопользовательские среды, эти два каталога могут быть одинаковыми.
196+
197+Существует также \[функция\] [[doc:rtl/sysutils/getappconfigfile.html|GetAppConfigFile]], который возвращает соответствующее имя для файла конфигурации приложения. Вы можете использовать это так:
198+
199+``` pascal
200+ConfigFilePath := GetAppConfigFile(False);
201+```
202+
203+Ниже приведены примеры вывода функций пути по умолчанию в различных системах:
204+
205+``` pascal
206+program project1;
207+
208+{$mode objfpc}{$H+}
209+
210+uses
211+ SysUtils;
212+
213+begin
214+ WriteLn(GetAppConfigDir(True));
215+ WriteLn(GetAppConfigDir(False));
216+ WriteLn(GetAppConfigFile(True));
217+ WriteLn(GetAppConfigFile(False));
218+end.
219+```
220+
221+Вывод в системе GNU/Linux с FPC 2.2.2. Обратите внимание, что использование True является ошибкой, что уже исправлено в 2.2.3:
222+
223+``` bash
224+/etc/project1/
225+/home/user/.config/project1/
226+/etc/project1.cfg
227+/home/user/.config/project1.cfg
228+```
229+
230+Вы можете заметить, что глобальные файлы конфигурации хранятся в каталоге /etc, а локальные конфигурации хранятся в скрытой папке в домашнем каталоге пользователя. Каталоги, имя которых начинается с точки (.), скрыты в Linux. Вы можете создать каталог в месте, возвращаемом GetAppConfigDir, а затем сохранить там файлы конфигурации.
231+
232+> **Note:** Обычные пользователи не могут писать в каталог /etc. Это могут делать только пользователи с правами администратора.
233+
234+Вывод на последние версии Windows с FPC 3.0.0 +:
235+
236+ C:\ProgramData\project1\
237+ C:\Users\user\AppData\Local\project1\
238+ C:\ProgramData\project1\project1.cfg
239+ C:\Users\user\AppData\Local\project1\project1.cfg
240+
241+Обратите внимание, что до FPC 2.2.4 функция использовала каталог, в котором приложение должно было хранить глобальные конфигурации в Windows.
242+
243+Вывод на Windows 98 с FPC 2.2.0:
244+
245+ C:\Program Files\PROJECT1
246+ C:\Windows\Local Settings\Application Data\PROJECT1
247+ C:\Program Files\PROJECT1\PROJECT1.cfg
248+ C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg
249+
250+Вывод на macOS с FPC 2.2.0:
251+
252+ /etc
253+ /Users/user/.config/project1
254+ /etc/project1.cfg
255+ /Users/user/.config/project1/project1.cfg
256+
257+> **Note:** Использование UPX мешает использованию функций GetAppConfigDir и GetAppConfigFile.
258+
259+------------------------------------------------------------------------
260+
261+Прим.перев.: Очевидно, имеется ввиду библиотека упаковщика исполняемых файлов - [UPX (the Ultimate Packer for eXecutables)](https://ru.wikipedia.org/wiki/UPX)
262+
263+------------------------------------------------------------------------
264+
265+> **Note:** В macOS файлы конфигурации в большинстве случаев являются файлами предпочтений, которые должны быть файлами XML с окончанием ".plist" и храниться в /Library/Preferences или ~/Library/Preferences с именами, взятыми из поля "Bundle identifier"(Идентификатор пакета) в Info.plist пакета приложения. Использование вызовов Carbon CFPreference ... вероятно, самый простой способ добиться этого. Файлы .config в каталоге User являются нарушением указаний по программированию.
266+
267+### Файлы данных и ресурсов
268+
269+Очень распространенный вопрос - где хранить файлы данных, которые могут понадобиться приложению, такие как изображения, музыка, файлы XML, файлы базы данных, файлы справки и т.д. К сожалению, нет кроссплатформенной функции, которая бы обеспечивала лучшее место для поиска файлов данных. Решение состоит в том, чтобы реализовать по-разному на каждой платформе, используя \[директивы условной компиляции\] IFDEF.
270+
271+#### Windows
272+
273+В Windows данные приложения, которые изменяет программа, должны быть помещены не в каталог приложения (например, C:\Program Files\\), а в определенном месте (см., например, [Как написать приложение для Windows XP](http://support.microsoft.com/kb/310294), в разделе "Classify Application Data"). Windows Vista и новее активно применяют это (пользователи имеют доступ на запись в эти каталоги только при использовании повышения \[прав\] или отключения UAC), но используют механизм перенаправления папок для поддержки старых, неправильно запрограммированных приложений. Просто чтение, а не запись данных из каталогов приложений все равно будет работать.
274+
275+Короче говоря: используйте такую папку:
276+
277+``` pascal
278+ OpDirLocal:= GetEnvironmentVariableUTF8('appdata')+'\MyAppName';
279+```
280+
281+См. [[Windows Programming Tips|Советы по программированию Windows: получение специальных папок (Мои документы, Рабочий стол, локальные данные приложения и т.д.)]].
282+
283+#### Unix/Linux
284+
285+В большинстве Unix-систем (таких как Linux, FreeBSD, OpenBSD, Solaris) файлы данных приложения расположены в фиксированном месте, которое может выглядеть примерно так: /usr/share/app_name или /opt/app_name.
286+
287+Данные приложения, которые должны быть записаны приложением, часто хранятся в таких местах, как /var/\<programname\>, с установленными соответствующими разрешениями.
288+
289+Специфичные для пользователя конфигурации/данные для чтения/записи обычно хранятся где-то в домашнем каталоге пользователя (например, в ~/.myfancyprogram).
290+
291+Как получить путь к этому домашнему каталогу:
292+
293+``` pascal
294+ OpDirLocal:= GetEnvironmentVariableUTF8('HOME')+'/.myappname';
295+```
296+
297+#### macOS
298+
299+macOS является исключением среди UNIX. Приложение публикуется в связке - каталоге с расширением "app", которое обрабатывается файловым менеджером как файл (вы также можете сделать "cd path/myapp.app"). Ваши файлы ресурсов должны находиться внутри пакета. Если пакет - "path/MyApp.app", то:
300+
301+- исполняемый файл - "path/MyApp.app/Contents/MacOS/myapp"
302+- каталог ресурсов - "path/MyApp.app/Contents/Resources"
303+
304+Сохраните файлы конфигурации в домашний каталог:
305+
306+``` pascal
307+ OpDirLocal:= GetEnvironmentVariableUTF8('HOME')+'/.myappname';
308+```
309+
310+Читайте ресурсы с:
311+
312+``` pascal
313+ OpDirRes:= ExtractFileDir(ExtractFileDir(Application.ExeName))+'/Resources';
314+```
315+
316+> **Warning:** **никогда** не используйте `paramstr(0)` на любой платформе Unix для определения расположения исполняемого файла, так как это соглашение Dos-Windows-OS/2 и имеет несколько концептуальных проблем, которые не могут быть решены с помощью эмуляции на других платформ. Единственное, что paramstr(0) гарантированно возвращает на платформах Unix, это имя, под которым была запущена программа. Каталог, в котором он находится, и имя фактического бинарного файла (в случае, если он был запущен с использованием символической ссылки) не обязательно будут доступны через paramstr(0).
317+
318+### 32/64 bit
319+
320+#### Обнаружение разрядности ОСи во время выполнения
321+
322+Хотя вы можете контролировать, компилируете ли вы 32- или 64-битную версию с помощью определений компилятора, иногда вы захотите знать, какова разрядность операционной системы. Например, если вы запускаете 32-битную программу Lazarus в 64-битной Windows, вы можете запустить внешнюю программу из каталога Program Files (x86) для 32-битных программ или вы можете захотеть получить различную информацию пользователя: мне это нужно в моей программе установки Лазаруса LazUpdater, чтобы предложить пользователю выбор 32- и 64-битных компиляторов. Код: [[Detect Windows x32-x64 example|пример определения Windows x32-x64]].
323+
324+#### Обнаружение разрядности внешней библиотеки перед ее загрузкой
325+
326+Если вы хотите загрузить функции из динамической библиотеки в вашу программу, она должна иметь ту же разрядность, что и ваше приложение. В 64-битной Windows ваше приложение может быть 32-битным или 64-битным, и в вашей системе могут быть 32-битные и 64-битные библиотеки. Поэтому вы можете проверить, является ли разрядность dll такой же, как разрядность вашего приложения, прежде чем загружать dll динамически. Вот функция, которая проверяет разрядность dll, ([предоставленная на форуме пользователем GetMem](http://forum.lazarus.freepascal.org/index.php/topic,36834.msg245859.html#msg245859)):
327+
328+``` pascal
329+
330+uses {..., } JwaWindows;
331+
332+function GetPEType(const APath: WideString): Byte;
333+const
334+ PE_UNKNOWN = 0; // если файл не является корректной dll, возвращается 0
335+ // PE_16BIT = 1; // не поддерживается этой функцией
336+ PE_32BIT = 2;
337+ PE_64BIT = 3;
338+var
339+ hFile, hFileMap: THandle;
340+ PMapView: Pointer;
341+ PIDH: PImageDosHeader;
342+ PINTH: PImageNtHeaders;
343+ Base: Pointer;
344+begin
345+ Result := PE_UNKNOWN;
346+
347+ hFile := CreateFileW(PWideChar(APath), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
348+ if hFile = INVALID_HANDLE_VALUE then
349+ begin
350+ CloseHandle(hFile);
351+ Exit;
352+ end;
353+
354+ hFileMap := CreateFileMapping(hFile, nil, PAGE_READONLY, 0, 0, nil);
355+ if hFileMap = 0 then
356+ begin
357+ CloseHandle(hFile);
358+ CloseHandle(hFileMap);
359+ Exit;
360+ end;
361+
362+ PMapView := MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
363+ if PMapView = nil then
364+ begin
365+ CloseHandle(hFile);
366+ CloseHandle(hFileMap);
367+ Exit;
368+ end;
369+
370+ PIDH := PImageDosHeader(PMapView);
371+ if PIDH^.e_magic <> IMAGE_DOS_SIGNATURE then
372+ begin
373+ CloseHandle(hFile);
374+ CloseHandle(hFileMap);
375+ UnmapViewOfFile(PMapView);
376+ Exit;
377+ end;
378+
379+ Base := PIDH;
380+ PINTH := PIMAGENTHEADERS(Base + LongWord(PIDH^.e_lfanew));
381+ if PINTH^.Signature = IMAGE_NT_SIGNATURE then
382+ begin
383+ case PINTH^.OptionalHeader.Magic of
384+ $10b: Result := PE_32BIT;
385+ $20b: Result := PE_64BIT
386+ end;
387+ end;
388+
389+ CloseHandle(hFile);
390+ CloseHandle(hFileMap);
391+ UnmapViewOfFile(PMapView);
392+end;
393+
394+//Теперь, если вы компилируете свое приложение для 32-битной и 64-битной windows, вы можете проверить, является ли разрядность dll такой же, как у вашего приложения:
395+function IsCorrectBitness(const APath: WideString): Boolean;
396+begin
397+ {$ifdef CPU32}
398+ Result := GetPEType(APath) = 2; //приложение скомпилировано как 32-битное, мы спрашиваем, возвращает ли GetPeType 2
399+ {$endif}
400+ {$ifdef CPU64}
401+ Result := GetPEType(APath) = 3; //приложение скомпилировано как 64-битное, мы спрашиваем, возвращает ли GetPeType 3
402+ {$endif}
403+end;
404+```
405+
406+#### Приведение типов Pointer/Integer
407+
408+Для указателей под 64-бит требуется 8 байтов вместо 4-х на 32-битных. Тип 'Integer' остается 32-битным на всех платформах для совместимости. Это означает, что вы не можете приводить тип pointers к целым числам и обратно.
409+
410+FPC задает для этого два типа: PtrInt и PtrUInt. PtrInt представляет собой 32-битное целое число со знаком на 32-битных платформах и 64-битное целое число со знаком на 64-битных платформах. То же самое для PtrUInt, но только - это целое число без знака.
411+
412+Используйте для кода, который должен работать с Delphi и FPC:
413+
414+``` pascal
415+ {$IFNDEF FPC}
416+ type
417+ PtrInt = integer;
418+ PtrUInt = cardinal;
419+ {$ENDIF}
420+```
421+
422+Замените все **integer(SomePointerOrObject)** на **PtrInt(SomePointerOrObject)**.
423+
424+### Порядок байтов
425+
426+Платформы Intel имеют младший порядок байтов, это означает, что младший байт стоит первым. Например, два байта слова \$1234 хранятся как \$34 \$12 в системах с младшим порядком байтов. В системах со старшим порядком байтов, таких как powerpc, два байта слова \$1234 хранятся как \$12 \$34. Разница важна при чтении файлов, созданных в других системах.
427+
428+------------------------------------------------------------------------
429+
430+Прим.перев.: подробнее о порядке байтов можно почитать в [Википедии](https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%80%D1%8F%D0%B4%D0%BE%D0%BA_%D0%B1%D0%B0%D0%B9%D1%82%D0%BE%D0%B2)
431+
432+------------------------------------------------------------------------
433+
434+Используйте для кода, который должен работать в обоих \[случаях\]:
435+
436+``` pascal
437+{$IFDEF ENDIAN_BIG}
438+...
439+{$ELSE}
440+...
441+{$ENDIF}
442+```
443+
444+И наоборот для ENDIAN_LITTLE.
445+
446+Системный модуль предоставляет достаточно много функций преобразования порядка байтов, таких как SwapEndian, BEtoN (со старшим порядком байтов в текущий), LEtoN (с младшим порядком байтов в текущий), NtoBE (с текущим порядком байтов в старший) и NtoLE (с текущим порядком байтов в младший).
447+
448+#### Libc и другие специальные модули
449+
450+Избегайте устаревших модулей, таких как "oldlinux" и "libc", которые не поддерживаются вне linux/i386.
451+
452+#### Ассемблер
453+
454+Избегайте использования [[Assembly language|ассемблера]].
455+
456+#### Директивы компилятора
457+
458+``` pascal
459+{$ifdef CPU32}
460+...напишите здесь код для 32-битных процессоров
461+{$ENDIF}
462+{$ifdef CPU64}
463+...напишите здесь код для 64-битных процессоров
464+{$ENDIF}
465+```
466+
467+### Проекты, пакеты и пути поиска
468+
469+Проекты и пакеты Lazarus предназначены для нескольких платформ. Обычно вы можете просто скопировать проект и необходимые пакеты на другой компьютер и скомпилировать их там. Вам не нужно создавать по отдельному проекту для каждой платформы.
470+
471+Несколько советов для достижения этого.
472+
473+Компилятор создает для каждого модуля ppu-файл с таким же именем. Этот ppu-файл может быть использован другими проектами и пакетами. Исходные файлы модулей (например, unit1.pas) не должны быть общими. Просто дайте компилятору выходной каталог модуля, в котором нужно создавать файлы ppu. IDE делает это по умолчанию, поэтому здесь вам нечего делать.
474+
475+Каждый файл модуля должен быть частью **одного** проекта или пакета. Если файл модуля используется только одним проектом, добавьте его в этот проект. В противном случае добавьте его в пакет. Если вы еще не создали пакет для общих модулей, см. здесь: [[Lazarus Packages/ru|создание пакета для ваших общих модулей]].
476+
477+Каждый проект и каждый пакет должны иметь **разобщенные каталоги** - они не должны совместно использовать каталоги. В противном случае вы должны быть экспертом в искусстве поиска путей компилятора. Если вы не являетесь экспертом или если те, кто может использовать ваш проект/пакет, не являются экспертами: не делитесь каталогами между проектами/пакетами.
478+
479+#### Платформозависимые модули
480+
481+Например, модуль wintricks.pas должен использоваться только под Windows. В разделе uses используйте:
482+
483+``` pascal
484+uses
485+ Classes, SysUtils
486+ {$IFDEF Windows}
487+ ,WinTricks
488+ {$ENDIF}
489+ ;
490+```
491+
492+Если модуль является частью пакета, вы также должны выбрать модуль в редакторе пакетов и снять флажок *Use unit*(Использовать модуль).
493+
494+См. также [[Lazarus Packages/ru|платформозависимые модули]]
495+
496+#### Платформозависимые пути поиска
497+
498+Когда вы ориентируетесь на несколько платформ и обращаетесь к операционной системе напрямую, вы быстро устаете от бесконечных конструкций IFDEF. Одним из решений, которое часто используется в исходниках FPC и Lazarus, является использование include-файлов. Создайте по одному подкаталогу для \[каждой\] целевой \[ОС\]. Например, win32, linux, bsd, darwin. Поместите в каждый каталог включаемый файл с тем же именем. Затем используйте макрос во включаемом пути. Модуль может использовать обычную включающую директиву. Пример для одного include-файла для каждого набора виджетов LCL:
499+
500+Создайте один файл для каждого набора виджетов, который вы хотите поддерживать:
501+
502+``` pascal
503+ win32/example.inc
504+ gtk/example.inc
505+ gtk2/example.inc
506+ carbon/example.inc
507+```
508+
509+Вам не нужно добавлять файлы в пакет или проект. Добавьте включаемый путь поиска *\$(LCLWidgetType)* в параметры компилятора вашего пакета или проекта.
510+
511+В вашем модуле используйте директиву:
512+
513+``` pascal
514+{$I example.inc}
515+```
516+
517+Вот некоторые полезные макросы и общие значения:
518+
519+- LCLWidgetType: win32, gtk, gtk2, qt, carbon, fpgui, nogui
520+- TargetOS: linux, win32, win64, wince, freebsd, netbsd, openbsd, darwin (многое другое)
521+- TargetCPU: i386, x86_64, arm, powerpc, sparc
522+- SrcOS: win, unix
523+
524+Вы можете использовать макрос \$Env() для использования переменных окружения.
525+
526+И конечно вы можете использовать комбинации. Например, LCL использует:
527+
528+``` pascal
529+$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)
530+```
531+
532+Смотрите здесь полный список макросов: [[IDE Macros in paths and filenames/ru|макросы в путях и именах файлов]]
533+
534+#### Аппаратно- / пользователь-зависимые пути поиска
535+
536+Например, у вас есть две машины Windows, Stan'а и Oliver'а. На \[машине\] Stan'а ваши модули находятся в *C:\units*, а на \[машине\] Oliver'а ваши модули находятся в *D:\path*. Модули принадлежат пакету *SharedStuff*, который представляет собой *C:\units\sharedstuff.lpk* на \[машине\] Stan'а и *D:\path\sharedstuff.lpk* на \[машине\] Oliver'а.
537+
538+После того, как вы открыли lpk в IDE или в lazbuild, путь автоматически сохраняется в его файлах конфигурации (packagefiles.xml). При компиляции проекта, для которого требуется пакет SharedStuff, среда IDE и lazbuild знают, где он находится. Так что никакой \[дополнительной\] конфигурации не требуется.
539+
540+Если вы хотите развернуть пакет на нескольких компьютерах или для всех пользователей компьютера (например, бассейн для студентов), вы можете добавить файл lpl в исходный каталог lazarus. Смотрите packager/globallinks для \[получения\] примеров.
541+
542+### Различия языковой среды
543+
544+Некоторые функции из Free Pascal, такие как StrToFloat, ведут себя по-разному в зависимости от текущей [[locale|языковой среды]] \[ОСи\]. Например, в США [[DecimalSeparator|десятичный разделитель]] обычно равен ".", но во многих странах Европы и Южной Америки это ",". Это может быть проблемой, поскольку иногда желательно, чтобы эти функции вели себя фиксированным образом, независимо от языковых настроек операционной системы. Примером является формат файла с десятичными точками, который всегда должен интерпретироваться одинаково.
545+
546+В следующих разделах объясняется, как это сделать.
547+
548+#### StrToFloat
549+
550+Новый набор настроек формата, которые устанавливают фиксированный десятичный разделитель, может быть создан с помощью следующего кода:
551+
552+``` pascal
553+// в вашем файле проекта .lpr
554+uses
555+...
556+{$IFDEF UNIX}
557+clocale
558+{ Требуется в Linux/Unix для поддержки форматирования. Должен быть одним из первых (вероятно, после cthreads?)}
559+{$ENDIF}
560+```
561+
562+и:
563+
564+``` pascal
565+// в вашем коде:
566+var
567+ FPointSeparator, FCommaSeparator: TFormatSettings;
568+begin
569+ // Настройки формата для преобразования строки в число с плавающей точкой
570+ FPointSeparator := DefaultFormatSettings;
571+ FPointSeparator.DecimalSeparator := '.';
572+ FPointSeparator.ThousandSeparator := '#';// отключаем тысячный разделитель
573+ FCommaSeparator := DefaultFormatSettings;
574+ FCommaSeparator.DecimalSeparator := ',';
575+ FCommaSeparator.ThousandSeparator := '#';// отключаем тысячный разделитель
576+```
577+
578+Позже вы можете использовать настройки этого формата при вызове StrToFloat, например:
579+
580+``` pascal
581+// Эта функция работает как StrToFloat, но просто пробует два возможных десятичных разделителя
582+// Это позволит избежать исключения, если формат строки не соответствует локали
583+function AnSemantico.StringToFloat(AStr: string): Double;
584+begin
585+ if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator)
586+ else Result := StrToFloat(AStr, FCommaSeparator);
587+end;
588+```
589+
590+### Gtk2 и маскировка исключений FPU
591+
592+Библиотека Gtk2 изменяет значение по умолчанию маски исключений FPU (модуль \[для операций\] с плавающей запятой). Следствием этого является то, что некоторые исключения с плавающей точкой не возникают, если приложение использует библиотеку Gtk2. Это означает, что, например, если вы разрабатываете приложение LCL в Windows с набором виджетов win32/64 (который по умолчанию для Windows) и планируете компилировать для Linux (где Gtk2 является набором виджетов по умолчанию), вы должны помнить об этой несовместимости.
593+
594+После [этой темы форума](http://www.lazarus.freepascal.org/index.php/topic,13460.0.html) и ответов на [этот багрепорт](http://bugs.freepascal.org/view.php?id=19674) стало ясно, что с этим ничего не поделаешь, поэтому мы должны знать, в чем эти различия.
595+
596+Поэтому давайте проведем тест:
597+
598+``` pascal
599+uses
600+ ..., math,...
601+
602+{...}
603+
604+var
605+ FPUException: TFPUException;
606+ FPUExceptionMask: TFPUExceptionMask;
607+begin
608+ FPUExceptionMask := GetExceptionMask;
609+ for FPUException := Low(TFPUException) to High(TFPUException) do begin
610+ write(FPUException, ' - ');
611+ if not (FPUException in FPUExceptionMask) then
612+ write('not ');
613+
614+ writeln('masked!');
615+ end;
616+ readln;
617+end.
618+```
619+
620+Наша простая программа получит значение FPC по умолчанию:
621+
622+``` pascal
623+ exInvalidOp - not masked!
624+ exDenormalized - masked!
625+ exZeroDivide - not masked!
626+ exOverflow - not masked!
627+ exUnderflow - masked!
628+ exPrecision - masked!
629+```
630+
631+Однако в Gtk2 только exOverflow не маскируется.
632+
633+Следствием этого является то, что исключения EInvalidOp и EZeroDivide не возникают, если приложение ссылается на библиотеку Gtk2! Обычно, деление ненулевого значения на ноль возбуждает исключение EZeroDivide, а деление нуля на ноль возбуждает исключение EInvalidOp. Например, такой код:
634+
635+``` pascal
636+var
637+ X, A, B: Double;
638+// ...
639+
640+try
641+ X := A / B;
642+ // блок кода 1
643+except
644+ // блок кода 2
645+end;
646+// ...
647+```
648+
649+при компиляции в приложении с набором виджетов Gtk2 примет другое направление. На виндовом widgetset'е, когда **B** равно нулю, будет сгенерировано исключение (EZeroDivide или EInvalidOp, в зависимости от того, равен ли **A** нулю или нет) и будет выполнен «блок кода 2». На Gtk2 **X** становится [Infinity (положительным бесконечным числом)](http://www.freepascal.org/docs-html/rtl/math/infinity.html), [NegInfinity (отрицательным бесконечным числом)](https://www.freepascal.org/docs-html/rtl/math/neginfinity.html) или [NaN (не числовым значением)](https://www.freepascal.org/docs-html/rtl/math/nan.html) и «блок кода 1» будет выполнен.
650+
651+Мы можем придумать разные способы преодоления этого несоответствия. В большинстве случаев вы можете просто проверить, равен ли **B** нулю, и не пытаться делить в этом случае. Однако иногда вам потребуется другой подход. Итак, взгляните на следующие примеры:
652+
653+``` pascal
654+uses
655+ ..., math,...
656+
657+//...
658+var
659+ X, A, B: Double;
660+ Ind: Boolean;
661+// ...
662+try
663+ X := A / B;
664+ Ind := IsInfinite(X) or IsNan(X); // на gtk2 мы "падаем" здесь
665+except
666+ Ind := True; // на windows мы "падаем" здесь, когда B равен нулю
667+end;
668+if Ind then begin
669+ // блок кода 2
670+end else begin
671+ // блок кода 1
672+end;
673+// ...
674+```
675+
676+Или:
677+
678+``` pascal
679+uses
680+ ..., math,...
681+
682+//...
683+var
684+ X, A, B: Double;
685+ FPUExceptionMask: TFPUExceptionMask;
686+// ...
687+
688+try
689+ FPUExceptionMask := GetExceptionMask;
690+ SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // демаскируем
691+ try
692+ X := A / B;
693+ finally
694+ SetExceptionMask(FPUExceptionMask); // немедленно возвращаем предыдущую маскировку, мы не должны позволять вызывать Gtk2 без маски
695+ end;
696+ // блок кода 1
697+except
698+ // блок кода 2
699+end;
700+// ...
701+```
702+
703+Будьте осторожны, не делайте что-то подобное (вызовите LCL с все еще удаленной маской):
704+
705+``` pascal
706+try
707+ FPUExceptionMask := GetExceptionMask;
708+ SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
709+ try
710+ Edit1.Text := FloatToStr(A / B); // НЕТ! Настройка присвоение значения тексту Edit'а сводится к внутренним элементам набора виджетов, и Gtk2 API нельзя вызывать без маски!
711+ finally
712+ SetExceptionMask(FPUExceptionMask);
713+ end;
714+ // блок кода 1
715+except
716+ // блок кода 2
717+end;
718+// ...
719+```
720+
721+Но используйте вспомогательную переменную:
722+
723+``` pascal
724+try
725+ FPUExceptionMask := GetExceptionMask;
726+ SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
727+ try
728+ X := A / B; // Сначала мы устанавливаем вспомогательную переменную X
729+ finally
730+ SetExceptionMask(FPUExceptionMask);
731+ end;
732+ Edit1.Text := FloatToStr(X); // Теперь мы можем присвоить значение текста Edit'а.
733+ // code block 1
734+except
735+ // code block 2
736+end;
737+// ...
738+```
739+
740+Во всех ситуациях при разработке приложений LCL очень важно знать об этом и иметь в виду, что некоторые операции с плавающей запятой могут идти по-разному с разными наборами виджетов. Тогда вы можете придумать подходящий способ обойти это, но это не должно остаться незамеченным.
741+
742+## Проблемы при переходе с Windows на *nix и т.д.
743+
744+Проблемы, специфичные для Linux, macOS, Android и другие Unix'ы, описаны здесь. Не все предметы могут применяться ко всем платформам.
745+
746+### В Unix нет "каталога приложений"
747+
748+Многие программисты используют вызов ExtractFilePath(ParamStr(0)) или Application.ExeName для получения местоположения исполняемого файла, а затем поиска необходимых файлов для выполнения программы (изображений, файлов XML, файлов базы данных и т.д.) на основе расположения исполняемого файла. Это неправильно в Unix. Строка в ParamStr(0) может содержать каталог, отличный от каталога исполняемого файла, и он также зависит от различных программ командной оболочки (sh, bash и т.д.).
749+
750+Даже если Application.ExeName на самом деле может знать каталог, в котором находится исполняемый файл, этот файл может быть символической ссылкой, так что вы можете вместо этого получить каталог ссылки (в зависимости от версии ядра Linux вы можете получить каталог ссылки или самого бинарного файла программы).
751+
752+Чтобы избежать этого, прочитайте разделы о [[Multiplatform Programming Guide/ru|файлах конфигурации]] и [[Multiplatform Programming Guide/ru|файлах данных]].
753+
754+### Обхождение без Windows COM Automation
755+
756+В Windows COM Automation - это мощный способ не только удаленного манипулирования другими программами, но и предоставления другим программам возможности манипулировать вашей программой. С помощью Delphi вы можете сделать вашу программу одновременно клиентом автоматизации COM и сервером автоматизации COM, то есть она может манипулировать другими программами и, в свою очередь, \[позволять\] манипулировать \[собой\] другим программам. Например, см. [[Office Automation/ru|Использование COM Automation для работы с OpenOffice и Microsoft Office]].
757+
758+#### Альтернатива \[COM для\] macOS
759+
760+К сожалению, COM Automation недоступна в macOS и Linux. Однако вы можете имитировать некоторые функции COM Automation в macOS, используя AppleScript.
761+
762+AppleScript в некотором роде похож на COM Automation. Например, вы можете писать скрипты, которые манипулируют другими программами. Вот очень простой пример AppleScript, который запускает NeoOffice (версия OpenOffice.org для Mac):
763+
764+``` bash
765+ tell application "NeoOffice"
766+ launch
767+ end tell
768+```
769+
770+Приложение, разработанное для работы с AppleScript, предоставляет «словарь» классов и команд, которые можно использовать с приложением, аналогично классам сервера Windows Automation. Однако даже такие приложения, как NeoOffice, которые не предоставляют словарь, все равно будут реагировать на команды "launch" (запуск), "activate" (активация) и "quit" (выход). AppleScript может быть запущен из редактора сценариев macOS или Finder или даже преобразован в приложение, которое вы можете поместить на док-станцию, как любое другое приложение. Вы также можете запустить AppleScript из своей программы, как в этом примере:
771+
772+``` bash
773+ fpsystem('myscript.applescript');
774+```
775+
776+Предполагается, что скрипт находится в указанном файле. Вы также можете запускать сценарии на лету из своего приложения с помощью команды macOS OsaScript:
777+
778+``` bash
779+ fpsystem('osascript -e '#39'tell application "NeoOffice"'#39 +
780+ ' -e '#39'launch'#39' -e '#39'end tell'#39);
781+ {Note use of #39 to single-quote the parameters}
782+```
783+
784+Однако эти примеры являются эквивалентом следующей команды Open:
785+
786+``` bash
787+ fpsystem('open -a NeoOffice');
788+```
789+
790+Аналогично, в macOS вы можете эмулировать команды оболочки Windows для запуска веб-браузера и запуска почтового клиента с:
791+
792+``` bash
793+ fpsystem('open -a safari "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');
794+```
795+
796+и
797+
798+``` bash
799+ fpsystem('open -a mail "mailto:ss4200@invalid.org"');
800+```
801+
802+что предполагает, \[что это\] достаточно безопасно, что в системе macOS будут установлены приложения Safari и Mail. Конечно, вы никогда не должны делать подобные предположения, и для двух предыдущих примеров вы можете просто положиться на macOS, чтобы поступить правильно и выбрать веб-браузер пользователя по умолчанию и почтовый клиент, если вы вместо этого используете эти варианты:
803+
804+``` bash
805+ fpsystem('open "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');
806+```
807+
808+и
809+
810+``` bash
811+ fpsystem('open "mailto:ss4200@invalid.org"');
812+```
813+
814+Не забудьте включить модуль Unix в вашу секцию uses, если вы используете `fpsystem` или `shell` (взаимозаменяемые).
815+
816+Настоящая сила AppleScript заключается в удаленном управлении программами для создания и открытия документов и автоматизации других действий. Сколько вы можете сделать с \[этой\] программой, зависит от того, насколько обширным является ее словарь AppleScript (если он есть). Например, программы Microsoft Office X не очень пригодны для использования с AppleScript, тогда как более новые программы Office 2004 полностью переписали словари AppleScript, которые во многих отношениях сравниваются с тем, что доступно через серверы Windows Office Automation.
817+
818+#### Альтернативы для Linux
819+
820+В то время как оболочка Linux поддерживают сложные сценарии командной строки, тип сценариев ограничен тем, что может быть передано программе из командной строки. Не существует единого, унифицированного способа доступа к внутренним классам и командам программы в Linux, как в COM Automation Windows и AppleScript macOS. Однако отдельные среды рабочего стола (GNOME/KDE) и прикладные среды часто предоставляют такие методы межпроцессорного взаимодействия. В GNOME см. Bonobo Components. KDE имеет фреймворк KParts, DCOP. OpenOffice имеет платформонезависимый API для удаленного управления офисом (google OpenOffice SDK) - хотя вам, вероятно, придется написать связующий код на другом языке, который имеет привязки (например, Python), чтобы использовать его. Кроме того, в некоторых приложениях «режимы сервера» активируются специальными параметрами командной строки, которые позволяют управлять ими из другого процесса. Также возможно (Borland сделал это с помощью браузера документов Kylix) «встроить» одно окно приложения X верхнего уровня в другое, используя XReparentWindow (я думаю).
821+
822+Как и в Windows, многие программы для macOS и Linux состоят из нескольких библиотечных файлов (расширения .dylib и .so). Иногда эти библиотеки разработаны так, что вы также можете использовать их в программах, которые вы пишете. Хотя это может быть способом добавления некоторых функций внешней программы к вашей программе, на самом деле это не то же самое, что запуск самой внешней программы и управление ею. Вместо этого ваша программа просто ссылается на библиотеку внешней программы и использует ее подобно тому, как она использует любую библиотеку программирования.
823+
824+### Альтернативы для функций Windows API
825+
826+Многие программы Windows широко используют Windows API. В кроссплатформенных приложениях функции Win API в модуле Windows не должны использоваться или должны быть заключены в условную компиляцию (например, {\$IFDEF MSWINDOWS}).
827+
828+К счастью, многие из часто используемых функций Windows API реализованы многоплатформенным способом в модуле [[lclintf]]. Это может быть решением для программ, которые в значительной степени зависят от Windows API, хотя лучшее решение - заменить эти вызовы настоящими кроссплатформенными компонентами из LCL. Например, вы можете заменить вызовы функций рисования GDI вызовами методов объекта TCanvas.
829+
830+### Коды клавиш
831+
832+К счастью, обнаружение кодов клавиш (например, по событиям KeyUp) является переносимым: см. [[LCL Key Handling]].
833+
834+### Установка вашего приложения
835+
836+См. [[Deploying Your Application|Развертывание вашего приложения]].
837+
838+## Смотри также
839+
840+- [[Writing portable code regarding the processor architecture/ru|Написание переносимого кода относительно архитектуры процессора]]
841+- [[Introduction to platform-sensitive development|Введение в чувствительную к платформе разработку]]
842+- [Руководство для программистов Windows, начиная с Kylix.](http://www.midnightbeach.com/KylixForDelphiProgrammers.html) Многие из концепций / фрагментов кода применимы к Lazarus.
843+- [Руководство по написанию переносимого исходного кода, в основном между различными компиляторами.](http://www.stack.nl/~marcov/porting.pdf)
844+
845+---
846+*From the Free Pascal wiki, page "Multiplatform Programming Guide/ru", as it stood on 2021-11-01 after a change by Slawek, 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