Difference between revisions of "Multiplatform Programming Guide/ru"

From Lazarus wiki
Jump to navigationJump to search
 
(64 intermediate revisions by 2 users not shown)
Line 13: Line 13:
 
Чтобы ответить на этот вопрос, вы должны сначала определить, кто ваши потенциальные пользователи и как ваша программа будет использоваться. Этот вопрос зависит от того, где вы развертываете свое приложение.
 
Чтобы ответить на этот вопрос, вы должны сначала определить, кто ваши потенциальные пользователи и как ваша программа будет использоваться. Этот вопрос зависит от того, где вы развертываете свое приложение.
  
Если вы разрабатываете стандартное настольное программное обеспечение в 2014 году, Microsoft Windows может быть самой важной платформой. Обратите внимание, что Mac OS X и/или Linux набирают популярность и могут стать важной целью для вашего приложения.
+
Если вы разрабатываете стандартное настольное программное обеспечение в 2014 году, Microsoft Windows может быть самой важной платформой. Обратите внимание, что macOS и/или Linux набирают популярность и могут стать важной целью для вашего приложения.
  
Популярность различных операционных систем для настольных компьютеров отличается в зависимости от страны, типа используемого программного обеспечения и целевой аудитории; тут нет общего правила. Например, Mac OS X довольно популярна в Северной Америке и Западной Европе, в то время как в Южной Америке компьютеры Mac в основном ограничены работой с видео и звуком.
+
Популярность различных операционных систем для настольных компьютеров отличается в зависимости от страны, типа используемого программного обеспечения и целевой аудитории; тут нет общего правила. Например, macOS довольно популярна в Северной Америке и Западной Европе, в то время как в Южной Америке компьютеры Mac в основном ограничены работой с видео и звуком.
  
 
Для многих контрактных проектов важна только одна платформа. Free Pascal и Lazarus вполне способны писать программы, ориентированные на конкретную платформу. Вы можете, например, получить доступ ко всему API Windows, чтобы написать хорошо интегрированную программу Windows.
 
Для многих контрактных проектов важна только одна платформа. Free Pascal и Lazarus вполне способны писать программы, ориентированные на конкретную платформу. Вы можете, например, получить доступ ко всему API Windows, чтобы написать хорошо интегрированную программу Windows.
Line 29: Line 29:
 
При работе с файлами и папками важно использовать платформо-независимые разделители путей и [маркеры] [[End_of_Line/ru|конца строки]]. Вот список объявленных [[Constant/ru|констант]] в Lazarus, которые будут использоваться при работе с файлами и папками.
 
При работе с файлами и папками важно использовать платформо-независимые разделители путей и [маркеры] [[End_of_Line/ru|конца строки]]. Вот список объявленных [[Constant/ru|констант]] в Lazarus, которые будут использоваться при работе с файлами и папками.
  
* '''PathSep''', '''PathSeparator''': path separator when adding many paths together (';', ...)
+
* '''PathSep''', '''PathSeparator''': разделитель пути при добавлении нескольких путей вместе (';', ...)
* '''PathDelim''', '''DirectorySeparator''': directory separator for each platform ('/', '\', ...)
+
* '''PathDelim''', '''DirectorySeparator''': разделитель каталогов для каждой платформы ('/', '\', ...)
* '''LineEnding''': proper line ending character sequence (#13#10 - CRLF, #10 - [[Line_feed|LF]], ...)
+
* '''LineEnding''': правильная последовательность символов окончания строки (#13#10 - CRLF, #10 - [[Line feed/ru|LF]], ...)
  
Another important thing to be noted is the case sensitiveness of the file system.
+
Еще одна важная вещь, которую следует отметить, - это чувствительность к регистру [имен файлов и каталогов] файловой системы.
On Windows filenames are usually not case sensitive, while they usually are on Linux and BSD platforms. But if a EXT2, EXT3, etc file system is mounted on Windows, it would be case-sensitive. Respectively a FAT file system mounted on Linux should not be case sensitive.
+
В Windows имена файлов обычно не чувствительны к регистру, в то время как они обычно [регистрозависимы] на платформах Linux и BSD. Но если файловая система EXT2, EXT3 и т.д. смонтирована в Windows, она будет чувствительна к регистру. Соответственно, файловая система FAT, смонтированная в Linux, не должна учитывать регистр символов.
  
It shall be paid special attention, that NTFS is non-case sensitive when used in Windows, but it is case sensitive when mounted by POSIX OSes. This could cause '''various problems, including loss of files''' if files with same filenames in different cases exist on a NTFS partition, mounted in Windows. Using custom functions for checking and preventing creation of several files with the same names on NTFS should be considered by the developers.
+
Следует обратить особое внимание, что NTFS не чувствительна к регистру при использовании в Windows, но она чувствительна к регистру при монтировании ОС POSIX. Это может вызвать различные проблемы, в том числе потерю файлов, если файлы с одинаковыми именами файлов в разных случаях существуют в разделе NTFS, смонтированном в Windows. Разработчики должны рассмотреть возможность использования пользовательских функций для проверки и предотвращения создания нескольких файлов с одинаковыми именами в NTFS.
  
Mac OS X use case insensitive filenames by default. This can be the cause of annoying bugs, so any portable application should use consistently filenames.
+
macOS по умолчанию использует имена файлов без учета регистра. Это может быть причиной досадных ошибок, поэтому любое переносимое приложение должно постоянно использовать имена файлов.
  
The RTL file functions use the system encoding for file names. Under Windows this is one of the windows code pages, while Linux, BSD and Mac OS X usually use UTF-8. The unit '''FileUtil''' of the LCL provides file functions which takes UTF-8 strings like the rest of the LCL.
+
RTL-функции файлов  используют системную кодировку для имен файлов. Под Windows это одна из кодовых страниц Windows, в то время как Linux, BSD и macOS обычно используют UTF-8. Модуль '''FileUtil''' [библиотеки] LCL предоставляет файловые функции, которые принимают строки UTF-8, как и остальная часть LCL.
  
<syntaxhighlight>// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, MacOSX
+
<syntaxhighlight lang=pascal>// [функциям] AnsiToUTF8 и UTF8ToAnsi нужен менеджер широких строк (widestringmanager) под Linux, BSD, macOS,
// but normally these OS use UTF-8 as system encoding so the widestringmanager
+
// но обычно эти ОС используют UTF-8 в качестве системной кодировки, поэтому [там] менеджер широких строк
// is not needed.
+
// не нужен.
function NeedRTLAnsi: boolean;// true if system encoding is not UTF-8
+
function NeedRTLAnsi: boolean;// true, если системная кодировка не UTF-8
 
procedure SetNeedRTLAnsi(NewValue: boolean);
 
procedure SetNeedRTLAnsi(NewValue: boolean);
function UTF8ToSys(const s: string): string;// as UTF8ToAnsi but more independent of widestringmanager
+
function UTF8ToSys(const s: string): string;// как UTF8ToAnsi, но более не зависим от widestringmanager
function SysToUTF8(const s: string): string;// as AnsiToUTF8 but more independent of widestringmanager
+
function SysToUTF8(const s: string): string;// как AnsiToUTF8, но более не зависим от widestringmanager
function UTF8ToConsole(const s: string): string;// converts UTF8 string to console encoding (used by Write, WriteLn)
+
function UTF8ToConsole(const s: string): string;// преобразовывает строку UTF8 в консольную кодировку (используется Write, WriteLn)
  
// file operations
+
// файловые операции
 
function FileExistsUTF8(const Filename: string): boolean;
 
function FileExistsUTF8(const Filename: string): boolean;
 
function FileAgeUTF8(const FileName: string): Longint;
 
function FileAgeUTF8(const FileName: string): Longint;
Line 74: Line 74:
 
function ForceDirectoriesUTF8(const Dir: string): Boolean;
 
function ForceDirectoriesUTF8(const Dir: string): Boolean;
  
// environment
+
// окружение
 
function ParamStrUTF8(Param: Integer): string;
 
function ParamStrUTF8(Param: Integer): string;
 
function GetEnvironmentStringUTF8(Index: Integer): string;
 
function GetEnvironmentStringUTF8(Index: Integer): string;
Line 80: Line 80:
 
function GetAppConfigDirUTF8(Global: Boolean): string;
 
function GetAppConfigDirUTF8(Global: Boolean): string;
  
// other
+
// другое
 
function SysErrorMessageUTF8(ErrorCode: Integer): String;</syntaxhighlight>
 
function SysErrorMessageUTF8(ErrorCode: Integer): String;</syntaxhighlight>
  
===Empty file names and double path delimiters===
 
  
There are differences in file/directory name handling in Windows versus Linux/Unix/Unix like systems.
+
----
 +
[[User:Zoltanleo|Прим.перев.]]: после появления поддержки юникода на уровне компилятора (FPC 2.7.1) вместо AnsiToUTF8, UTF8ToAnsi, SysToUTF8, UTF8ToAnsi для работы с WinAPI рекомендуется использовать функции '''UTF8ToWinCP''' и '''WinCPToUTF8'''. Подробнее здесь: [[LCL_Unicode_Support/ru#RTL с кодовой страницей UTF-8 по умолчанию|RTL с кодовой страницей UTF-8 по умолчанию]]
 +
----
  
* Windows allows empty file names. That's why FileExistsUTF8('..\') checks under Windows in the parent directory for a file without name.
+
===Пустые имена файлов и двойные разделители путей===
* On Linux/Unix/Unix-like systems an empty file is mapped to the directory and directories are treated as files. This means that FileExistsUTF8('../') under Unix checks for the existence of the parent directory, which normally results true.
 
  
Double path delimiters in file names are also treated differently:
+
Существуют различия в обработке имен файлов/каталогов в Windows по сравнению с Linux/Unix/Unix-подобными системами.
* Windows: 'C:\' is not the same as 'C:\\'
 
* Unix like OS: the path '/usr//' is the same as '/usr/'. If '/usr' is a directory then even all three are the same.  
 
  
This is important when concatenating file names. For example:
+
* Windows позволяет пустые имена файлов. Вот почему FileExistsUTF8 ('..\') проверяет в Windows в родительском каталоге наличие файла без имени.
 +
* В Linux/Unix/Unix-подобных системах пустой файл сопоставляется с каталогом, а каталоги рассматриваются как файлы. Это означает, что FileExistsUTF8 ('../') в Unix проверяет наличие родительского каталога, что обычно приводит к значению true.
  
<syntaxhighlight>FullFilename:=FilePath+PathDelim+ShortFilename; // can result in two PathDelims which gives different results under Windows and Linux
+
Двойные разделители пути в именах файлов также обрабатываются по-разному:
FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // creates only one PathDelim
+
* Windows: 'C:\' не то же самое, что 'C:\\'
FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // creates only one PathDelim and do some more clean up</syntaxhighlight>
+
* Unix-подобные OS: путь '/usr//' совпадает с '/usr/'. Если '/usr' является каталогом, то даже все три равнозначны.
  
The function TrimFilename replaces double path delimiters with single ones and shorten '..' paths. For example /usr//lib/../src is trimmed to /usr/src.
+
Это важно при объединении имен файлов. Например:
  
If you want to know if a directory exists use '''DirectoryExistsUTF8'''.
+
<syntaxhighlight lang=pascal>FullFilename:=FilePath+PathDelim+ShortFilename; // может привести к двум PathDelims, которые дают разные результаты под Windows и Linux
 +
FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // создает только один PathDelim
 +
FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // создает только один PathDelim и делает еще несколько чисток</syntaxhighlight>
  
Another common task is to check if the path part of a file name exists. You can get the path with ExtractFilePath, but this will contain the path delimiter.  
+
----
* Under Unix like system you can simply use FileExistsUTF8 on the path. For example FileExistsUTF8('/home/user/') will return true if the directory /home/user exists.  
+
[[user:zoltanleo|Прим.перев.]]: для "нормализации" количества и вида разделителей в путях папок и файлов можно использовать следующие функции из модуля <tt>LazFileUtils</tt>
* Under Windows you must use the DirectoryExistsUTF8 function, but before that you must delete the path delimiter, for example with the ChompPathDelim function.  
+
<syntaxhighlight lang=pascal>
 +
function CleanAndExpandDirectory(const Filename: string): string;
 +
function CleanAndExpandFilename(const Filename: string): string;
 +
</syntaxhighlight>
 +
----
 +
 
 +
Функция TrimFilename заменяет разделители двойных путей одиночными и сокращает пути '..'. Например /usr//lib/../src обрезается до /usr/src.
 +
 
 +
Если вы хотите узнать, существует ли каталог, используйте '''DirectoryExistsUTF8'''.
  
Under Unix like systems the root directory is '/' and using the ChompPathDelim function will create an empty string. The function DirPathExists works like the DirectoryExistsUTF8 function, but trims the given path.
+
Другая распространенная задача - проверить, существует ли часть пути имени файла. Вы можете получить путь с помощью ExtractFilePath, но он будет содержать разделитель пути.
 +
* Под Unix-подобной системой вы можете просто использовать FileExistsUTF8 в пути. Например, FileExistsUTF8('/home/user/') вернет true, если каталог /home/user существует.
 +
* В Windows вы должны использовать функцию DirectoryExistsUTF8, но перед этим вы должны удалить разделитель пути, например, с помощью функции ChompPathDelim.  
  
Note that Unix/Linux uses the '~' (tilde) symbol to stand for the home directory, typically '/home/jim/' for a user called jim. So '~/myapp/myfile' and '/home/jim/myapp/myfile' are identical on the command line and in scripts. However, the tilde is not automatically expanded by Lazarus. It is necessary to use ExpandFileNameUTF8('~/myapp/myfile') to get the full path.
+
В Unix-подобных системах корневым каталогом является '/', а использование функции ChompPathDelim создаст пустую строку. Функция DirPathExists работает как функция DirectoryExistsUTF8, но обрезает заданный путь.
  
=== Text encoding ===
+
Обратите внимание, что Unix/Linux использует символ '~' (тильда) для обозначения домашнего каталога, обычно '/home/jim/' для пользователя с именем jim. Так что '~/myapp/myfile' и '/home/jim/myapp/myfile' идентичны в командной строке и в скриптах. Тем не менее, тильда не будет автоматически расширяться Lazarus'ом. Необходимо использовать  ExpandFileNameUTF8('~/myapp/myfile'), чтобы получить полный путь.
 +
 
 +
 
 +
----
 +
[[User:Zoltanleo|Прим.перев.]]: Если code completion "не находит" выше описанные функции, добавьте в секцию uses модули '''LazUTF8''' и '''LazFileUtils'''.
 +
----
 +
 
 +
=== Кодировка текста ===
 
   
 
   
Text files are often encoded in the current system encoding. Under Windows this is usually one of the windows code pages, while Linux, BSD, and Mac OS X usually use UTF-8.  
+
Текстовые файлы часто кодируются в текущей кодировке системы. В Windows это обычно одна из кодовых страниц Windows, в то время как Linux, BSD и macOS обычно используют UTF-8.
There is no 100% rule to find out which encoding a text file uses. The LCL unit '''lconvencoding''' has a function to guess the encoding:
+
Не существует 100%-го правила, чтобы узнать, какую кодировку использует текстовый файл. Модуль LCL '''lconvencoding''' имеет функцию для угадывания кодировки:
  
<syntaxhighlight>function GuessEncoding(const s: string): string;
+
<syntaxhighlight lang=pascal>function GuessEncoding(const s: string): string;
 
function GetDefaultTextEncoding: string;</syntaxhighlight>
 
function GetDefaultTextEncoding: string;</syntaxhighlight>
  
And it contains functions to convert from one encoding to another:
+
И он содержит функции для преобразования из одной кодировки в другую:
  
<syntaxhighlight>function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;
+
<syntaxhighlight lang=pascal>function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;
  
function UTF8BOMToUTF8(const s: string): string; // UTF8 with BOM
+
function UTF8BOMToUTF8(const s: string): string; // UTF8 с BOM
function ISO_8859_1ToUTF8(const s: string): string; // central europe
+
function ISO_8859_1ToUTF8(const s: string): string; // Центральная Европа
function CP1250ToUTF8(const s: string): string; // central europe
+
function CP1250ToUTF8(const s: string): string; // Центральная Европа
function CP1251ToUTF8(const s: string): string; // cyrillic
+
function CP1251ToUTF8(const s: string): string; // кириллица
 
function CP1252ToUTF8(const s: string): string; // latin 1
 
function CP1252ToUTF8(const s: string): string; // latin 1
 
...
 
...
function UTF8ToUTF8BOM(const s: string): string; // UTF8 with BOM
+
function UTF8ToUTF8BOM(const s: string): string; // UTF8 с BOM
function UTF8ToISO_8859_1(const s: string): string; // central europe
+
function UTF8ToISO_8859_1(const s: string): string; // Центральная Европа
function UTF8ToCP1250(const s: string): string; // central europe
+
function UTF8ToCP1250(const s: string): string; // Центральная Европа
function UTF8ToCP1251(const s: string): string; // cyrillic
+
function UTF8ToCP1251(const s: string): string; // кириллица
 
function UTF8ToCP1252(const s: string): string; // latin 1
 
function UTF8ToCP1252(const s: string): string; // latin 1
 
...</syntaxhighlight>
 
...</syntaxhighlight>
  
For example to load a text file and convert it to UTF-8 you can use:
+
Например, чтобы загрузить текстовый файл и преобразовать его в UTF-8, вы можете использовать:
  
<syntaxhighlight>var
+
<syntaxhighlight lang=pascal>var
 
   sl: TStringList;
 
   sl: TStringList;
 
   OriginalText: String;
 
   OriginalText: String;
Line 146: Line 164:
 
   sl:=TStringList.Create;
 
   sl:=TStringList.Create;
 
   try
 
   try
     sl.LoadFromFile('sometext.txt'); // beware: this changes line endings to system line endings
+
     sl.LoadFromFile('sometext.txt'); // осторожно: это изменяет конец строки на конец строки OCи
 
     OriginalText:=sl.Text;
 
     OriginalText:=sl.Text;
 
     TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);
 
     TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);
Line 155: Line 173:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
And to save a text file in the system encoding you can use:
+
А для сохранения текстового файла в системной кодировке вы можете использовать:
<syntaxhighlight>sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);
+
<syntaxhighlight lang=pascal>sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);
 
sl.SaveToFile('sometext.txt');</syntaxhighlight>
 
sl.SaveToFile('sometext.txt');</syntaxhighlight>
  
=== Configuration files ===
+
=== Конфигурационные файлы ===
  
You can use the [[doc:rtl/sysutils/getappconfigdir.html|GetAppConfigDir]] function from SysUtils unit to get a suitable place to store configuration files on different system. The function has one parameter, called Global. If it is True then the directory returned is a global directory, i.e. valid for all users on the system. If the parameter Global is false, then the directory is specific for the user who is executing the program. On systems that do not support multi-user environments, these two directories may be the same.
+
Вы можете использовать функцию [[doc:rtl/sysutils/getappconfigdir.html|GetAppConfigDir]] из модуля SysUtils, чтобы найти подходящее место для хранения файлов конфигурации в другой системе. Функция имеет один параметр, называемый Global. Если [его значение] - True, то возвращаемый каталог является глобальным каталогом, то есть действительным для всех пользователей в системе. Если параметр Global имеет значение false, тогда каталог является специфическим для пользователя, выполняющего программу. В системах, которые не поддерживают многопользовательские среды, эти два каталога могут быть одинаковыми.
  
There is also the [[doc:rtl/sysutils/getappconfigfile.html|GetAppConfigFile]] which will return an appropriate name for an application configuration file. You can use it like this:
+
Существует также [функция] [[doc:rtl/sysutils/getappconfigfile.html|GetAppConfigFile]], который возвращает соответствующее имя для файла конфигурации приложения. Вы можете использовать это так:
  
ConfigFilePath := GetAppConfigFile(False);
+
<syntaxhighlight lang=pascal>ConfigFilePath := GetAppConfigFile(False);</syntaxhighlight>
  
Below are examples of the output of default path functions on different systems:
+
Ниже приведены примеры вывода функций пути по умолчанию в различных системах:
  
<syntaxhighlight>program project1;
+
<syntaxhighlight lang=pascal>program project1;
  
 
{$mode objfpc}{$H+}
 
{$mode objfpc}{$H+}
Line 183: Line 201:
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
The output on a GNU/Linux system with FPC 2.2.2. Note that using True is buggy, already fixed in 2.2.3:
+
Вывод в системе GNU/Linux с FPC 2.2.2. Обратите внимание, что использование True является ошибкой, что уже исправлено в 2.2.3:
  
<pre>/etc/project1/
+
<syntaxhighlight lang=bash>/etc/project1/
 
/home/user/.config/project1/
 
/home/user/.config/project1/
 
/etc/project1.cfg
 
/etc/project1.cfg
/home/user/.config/project1.cfg</pre>
+
/home/user/.config/project1.cfg</syntaxhighlight>
  
You can notice that global configuration files are stored on the /etc directory and local configurations are stored on a hidden folder on the user's home directory. Directories whose name begin with a dot (.) are hidden on Linux. You can create a directory on the location returned by GetAppConfigDir and then store configuration files there.
+
Вы можете заметить, что глобальные файлы конфигурации хранятся в каталоге /etc, а локальные конфигурации хранятся в скрытой папке в домашнем каталоге пользователя. Каталоги, имя которых начинается с точки (.), скрыты в Linux. Вы можете создать каталог в месте, возвращаемом GetAppConfigDir, а затем сохранить там файлы конфигурации.
  
{{Note| Normal users are not allowed to write to the /etc directory. Only users with administration rights can do this.}}
+
{{Note| Обычные пользователи не могут писать в каталог /etc. Это могут делать только пользователи с правами администратора.}}
  
The output on recent versions of Windows with FPC 3.0.0 + :
+
Вывод на последние версии Windows с FPC 3.0.0 +:
  
 
<pre>C:\ProgramData\project1\
 
<pre>C:\ProgramData\project1\
Line 201: Line 219:
 
C:\Users\user\AppData\Local\project1\project1.cfg</pre>
 
C:\Users\user\AppData\Local\project1\project1.cfg</pre>
  
Notice that before FPC 2.2.4 the function was using the directory where the application was to store global configurations on Windows.
+
Обратите внимание, что до FPC 2.2.4 функция использовала каталог, в котором приложение должно было хранить глобальные конфигурации в Windows.
  
The output on Windows 98 with FPC 2.2.0:
+
Вывод на Windows 98 с FPC 2.2.0:
  
 
<pre>C:\Program Files\PROJECT1
 
<pre>C:\Program Files\PROJECT1
Line 210: Line 228:
 
C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg</pre>
 
C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg</pre>
  
The output on Mac OS X with FPC 2.2.0:
+
Вывод на macOS с FPC 2.2.0:
  
 
<pre>/etc
 
<pre>/etc
Line 217: Line 235:
 
/Users/user/.config/project1/project1.cfg</pre>
 
/Users/user/.config/project1/project1.cfg</pre>
  
{{Note| The use of UPX interferes with the use of the GetAppConfigDir and GetAppConfigFile functions.}}
+
{{Note| Использование UPX мешает использованию функций GetAppConfigDir и GetAppConfigFile.}}
  
{{Note| Under Mac OS X, in most cases config files are preference files, which should be XML files with the ending ".plist" and be stored in /Library/Preferences or ~/Library/Preferences with Names taken from the field "Bundle identifier" in the Info.plist of the application bundle. Using the Carbon calls CFPreference... is probably the easiest way to achieve this. .config files in the User directory are a violation of the programming guide lines.}}
 
  
=== Data and resource files ===
+
----
 +
[[User:Zoltanleo|Прим.перев.]]: Очевидно, имеется ввиду библиотека упаковщика исполняемых файлов - [https://ru.wikipedia.org/wiki/UPX UPX (the Ultimate Packer for eXecutables)]
 +
----
  
A very common question is where to store data files an application might need, such as Images, Music, XML files, database files, help files, etc. Unfortunately there is no cross-platform function to get the best location to look for data files. The solution is to implement differently on each platform using IFDEFs.
+
 
 +
{{Note| В macOS файлы конфигурации в большинстве случаев являются файлами предпочтений, которые должны быть файлами XML с окончанием ".plist" и храниться в /Library/Preferences или ~/Library/Preferences с именами, взятыми из поля "Bundle identifier"(Идентификатор пакета) в Info.plist пакета приложения. Использование вызовов Carbon CFPreference ... вероятно, самый простой способ добиться этого. Файлы .config в каталоге User являются нарушением указаний по программированию.}}
 +
 
 +
=== Файлы данных и ресурсов ===
 +
 
 +
Очень распространенный вопрос - где хранить файлы данных, которые могут понадобиться приложению, такие как изображения, музыка, файлы XML, файлы базы данных, файлы справки и т.д. К сожалению, нет кроссплатформенной функции, которая бы обеспечивала лучшее место для поиска файлов данных. Решение состоит в том, чтобы реализовать по-разному на каждой платформе, используя [директивы условной компиляции] IFDEF.
  
 
==== Windows ====
 
==== Windows ====
On Windows, application data that the program modifies should not be put in the application's directory (e.g. C:\Program Files\) but in a specific location (see e.g. [http://support.microsoft.com/kb/310294], under "Classify Application Data"). Windows Vista and newer actively enforce this (users only have write access to these directories when using elevation or disabling UAC) but uses a folder redirection mechanism to accommodate older, wrongly programmed applications. Just reading, not writing, data from application directories would still work.
+
В Windows данные приложения, которые изменяет программа, должны быть помещены не в каталог приложения (например, C:\Program Files\), а в определенном месте (см., например, [http://support.microsoft.com/kb/310294 Как написать приложение для Windows XP], в разделе "Classify Application Data"). Windows Vista и новее активно применяют это (пользователи имеют доступ на запись в эти каталоги только при использовании повышения [прав] или отключения UAC), но используют механизм перенаправления папок для поддержки старых, неправильно запрограммированных приложений. Просто чтение, а не запись данных из каталогов приложений все равно будет работать.
  
In short: use such folder:
+
Короче говоря: используйте такую папку:
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
     OpDirLocal:= GetEnvironmentVariableUTF8('appdata')+'\MyAppName';
 
     OpDirLocal:= GetEnvironmentVariableUTF8('appdata')+'\MyAppName';
 
</syntaxhighlight>
 
</syntaxhighlight>
  
  
See [[Windows_Programming_Tips#Getting_special_folders_.28My_documents.2C_Desktop.2C_local_application_data.2C_etc.29]]
+
См. [[Windows_Programming_Tips#Getting_special_folders_.28My_documents.2C_Desktop.2C_local_application_data.2C_etc.29|Советы по программированию Windows: получение специальных папок (Мои документы, Рабочий стол, локальные данные приложения и т.д.)]].
  
 
==== Unix/Linux ====
 
==== Unix/Linux ====
On most Unixes (like Linux, FreeBSD, OpenBSD, Solaris), application data files are located in a fixed location, which can be something like: /usr/share/app_name or /opt/app_name.
+
В большинстве Unix-систем (таких как Linux, FreeBSD, OpenBSD, Solaris) файлы данных приложения расположены в фиксированном месте, которое может выглядеть примерно так: /usr/share/app_name или /opt/app_name.
  
Application data that needs to be written to by the application often gets stored in places like /var/<programname>, with appropriate permissions set.
+
Данные приложения, которые должны быть записаны приложением, часто хранятся в таких местах, как /var/<programname>, с установленными соответствующими разрешениями.
  
User-specific read/write config/data will normally be stored somewhere under the user's home directory (e.g. in ~/.myfancyprogram).
+
Специфичные для пользователя конфигурации/данные для чтения/записи обычно хранятся где-то в домашнем каталоге пользователя (например, в  ~/.myfancyprogram).
  
How to get this home dir path:
+
Как получить путь к этому домашнему каталогу:
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
     OpDirLocal:= GetEnvironmentVariableUTF8('HOME')+'/.myappname';
 
     OpDirLocal:= GetEnvironmentVariableUTF8('HOME')+'/.myappname';
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==== OS X ====
+
==== macOS ====
macOS (Mac OS X) is an exception among UNIXes. Application is published in a bundle - directory with "app" extension, which is treated by file-manager as a file (you can also do "cd path/myapp.app"). Your resource files should be located inside the bundle. If bundle is "path/MyApp.app", then:
+
macOS является исключением среди UNIX. Приложение публикуется в связке - каталоге с расширением "app", которое обрабатывается файловым менеджером как файл (вы также можете сделать "cd path/myapp.app"). Ваши файлы ресурсов должны находиться внутри пакета. Если пакет - "path/MyApp.app", то:
  
* executable file is "path/MyApp.app/Contents/MacOS/myapp"
+
* исполняемый файл - "path/MyApp.app/Contents/MacOS/myapp"
* resources dir is "path/MyApp.app/Contents/Resources"
+
* каталог ресурсов - "path/MyApp.app/Contents/Resources"
  
Save config files to the home dir:
+
Сохраните файлы конфигурации в домашний каталог:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
     OpDirLocal:= GetEnvironmentVariableUTF8('HOME')+'/.myappname';
 
     OpDirLocal:= GetEnvironmentVariableUTF8('HOME')+'/.myappname';
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Read resources from:
+
Читайте ресурсы с:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
     OpDirRes:= ExtractFileDir(ExtractFileDir(Application.ExeName))+'/Resources';
 
     OpDirRes:= ExtractFileDir(ExtractFileDir(Application.ExeName))+'/Resources';
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Warning: '''never''' use paramstr(0) on any Unix platform to determine the location of the executable, as this is a Dos-Windows-OS/2 convention and has several conceptual problems, which cannot be solved using emulation on other platforms. The only thing paramstr(0) is guaranteed to return on Unix platforms is the name using which the program was started. The directory in which it is located and the name of the actual binary (in case it was started using a symbolic link) are not guaranteed to be available via paramstr(0).
+
{{Warning| '''никогда''' не используйте <tt>paramstr(0)</tt> на любой платформе Unix для определения расположения исполняемого файла, так как это соглашение Dos-Windows-OS/2 и имеет несколько концептуальных проблем, которые не могут быть решены с помощью эмуляции на других платформ. Единственное, что paramstr(0) гарантированно возвращает на платформах Unix, это имя, под которым была запущена программа. Каталог, в котором он находится, и имя фактического бинарного файла (в случае, если он был запущен с использованием символической ссылки) не обязательно будут доступны через paramstr(0).}}
 
 
==== Example code ====
 
 
 
Code: [[Cross-platform resources path]]
 
  
 
=== 32/64 bit ===
 
=== 32/64 bit ===
  
====Detecting bitness at runtime====
+
====Обнаружение разрядности ОСи во время выполнения====
While you can control whether you compile for 32 or 64 bit with compiler defines, sometimes you want to know what bitness the operating system runs.
+
Хотя вы можете контролировать, компилируете ли вы 32- или 64-битную версию с помощью определений компилятора, иногда вы захотите знать, какова разрядность операционной системы.
For example, if you are running a 32 bit Lazarus program on 64 bit Windows, you might want to run an external program in a 32 bit program files directory, or you might want to give different information to users: I need this in my LazUpdater Lazarus installer to offer the user a choice of 32 and 64 bit compilers. Code: [[Detect Windows x32-x64 example]].
+
Например, если вы запускаете 32-битную программу Lazarus в 64-битной Windows, вы можете запустить внешнюю программу из каталога Program Files (x86) для 32-битных программ или вы можете захотеть получить различную информацию пользователя: мне это нужно в моей программе установки Лазаруса LazUpdater, чтобы предложить пользователю выбор 32- и 64-битных компиляторов. Код: [[Detect Windows x32-x64 example|пример определения  Windows x32-x64]].
  
====Detecting bitness of external library before loading it====
+
====Обнаружение разрядности внешней библиотеки перед ее загрузкой====
When you want to load functions from dynamic library into your program, it has to have same bitness as your application. On 64 bit Windows, your application might be 32-bit or 64-bit, and there can be 32-bit and 64-bit libraries on your system.
+
Если вы хотите загрузить функции из динамической библиотеки в вашу программу, она должна иметь ту же разрядность, что и ваше приложение. В 64-битной Windows ваше приложение может быть 32-битным или 64-битным, и в вашей системе могут быть 32-битные и 64-битные библиотеки.
So you might want to check whether dll's bitness is same as your application's bitness before loading the dll dynamically. Here is a function which tests dll's bitness ([http://forum.lazarus.freepascal.org/index.php/topic,36834.msg245859.html#msg245859 contributed in forum by GetMem]):
+
Поэтому вы можете проверить, является ли разрядность dll такой же, как разрядность вашего приложения, прежде чем загружать dll динамически. Вот функция, которая проверяет разрядность dll([http://forum.lazarus.freepascal.org/index.php/topic,36834.msg245859.html#msg245859 предоставленная на форуме пользователем GetMem]):
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
  
 
uses {..., } JwaWindows;
 
uses {..., } JwaWindows;
Line 287: Line 307:
 
function GetPEType(const APath: WideString): Byte;
 
function GetPEType(const APath: WideString): Byte;
 
const
 
const
   PE_UNKNOWN = 0; //if the file is not a valid dll, 0 is returned
+
   PE_UNKNOWN = 0; // если файл не является корректной dll, возвращается 0
  // PE_16BIT  = 1; // not supported by this function
+
  // PE_16BIT  = 1; // не поддерживается этой функцией
 
   PE_32BIT  = 2;
 
   PE_32BIT  = 2;
 
   PE_64BIT  = 3;
 
   PE_64BIT  = 3;
Line 347: Line 367:
 
end;
 
end;
  
//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:
+
//Теперь, если вы компилируете свое приложение для 32-битной и 64-битной windows, вы можете проверить, является ли разрядность dll такой же, как у вашего приложения:
 
function IsCorrectBitness(const APath: WideString): Boolean;
 
function IsCorrectBitness(const APath: WideString): Boolean;
 
begin   
 
begin   
 
   {$ifdef CPU32}
 
   {$ifdef CPU32}
     Result := GetPEType(APath) = 2; //the application is compiled as 32-bit, we ask if GetPeType returns 2
+
     Result := GetPEType(APath) = 2; //приложение скомпилировано как 32-битное, мы спрашиваем, возвращает ли GetPeType 2
 
   {$endif}
 
   {$endif}
 
   {$ifdef CPU64}
 
   {$ifdef CPU64}
     Result := GetPEType(APath) = 3; //the application is compiled as 64-bit, we ask if GetPeType returns 3
+
     Result := GetPEType(APath) = 3; //приложение скомпилировано как 64-битное, мы спрашиваем, возвращает ли GetPeType 3
 
   {$endif}
 
   {$endif}
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==== Pointer / Integer Typecasts ====
+
==== Приведение типов Pointer/Integer ====
  
Pointers under 64bit need 8 bytes instead of 4 on 32bit. The 'Integer' type remains 32bit on all platforms for compatibility. This means you can not typecast pointers into integers and back.  
+
Для указателей под 64-бит требуется 8 байтов вместо 4-х на 32-битных. Тип 'Integer' остается 32-битным на всех платформах для совместимости. Это означает, что вы не можете приводить тип pointers к целым числам и обратно.
  
FPC defines two types for this: PtrInt and PtrUInt. PtrInt is a 32bit signed integer on 32 bit platforms and a 64bit signed integer on 64bit platforms. The same for PtrUInt, but ''unsigned'' integer instead.
+
FPC задает для этого два типа: PtrInt и PtrUInt. PtrInt представляет собой 32-битное целое число со знаком на 32-битных платформах и 64-битное целое число со знаком на 64-битных платформах. То же самое для PtrUInt, но только - это целое число без знака.
  
Use for code that should work with Delphi and FPC:
+
Используйте для кода, который должен работать с Delphi и FPC:
 +
<syntaxhighlight lang=pascal>
 
  {$IFNDEF FPC}
 
  {$IFNDEF FPC}
 
  type
 
  type
 
   PtrInt = integer;
 
   PtrInt = integer;
 
   PtrUInt = cardinal;
 
   PtrUInt = cardinal;
  {$ENDIF}
+
  {$ENDIF}</syntaxhighlight>
  
Replace all '''integer(SomePointerOrObject)''' with '''PtrInt(SomePointerOrObject)'''.
+
Замените все '''integer(SomePointerOrObject)''' на '''PtrInt(SomePointerOrObject)'''.
  
=== Endianess ===
+
=== Порядок байтов ===
  
Intel platforms are little endian, that means the least significant byte comes first. For example the two bytes of a word $1234 is stored as $34 $12 on little endian systems.
+
Платформы Intel имеют младший порядок байтов, это означает, что младший байт стоит первым. Например, два байта слова $1234 хранятся как $34 $12 в системах с младшим порядком байтов.
On big endian systems like the powerpc the two bytes of a word $1234 are stored as $12 $34. The difference is important when reading files created on other systems.
+
В системах со старшим порядком байтов, таких как powerpc, два байта слова $1234 хранятся как $12 $34. Разница важна при чтении файлов, созданных в других системах.
  
Use for code that should work on both:
+
----
<syntaxhighlight>{$IFDEF ENDIAN_BIG}
+
[[User:Zoltanleo|Прим.перев.]]: подробнее о порядке байтов можно почитать в [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 Википедии]
 +
----
 +
 
 +
Используйте для кода, который должен работать в обоих [случаях]:
 +
<syntaxhighlight lang=pascal>{$IFDEF ENDIAN_BIG}
 
...
 
...
 
{$ELSE}
 
{$ELSE}
Line 386: Line 411:
 
{$ENDIF}</syntaxhighlight>
 
{$ENDIF}</syntaxhighlight>
  
The opposite is ENDIAN_LITTLE.
+
И наоборот для ENDIAN_LITTLE.
  
The system unit provides plenty of endian converting functions, like SwapEndian, BEtoN (big endian to current endian), LEtoN (little endian to current endian), NtoBE (current endian to big endian) and NtoLE (current endian to little endian).
+
Системный модуль предоставляет достаточно много функций преобразования порядка байтов, таких как SwapEndian, BEtoN (со старшим порядком байтов в текущий), LEtoN (с младшим порядком байтов в текущий), NtoBE (с текущим порядком байтов в старший) и NtoLE (с текущим порядком байтов в младший).
  
  
==== Libc and other special units ====
+
==== Libc и другие специальные модули ====
  
Avoid legacy units like "oldlinux" and "libc" that are not supported outside of linux/i386.
+
Избегайте устаревших модулей, таких как "oldlinux" и "libc", которые не поддерживаются вне linux/i386.
  
==== Assembler ====
+
==== Ассемблер ====
  
Avoid [[Assembly language|assembler]].
+
Избегайте использования [[Assembly language|ассемблера]].
  
==== Compiler defines ====
+
==== Директивы компилятора ====
  
<syntaxhighlight>{$ifdef CPU32}
+
<syntaxhighlight lang=pascal>{$ifdef CPU32}
...write here code for 32 bit processors
+
...напишите здесь код для 32-битных процессоров
 
{$ENDIF}
 
{$ENDIF}
 
{$ifdef CPU64}
 
{$ifdef CPU64}
...write here code for 64 bit processors
+
...напишите здесь код для 64-битных процессоров
 
{$ENDIF}</syntaxhighlight>
 
{$ENDIF}</syntaxhighlight>
  
=== Projects, packages and search paths ===
+
=== Проекты, пакеты и пути поиска ===
  
Lazarus projects and packages are designed for multi platforms. Normally you can simply copy the project and the required packages to another machine and compile them there. You don't need to create one project per platform.
+
Проекты и пакеты Lazarus предназначены для нескольких платформ. Обычно вы можете просто скопировать проект и необходимые пакеты на другой компьютер и скомпилировать их там. Вам не нужно создавать по отдельному проекту для каждой платформы.
  
Some advice to achieve this
+
Несколько советов для достижения этого.
  
The compiler creates for every unit a ppu with the same name. This ppu can be used by other projects and packages. The unit source files (e.g. unit1.pas) should not be shared. Simply give the compiler a unit output directory where to create the ppu files. The IDE does that by default, so nothing to do for you here.
+
Компилятор создает для каждого модуля ppu-файл с таким же именем. Этот ppu-файл может быть использован другими проектами и пакетами. Исходные файлы модулей (например, unit1.pas) не должны быть общими. Просто дайте компилятору выходной каталог модуля, в котором нужно создавать файлы ppu. IDE делает это по умолчанию, поэтому здесь вам нечего делать.
  
Every unit file must be part of '''one''' project or package. If a unit file is only used by a single project, add it to this project. Otherwise add it to a package. If you have not yet created a package for your shared units, see here: [[Lazarus_Packages#Creating a package for your common units|Creating a package for your common units]]
+
Каждый файл модуля должен быть частью '''одного''' проекта или пакета. Если файл модуля используется только одним проектом, добавьте его в этот проект. В противном случае добавьте его в пакет. Если вы еще не создали пакет для общих модулей, см. здесь: [[Lazarus_Packages/ru#Создание пакета для ваших общих модулей|создание пакета для ваших общих модулей]].
  
Every project and every package should have '''disjunct directories''' - they should not share directories. Otherwise you must be an expert in the art of compiler search paths. If you are not an expert or if others who may use your project/package are not experts: do not share directories between projects/packages.
+
Каждый проект и каждый пакет должны иметь '''разобщенные каталоги''' - они не должны совместно использовать каталоги. В противном случае вы должны быть экспертом в искусстве поиска путей компилятора. Если вы не являетесь экспертом или если те, кто может использовать ваш проект/пакет, не являются экспертами: не делитесь каталогами между проектами/пакетами.
  
==== Platform specific units ====
+
==== Платформозависимые модули ====
For example the unit wintricks.pas should only be used under Windows. In the uses section use:
+
Например, модуль wintricks.pas должен использоваться только под Windows. В разделе uses используйте:
  
<syntaxhighlight>uses
+
<syntaxhighlight lang=pascal>uses
 
   Classes, SysUtils
 
   Classes, SysUtils
 
   {$IFDEF Windows}
 
   {$IFDEF Windows}
Line 430: Line 455:
 
   ;</syntaxhighlight>
 
   ;</syntaxhighlight>
  
If the unit is part of a package, you must also select the unit in the package editor of the package and disable the ''Use unit'' checkbox.
+
Если модуль является частью пакета, вы также должны выбрать модуль в редакторе пакетов и снять флажок ''Use unit''(Использовать модуль).
  
See also [[Lazarus_Packages#Platform_specific_units|Platform specific units]]
+
См. также [[Lazarus_Packages/ru#Платформозависимые модули|платформозависимые модули]]
  
==== Platform specific search paths ====
+
==== Платформозависимые пути поиска ====
  
When you target several platforms and access the operating system directly, then you will quickly get tired of endless IFDEF constructions. One solution that is used often in the FPC and Lazarus sources is to use include files. Create one sub directory per target. For example win32, linux, bsd, darwin. Put into each directory an include file with the same name. Then use a macro in the include path. The unit can use a normal include directive.  
+
Когда вы ориентируетесь на несколько платформ и обращаетесь к операционной системе напрямую, вы быстро устаете от бесконечных конструкций IFDEF. Одним из решений, которое часто используется в исходниках FPC и Lazarus, является использование include-файлов. Создайте по одному подкаталогу для [каждой] целевой [ОС]. Например, win32, linux, bsd, darwin. Поместите в каждый каталог включаемый файл с тем же именем. Затем используйте макрос во включаемом пути. Модуль может использовать обычную включающую директиву.
An example for one include file for each LCL widget set:
+
Пример для одного include-файла для каждого набора виджетов LCL:
  
Create one file for each widget set you want to support:
+
Создайте один файл для каждого набора виджетов, который вы хотите поддерживать:
 +
<syntaxhighlight lang=pascal>
 
  win32/example.inc
 
  win32/example.inc
 
  gtk/example.inc
 
  gtk/example.inc
 
  gtk2/example.inc
 
  gtk2/example.inc
 
  carbon/example.inc
 
  carbon/example.inc
 +
</syntaxhighlight>
 +
Вам не нужно добавлять файлы в пакет или проект.
 +
Добавьте включаемый путь поиска ''$(LCLWidgetType)'' в параметры компилятора вашего пакета или проекта.
  
You do not need to add the files to the package or project.
+
В вашем модуле используйте директиву:
Add the include search path ''$(LCLWidgetType)'' to the compiler options of your package or project.
+
<syntaxhighlight lang=pascal>{$I example.inc}</syntaxhighlight>
 
 
In your unit use the directive:
 
{$I example.inc}
 
  
Here are some useful macros and common values:
+
Вот некоторые полезные макросы и общие значения:
 
*LCLWidgetType: win32, gtk, gtk2, qt, carbon, fpgui, nogui
 
*LCLWidgetType: win32, gtk, gtk2, qt, carbon, fpgui, nogui
*TargetOS: linux, win32, win64, wince, freebsd, netbsd, openbsd, darwin (many more)
+
*TargetOS: linux, win32, win64, wince, freebsd, netbsd, openbsd, darwin (многое другое)
 
*TargetCPU: i386, x86_64, arm, powerpc, sparc
 
*TargetCPU: i386, x86_64, arm, powerpc, sparc
 
*SrcOS: win, unix
 
*SrcOS: win, unix
  
You can use the $Env() macro to use environment variables.
+
Вы можете использовать макрос $Env() для использования переменных окружения.
  
And of course you can use combinations. For example the LCL uses:  
+
И конечно вы можете использовать комбинации. Например, LCL использует:  
  
$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)
+
<syntaxhighlight lang=pascal>$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)</syntaxhighlight>
  
See here the complete list of macros: [[IDE Macros in paths and filenames]]
+
Смотрите здесь полный список макросов: [[IDE_Macros_in_paths_and_filenames/ru|макросы в путях и именах файлов]]
  
==== Machine / User specific search paths ====
+
==== Аппаратно- / пользователь-зависимые пути поиска ====
  
For example you have two windows machines stan and oliver. On stan your units are in ''C:\units'' and on oliver your units are in ''D:\path''. The units belong to the package ''SharedStuff'' which is ''C:\units\sharedstuff.lpk'' on stan and ''D:\path\sharedstuff.lpk'' on oliver.
+
Например, у вас есть две машины Windows, Stan'а и Oliver'а. На [машине] Stan'а ваши модули находятся в ''C:\units'', а на [машине] Oliver'а ваши модули находятся в ''D:\path''. Модули принадлежат пакету ''SharedStuff'', который представляет собой ''C:\units\sharedstuff.lpk'' на [машине] Stan'а и ''D:\path\sharedstuff.lpk'' на [машине] Oliver'а.
Once you opened the lpk in the IDE or by lazbuild, the path is automatically stored in its configuration files (packagefiles.xml).
 
When compiling a project that requires the package ''SharedStuff'', the IDE and lazbuild knows where it is. So no configuration is needed.
 
  
If you have want to deploy a package over many machine or for all users of a machine (e.g. a pool for students), then you can add a lpl file in the lazarus source directory. See packager/globallinks for examples.
+
После того, как вы открыли lpk в IDE или в lazbuild, путь автоматически сохраняется в его файлах конфигурации (packagefiles.xml).
 +
При компиляции проекта, для которого требуется пакет SharedStuff, среда IDE и lazbuild знают, где он находится. Так что никакой [дополнительной] конфигурации не требуется.
  
=== Locale differences ===
+
Если вы хотите развернуть пакет на нескольких компьютерах или для всех пользователей компьютера (например, бассейн для студентов), вы можете добавить файл lpl в исходный каталог lazarus. Смотрите packager/globallinks для [получения] примеров.
  
Some functions from Free Pascal, like StrToFloat behave differently depending on the current [locale]]. For example, in the USA the [[DecimalSeparator|decimal separator]] is usually ".", but in many European and South American countries it is ",". This can be a problem as sometimes it is desired to have these functions behave in a fixed way, independently from the locale.
+
=== Различия языковой среды ===
An example is a file format with decimal points that always needs to be interpreted the same way.
 
  
The next sections explain how to do that.
+
Некоторые функции из Free Pascal, такие как StrToFloat, ведут себя по-разному в зависимости от текущей [[locale|языковой среды]] [ОСи]. Например, в США [[DecimalSeparator|десятичный разделитель]] обычно равен ".", но во многих странах Европы и Южной Америки это ",". Это может быть проблемой, поскольку иногда желательно, чтобы эти функции вели себя фиксированным образом, независимо от языковых настроек операционной системы.
 +
Примером является формат файла с десятичными точками, который всегда должен интерпретироваться одинаково.
  
 +
В следующих разделах объясняется, как это сделать.
  
 
====StrToFloat====
 
====StrToFloat====
  
A new set of format settings which set a fixed decimal separator can be created with the following code:
+
Новый набор настроек формата, которые устанавливают фиксированный десятичный разделитель, может быть создан с помощью следующего кода:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
// in your .lpr project file
+
// в вашем файле проекта .lpr
 
uses
 
uses
 
...
 
...
 
{$IFDEF UNIX}
 
{$IFDEF UNIX}
 
clocale  
 
clocale  
{ required on Linux/Unix for formatsettings support. Should be one of the first (probably after cthreads?}
+
{ Требуется в Linux/Unix для поддержки форматирования. Должен быть одним из первых (вероятно, после cthreads?)}
 
{$ENDIF}
 
{$ENDIF}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
and:
+
и:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
// in your code:
+
// в вашем коде:
 
var
 
var
 
   FPointSeparator, FCommaSeparator: TFormatSettings;
 
   FPointSeparator, FCommaSeparator: TFormatSettings;
 
begin
 
begin
   // Format settings to convert a string to a float
+
   // Настройки формата для преобразования строки в число с плавающей точкой
 
   FPointSeparator := DefaultFormatSettings;
 
   FPointSeparator := DefaultFormatSettings;
 
   FPointSeparator.DecimalSeparator := '.';
 
   FPointSeparator.DecimalSeparator := '.';
   FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
+
   FPointSeparator.ThousandSeparator := '#';// отключаем тысячный разделитель
 
   FCommaSeparator := DefaultFormatSettings;
 
   FCommaSeparator := DefaultFormatSettings;
 
   FCommaSeparator.DecimalSeparator := ',';
 
   FCommaSeparator.DecimalSeparator := ',';
   FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator</syntaxhighlight>
+
   FCommaSeparator.ThousandSeparator := '#';// отключаем тысячный разделитель</syntaxhighlight>
  
Later on you can use this format settings when calling StrToFloat, like this:
+
Позже вы можете использовать настройки этого формата при вызове StrToFloat, например:
  
<syntaxhighlight>// This function works like StrToFloat, but simply tries two possible decimal separator
+
<syntaxhighlight lang=pascal>// Эта функция работает как StrToFloat, но просто пробует два возможных десятичных разделителя
// This will avoid an exception when the string format doesn't match the locale
+
// Это позволит избежать исключения, если формат строки не соответствует локали
 
function AnSemantico.StringToFloat(AStr: string): Double;
 
function AnSemantico.StringToFloat(AStr: string): Double;
 
begin
 
begin
Line 520: Line 546:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
=== Gtk2 and masking FPU exceptions ===
+
=== Gtk2 и маскировка исключений FPU ===
  
Gtk2 library changes the default value of FPU (floating point unit) exception mask. The consequence of this is that some floating point exceptions do not get raised if Gtk2 library is used by the application. That means that, if for example you develop a LCL application on Windows with win32/64 widgetset (which is Windows default) and plan to compile for Linux (where Gtk2 is default widgetset), you should keep this incompatibilities in mind.
+
Библиотека Gtk2 изменяет значение по умолчанию маски исключений FPU (модуль [для операций] с плавающей запятой). Следствием этого является то, что некоторые исключения с плавающей точкой не возникают, если приложение использует библиотеку Gtk2. Это означает, что, например, если вы разрабатываете приложение LCL в Windows с набором виджетов win32/64 (который по умолчанию для Windows) и планируете компилировать для Linux (где Gtk2 является набором виджетов по умолчанию), вы должны помнить об этой несовместимости.
  
After [http://www.lazarus.freepascal.org/index.php/topic,13460.0.html this forum topic] and answers on [http://bugs.freepascal.org/view.php?id=19674 this bug report] it became clear that nothing can be done about this, so we must know what actually these differences are.
+
После [http://www.lazarus.freepascal.org/index.php/topic,13460.0.html этой темы форума] и ответов на [http://bugs.freepascal.org/view.php?id=19674 этот багрепорт] стало ясно, что с этим ничего не поделаешь, поэтому мы должны знать, в чем эти различия.
  
Therefore, let's do a test:
+
Поэтому давайте проведем тест:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses
 
uses
 
   ..., math,...
 
   ..., math,...
Line 550: Line 576:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Our simple program will get what FPC default is:
+
Наша простая программа получит значение FPC по умолчанию:
  
<code>
+
<syntaxhighlight lang=pascal>
 
  exInvalidOp - not masked!
 
  exInvalidOp - not masked!
 
  exDenormalized - masked!
 
  exDenormalized - masked!
Line 559: Line 585:
 
  exUnderflow - masked!
 
  exUnderflow - masked!
 
  exPrecision - masked!
 
  exPrecision - masked!
</code>
+
</syntaxhighlight>
  
However, with Gtk2, only exOverflow is not masked.
+
Однако в Gtk2 только exOverflow не маскируется.
  
The consequence is that EInvalidOp and EZeroDivide exceptions do not get raised if the application links to Gtk2 library! Normally, dividing non-zero value by zero raises EZeroDivide exception and dividing zero by zero raises EInvalidOp. For example the code like this:
+
Следствием этого является то, что исключения EInvalidOp и EZeroDivide не возникают, если приложение ссылается на библиотеку Gtk2! Обычно, деление ненулевого значения на ноль возбуждает исключение EZeroDivide, а деление нуля на ноль возбуждает исключение EInvalidOp. Например, такой код:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
var
 
var
 
   X, A, B: Double;
 
   X, A, B: Double;
Line 572: Line 598:
 
try
 
try
 
   X := A / B;
 
   X := A / B;
   // code block 1
+
   // блок кода 1
 
except   
 
except   
   // code block 2
+
   // блок кода 2
 
end;
 
end;
 
// ...
 
// ...
 
</syntaxhighlight>
 
</syntaxhighlight>
  
will take different direction when compiled in application with Gtk2 widgetset. On win widgetset, when B equals zero, an exception will get raised (EZeroDivide or EInvalidOp, depending on whether A is zero) and "code block 2" will be executed. On Gtk2 X becomes [http://www.freepascal.org/docs-html/rtl/math/infinity.html Infinity], [http://www.freepascal.org/docs-html/rtl/math/neginfinity.html NegInfinity], or [http://www.freepascal.org/docs-html/rtl/math/nan.html NaN] and "code block 1" will be executed.
+
при компиляции в приложении с набором виджетов Gtk2 примет другое направление. На виндовом widgetset, когда '''B''' равно нулю, будет сгенерировано исключение (EZeroDivide или EInvalidOp, в зависимости от того, равен ли '''A''' нулю или нет) и будет выполнен «блок кода 2». На Gtk2 '''X''' становится [http://www.freepascal.org/docs-html/rtl/math/infinity.html Infinity (положительным бесконечным числом)], [https://www.freepascal.org/docs-html/rtl/math/neginfinity.html NegInfinity (отрицательным бесконечным числом)] или [https://www.freepascal.org/docs-html/rtl/math/nan.html NaN (не числовым значением)] и «блок кода 1» будет выполнен.
  
We can think of different ways to overcome this inconsistency. Most of the time you can simply test if B equals zero and don't try the dividing in that case. However, sometimes you will need some different approach. So, take a look at the following examples:
+
Мы можем придумать разные способы преодоления этого несоответствия. В большинстве случаев вы можете просто проверить, равен ли '''B''' нулю, и не пытаться делить в этом случае. Однако иногда вам потребуется другой подход. Итак, взгляните на следующие примеры:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses
 
uses
 
   ..., math,...
 
   ..., math,...
Line 594: Line 620:
 
try
 
try
 
   X := A / B;
 
   X := A / B;
   Ind := IsInfinite(X) or IsNan(X); // with gtk2, we fall here
+
   Ind := IsInfinite(X) or IsNan(X); // на gtk2 мы "падаем" здесь
 
except   
 
except   
   Ind := True; // in windows, we fall here when B equals zero
+
   Ind := True; // на windows мы "падаем" здесь, когда B равен нулю
 
end;
 
end;
 
if Ind then begin
 
if Ind then begin
   // code block 2
+
   // блок кода 2
 
end else begin
 
end else begin
   // code block 1
+
   // блок кода 1
 
end;
 
end;
 
// ...
 
// ...
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Or:
+
Или:
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
uses
 
uses
 
   ..., math,...
 
   ..., math,...
Line 619: Line 645:
 
try
 
try
 
   FPUExceptionMask := GetExceptionMask;
 
   FPUExceptionMask := GetExceptionMask;
   SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // unmask
+
   SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // демаскируем
 
   try
 
   try
 
     X := A / B;
 
     X := A / B;
 
   finally
 
   finally
     SetExceptionMask(FPUExceptionMask); // return previous masking immediately, we must not let Gtk2 internals to be called without the mask
+
     SetExceptionMask(FPUExceptionMask); // немедленно возвращаем предыдущую маскировку, мы не должны позволять вызывать Gtk2 без маски
 
   end;
 
   end;
   // code block 1
+
   // блок кода 1
 
except   
 
except   
   // code block 2
+
   // блок кода 2
 
end;
 
end;
 
// ...
 
// ...
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Be cautious, do not do something like this (call LCL with still removed mask):
+
Будьте осторожны, не делайте что-то подобное (вызовите LCL с все еще удаленной маской):
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
try
 
try
 
   FPUExceptionMask := GetExceptionMask;
 
   FPUExceptionMask := GetExceptionMask;
 
   SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
 
   SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
 
   try
 
   try
     Edit1.Text := FloatToStr(A / B); // NO! Setting Edit's text goes down to widgetset internals and Gtk2 API must not be called without the mask!
+
     Edit1.Text := FloatToStr(A / B); // НЕТ! Настройка присвоение значения тексту Edit'а сводится к внутренним элементам набора виджетов, и Gtk2 API нельзя вызывать без маски!
 
   finally
 
   finally
 
     SetExceptionMask(FPUExceptionMask);
 
     SetExceptionMask(FPUExceptionMask);
 
   end;
 
   end;
   // code block 1
+
   // блок кода 1
 
except   
 
except   
   // code block 2
+
   // блок кода 2
 
end;
 
end;
 
// ...
 
// ...
 
</syntaxhighlight>
 
</syntaxhighlight>
  
But use an auxiliary variable:
+
Но используйте вспомогательную переменную:
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
try
 
try
 
   FPUExceptionMask := GetExceptionMask;
 
   FPUExceptionMask := GetExceptionMask;
 
   SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
 
   SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
 
   try
 
   try
     X := A / B; // First, we set auxiliary variable X
+
     X := A / B; // Сначала мы устанавливаем вспомогательную переменную X
 
   finally
 
   finally
 
     SetExceptionMask(FPUExceptionMask);
 
     SetExceptionMask(FPUExceptionMask);
 
   end;
 
   end;
   Edit1.Text := FloatToStr(X); // Now we can set Edit's text.
+
   Edit1.Text := FloatToStr(X); // Теперь мы можем присвоить значение текста Edit'а.
 
   // code block 1
 
   // code block 1
 
except   
 
except   
Line 667: Line 693:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
In all situations, when developing LCL applications, it is most important to know about this and to keep in mind that some floating point operations can go different way with different widgetsets. Then you can think of an appropriate way to workaround this, but this should not go unnoticed.
+
Во всех ситуациях при разработке приложений LCL очень важно знать об этом и иметь в виду, что некоторые операции с плавающей запятой могут идти по-разному с разными наборами виджетов. Тогда вы можете придумать подходящий способ обойти это, но это не должно остаться незамеченным.
  
==Issues when moving from Windows to *nix etc==
+
==Проблемы при переходе с Windows на *nix и т.д.==
Issues specific to Linux, OSX, Android and other Unixes are described here. Not all subjects may apply to all platforms
+
Проблемы, специфичные для Linux, macOS, Android и другие Unix'ы, описаны здесь. Не все предметы могут применяться ко всем платформам.
  
=== On Unix there is no "application directory" ===
+
=== В Unix нет "каталога приложений" ===
Many programmers are used to call ExtractFilePath(ParamStr(0)) or Application.ExeName to get the location of the executable, and then search for the necessary files for the program execution (Images, XML files, database files, etc) based on the location of the executable. This is wrong on unixes. The string on ParamStr(0) may contain a directory other than the one of the executable, and it also varies between different shell programs (sh, bash, etc).
 
  
Even if Application.ExeName could in fact know the directory where the executable is, that file could be a symbolic link, so you could get the directory of the link instead (depending on the Linux kernel version, you either get the directory of the link or of the program binary itself).
+
Многие программисты используют вызов ExtractFilePath(ParamStr(0)) или Application.ExeName для получения местоположения исполняемого файла, а затем поиска необходимых файлов для выполнения программы (изображений, файлов XML, файлов базы данных и т.д.) на основе расположения исполняемого файла. Это неправильно в Unix. Строка в ParamStr(0) может содержать каталог, отличный от каталога исполняемого файла, и он также зависит от различных программ командной оболочки (sh, bash и т.д.).
  
To avoid this read the sections about [[Multiplatform_Programming_Guide#Configuration_files|configuration files]] and [[Multiplatform_Programming_Guide#Data_and_resource_files|data files]].
+
Даже если Application.ExeName на самом деле может знать каталог, в котором находится исполняемый файл, этот файл может быть символической ссылкой, так что вы можете вместо этого получить каталог ссылки (в зависимости от версии ядра Linux вы можете получить каталог ссылки или самого бинарного файла программы).
  
=== Making do without Windows COM Automation ===
+
Чтобы избежать этого, прочитайте разделы о [[Multiplatform_Programming_Guide/ru#Конфигурационные файлы|файлах конфигурации]] и [[Multiplatform_Programming_Guide/ru#Файлы данных и ресурсов|файлах данных]].
  
With Windows, COM Automation is a powerful way not only of manipulating other programs remotely but also for allowing other programs to manipulate your program. With Delphi you can make your program both an COM Automation client and a COM Automation server, meaning it can both manipulate other programs and in turn be manipulated by other programs. For examples,
+
=== Обхождение без Windows COM Automation ===
see [http://wiki.lazarus.freepascal.org/Office_Automation#Using_COM_Automation_to_interact_with_OpenOffice_and_Microsoft_Office  Using COM Automation to interact with OpenOffice and Microsoft Office].
 
  
==== OSX alternative ====
+
В Windows COM Automation - это мощный способ не только удаленного манипулирования другими программами, но и предоставления другим программам возможности манипулировать вашей программой. С помощью Delphi вы можете сделать вашу программу одновременно клиентом автоматизации COM и сервером автоматизации COM, то есть она может манипулировать другими программами и, в свою очередь, [позволять] манипулировать [собой] другим программам. Например,
Unfortunately, COM Automation isn't available on OS X and Linux. However, you can simulate some of the functionality of COM Automation on OS X using AppleScript.
+
см. [[Office_Automation/ru#Использование COM Automation для работы с OpenOffice и Microsoft Office|Использование COM Automation для работы с OpenOffice и Microsoft Office]].
  
AppleScript is similar to COM Automation in some ways. For example, you can write scripts that manipulate other programs. Here's a very simple example of AppleScript that starts NeoOffice (the Mac version of OpenOffice.org):
+
==== Альтернатива [COM для] macOS ====
 +
К сожалению, COM Automation недоступна в macOS и Linux. Однако вы можете имитировать некоторые функции COM Automation в macOS, используя AppleScript.
  
 +
AppleScript в некотором роде похож на COM Automation. Например, вы можете писать скрипты, которые манипулируют другими программами. Вот очень простой пример AppleScript, который запускает NeoOffice (версия OpenOffice.org для Mac):
 +
<syntaxhighlight lang="bash">
 
   tell application "NeoOffice"
 
   tell application "NeoOffice"
 
     launch
 
     launch
 
   end tell
 
   end tell
 +
</syntaxhighlight>
 +
Приложение, разработанное для работы с AppleScript, предоставляет «словарь» классов и команд, которые можно использовать с приложением, аналогично классам сервера Windows Automation. Однако даже такие приложения, как NeoOffice, которые не предоставляют словарь, все равно будут реагировать на команды "launch" (запуск), "activate" (активация) и "quit" (выход). AppleScript может быть запущен из редактора сценариев macOS или Finder или даже преобразован в приложение, которое вы можете поместить на док-станцию, как любое другое приложение. Вы также можете запустить AppleScript из своей программы, как в этом примере:
  
An app that is designed to be manipulated by AppleScript provides a "dictionary" of classes and commands that can be used with the app, similar to the classes of a Windows Automation server. However, even apps like NeoOffice that don't provide a dictionary will still respond to the commands "launch", "activate" and "quit". AppleScript can be run from the OS X Script Editor or Finder or even converted to an app that you can drop on the dock just like any app. You can also run AppleScript from your program, as in this example:
+
<syntaxhighlight lang="bash">  fpsystem('myscript.applescript');</syntaxhighlight>
  
  fpsystem('myscript.applescript');
+
Предполагается, что скрипт находится в указанном файле. Вы также можете запускать сценарии на лету из своего приложения с помощью команды macOS OsaScript:
  
This assumes the script is in the indicated file. You can also run scripts on the fly from your app using the OS X OsaScript command:
+
<syntaxhighlight lang="bash">  fpsystem('osascript -e '#39'tell application "NeoOffice"'#39 +
 
 
  fpsystem('osascript -e '#39'tell application "NeoOffice"'#39 +
 
 
         ' -e '#39'launch'#39' -e '#39'end tell'#39);
 
         ' -e '#39'launch'#39' -e '#39'end tell'#39);
         {Note use of #39 to single-quote the parameters}
+
         {Note use of #39 to single-quote the parameters}</syntaxhighlight>
 
 
However, these examples are just the equivalent of the following Open command:
 
  
  fpsystem('open -a NeoOffice');
+
Однако эти примеры являются эквивалентом следующей команды Open:
  
Similarly, in OS X you can emulate the Windows shell commands to launch a web browser and launch an email client with:
+
<syntaxhighlight lang="bash">  fpsystem('open -a NeoOffice');</syntaxhighlight>
  
  fpsystem('open -a safari "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');
+
Аналогично, в macOS вы можете эмулировать команды оболочки Windows для запуска веб-браузера и запуска почтового клиента с:
  
and
+
<syntaxhighlight lang="bash">  fpsystem('open -a safari "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');</syntaxhighlight>
  
  fpsystem('open -a mail "mailto:ss4200@invalid.org"');
+
и
  
which assumes, fairly safely, that an OS X system will have the Safari and Mail applications installed. Of course, you should never make assumptions like this, and for the two previous examples, you can in fact just rely on OS X to do the right thing and pick the user's default web browser and email client if you instead use these variations:
+
<syntaxhighlight lang="bash">  fpsystem('open -a mail "mailto:ss4200@invalid.org"');</syntaxhighlight>
  
  fpsystem('open "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');
+
что предполагает, [что это] достаточно безопасно, что в системе macOS будут установлены приложения Safari и Mail. Конечно, вы никогда не должны делать подобные предположения, и для двух предыдущих примеров вы можете просто положиться на macOS, чтобы поступить правильно и выбрать веб-браузер пользователя по умолчанию и почтовый клиент, если вы вместо этого используете эти варианты:
  
and
+
<syntaxhighlight lang="bash">  fpsystem('open "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');</syntaxhighlight>
  
  fpsystem('open "mailto:ss4200@invalid.org"');
+
и
  
Do not forget to include the Unix unit in your uses clause if you use <code>fpsystem</code> or <code>shell</code> (interchangeable).
+
<syntaxhighlight lang="bash"> fpsystem('open "mailto:ss4200@invalid.org"');</syntaxhighlight>
  
The real power of AppleScript is to manipulate programs remotely to create and open documents and automate other activities. How much you can do with a program depends on how extensive its AppleScript dictionary is (if it has one). For example, Microsoft's Office X programs are not very usable with AppleScript, whereas the newer Office 2004 programs have completely rewritten AppleScript dictionaries that compare in many ways with what's available via the Windows Office Automation servers.
+
Не забудьте включить модуль Unix в вашу секцию uses, если вы используете <code>fpsystem</code> или <code>shell</code> (взаимозаменяемые).
  
==== Linux alternatives ====
+
Настоящая сила AppleScript заключается в удаленном управлении программами для создания и открытия документов и автоматизации других действий. Сколько вы можете сделать с [этой] программой, зависит от того, насколько обширным является ее словарь AppleScript (если он есть). Например, программы Microsoft Office X не очень пригодны для использования с AppleScript, тогда как более новые программы Office 2004 полностью переписали словари AppleScript, которые во многих отношениях сравниваются с тем, что доступно через серверы Windows Office Automation.
While Linux shells support sophisticated command line scripting, the type of scripting is limited to what can be passed to a program on the command line. There is no single, unified way to access a program's internal classes and commands  with Linux the way they are via Windows COM Automation and OS X AppleScript. However, individual desktop environments (GNOME/KDE) and application frameworks often provide such methods of interprocess communication. On GNOME see Bonobo Components. KDE has the KParts framework, DCOP. OpenOffice has a platform neutral API for controlling the office remotely (google OpenOffice SDK) - though you would probably have to write glue code in another language that has bindings (such as Python) to use it. In addition, some applications have "server modes" activated by special command-line options that allow them to be controlled from another process. It is also possible (Borland did it with Kylix document browser) to "embed" one top-level X application window into another using XReparentWindow (I think).
 
  
As with Windows, many OS X and Linux programs are made up of multiple library files (.dylib and .so extensions). Sometimes these libraries are designed so you can also use them in programs you write. While this can be a way of adding some of the functionality of an external program to your program, it's not really the same as running and manipulating the external program itself. Instead, your program is just linking to and using the external program's library similar to the way it would use any programming library.
+
==== Альтернативы для Linux ====
 +
В то время как оболочка 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 (я думаю).
  
=== Alternatives for Windows API functions ===
+
Как и в Windows, многие программы для macOS и Linux состоят из нескольких библиотечных файлов (расширения .dylib и .so). Иногда эти библиотеки разработаны так, что вы также можете использовать их в программах, которые вы пишете. Хотя это может быть способом добавления некоторых функций внешней программы к вашей программе, на самом деле это не то же самое, что запуск самой внешней программы и управление ею. Вместо этого ваша программа просто ссылается на библиотеку внешней программы и использует ее подобно тому, как она использует любую библиотеку программирования.
  
Many Windows programs use the Windows API extensively. In cross-platform applications Win API functions in the Windows unit should not be used, or should be enclosed by a conditional compile (e.g. {$IFDEF MSWINDOWS} ).
+
=== Альтернативы для функций Windows API ===
  
Fortunately many of the commonly used Windows API functions are implemented in a multiplatform way in the unit [[lclintf]]. This can be a solution for programs which rely heavily on the Windows API, although the best solution is to replace these calls with true cross-platform components from the LCL. You can replace calls to GDI painting functions with calls to a TCanvas object's methods, for example.
+
Многие программы Windows широко используют Windows API. В кроссплатформенных приложениях функции Win API в модуле Windows не должны использоваться или должны быть заключены в условную компиляцию (например, {$IFDEF MSWINDOWS}).
  
=== Key codes ===
+
К счастью, многие из часто используемых функций Windows API реализованы многоплатформенным способом в модуле [[lclintf]]. Это может быть решением для программ, которые в значительной степени зависят от Windows API, хотя лучшее решение - заменить эти вызовы настоящими кроссплатформенными компонентами из LCL. Например, вы можете заменить вызовы функций рисования GDI вызовами методов объекта TCanvas.
Fortunately, detecting key codes (e.g. on KeyUp events) is portable: see [[LCL Key Handling]].
 
  
===Installing your application===
+
=== Коды клавиш ===
See [[Deploying Your Application]].
+
К счастью, обнаружение кодов клавиш (например, по событиям KeyUp) является переносимым: см. [[LCL Key Handling]].
  
== See Also ==
+
===Установка вашего приложения===
* [[Writing portable code regarding the processor architecture]]
+
См. [[Deploying Your Application|Развертывание вашего приложения]].
* [[Introduction to platform-sensitive development]]
 
* http://www.midnightbeach.com/KylixForDelphiProgrammers.html A guide for Windows programmers starting with Kylix. Many of concepts / code snippets apply to Lazarus.
 
* http://www.stack.nl/~marcov/porting.pdf A guide for writing portable source code, mainly between different compilers.
 
  
[[Category:Tutorials]]
+
== Смотри также ==
[[Category:Multiplatform Programming]]
+
* [[Writing portable code regarding the processor architecture/ru|Написание переносимого кода относительно архитектуры процессора]]
[[Category:Platform-sensitive development]]
+
* [[Introduction to platform-sensitive development|Введение в чувствительную к платформе разработку]]
[[Category:Inter-process communication]]
+
* [http://www.midnightbeach.com/KylixForDelphiProgrammers.html Руководство для программистов Windows, начиная с Kylix.] Многие из концепций / фрагментов кода применимы к Lazarus.
 +
* [http://www.stack.nl/~marcov/porting.pdf Руководство по написанию переносимого исходного кода, в основном между различными компиляторами.]

Latest revision as of 22:42, 1 November 2021

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

Большинство LCL приложений работают в кроссплатформенном режиме без каких-либо дополнительных усилий.

Это руководство по написанию кроссплатформенных приложений с использованием Lazarus и Free Pascal. Он будет охватывать необходимые меры предосторожности, чтобы помочь в создании кроссплатформенной программы, готовой к Развертывание вашего приложения.

Введение в мультиплатформенное (кроссплатформенное) программирование

Сколько платформ вам нужно?

Чтобы ответить на этот вопрос, вы должны сначала определить, кто ваши потенциальные пользователи и как ваша программа будет использоваться. Этот вопрос зависит от того, где вы развертываете свое приложение.

Если вы разрабатываете стандартное настольное программное обеспечение в 2014 году, Microsoft Windows может быть самой важной платформой. Обратите внимание, что macOS и/или Linux набирают популярность и могут стать важной целью для вашего приложения.

Популярность различных операционных систем для настольных компьютеров отличается в зависимости от страны, типа используемого программного обеспечения и целевой аудитории; тут нет общего правила. Например, macOS довольно популярна в Северной Америке и Западной Европе, в то время как в Южной Америке компьютеры Mac в основном ограничены работой с видео и звуком.

Для многих контрактных проектов важна только одна платформа. Free Pascal и Lazarus вполне способны писать программы, ориентированные на конкретную платформу. Вы можете, например, получить доступ ко всему API Windows, чтобы написать хорошо интегрированную программу Windows.

Если вы разрабатываете программное обеспечение, которое будет работать на веб-сервере, обычно используется платформа Unix в одном из ее различных вариантов. В этом случае, возможно, только Linux, Solaris, * BSD и другие Unix-системы имеют смысл в качестве целевых платформ, хотя вы можете добавить поддержку Windows для полноты [картины].

Решив любые кросс-платформенные проблемы в своем дизайне, вы можете в значительной степени игнорировать другие платформы, так же, как и при разработке для одной платформы. Однако в какой-то момент вам нужно будет протестировать развертывание и запуск вашей программы на других платформах. Для этого будет полезно иметь неограниченный доступ к машинам с целевыми операционными системами. Если вам не нужны несколько физических компьютеров, попробуйте решения с двойной загрузкой или виртуальные машины, такие как VMware или Parallels.

Кроссплатформенное программирование

Работа с файлами и папками

При работе с файлами и папками важно использовать платформо-независимые разделители путей и [маркеры] конца строки. Вот список объявленных констант в Lazarus, которые будут использоваться при работе с файлами и папками.

  • PathSep, PathSeparator: разделитель пути при добавлении нескольких путей вместе (';', ...)
  • PathDelim, DirectorySeparator: разделитель каталогов для каждой платформы ('/', '\', ...)
  • LineEnding: правильная последовательность символов окончания строки (#13#10 - CRLF, #10 - LF, ...)

Еще одна важная вещь, которую следует отметить, - это чувствительность к регистру [имен файлов и каталогов] файловой системы. В Windows имена файлов обычно не чувствительны к регистру, в то время как они обычно [регистрозависимы] на платформах Linux и BSD. Но если файловая система EXT2, EXT3 и т.д. смонтирована в Windows, она будет чувствительна к регистру. Соответственно, файловая система FAT, смонтированная в Linux, не должна учитывать регистр символов.

Следует обратить особое внимание, что NTFS не чувствительна к регистру при использовании в Windows, но она чувствительна к регистру при монтировании ОС POSIX. Это может вызвать различные проблемы, в том числе потерю файлов, если файлы с одинаковыми именами файлов в разных случаях существуют в разделе NTFS, смонтированном в Windows. Разработчики должны рассмотреть возможность использования пользовательских функций для проверки и предотвращения создания нескольких файлов с одинаковыми именами в NTFS.

macOS по умолчанию использует имена файлов без учета регистра. Это может быть причиной досадных ошибок, поэтому любое переносимое приложение должно постоянно использовать имена файлов.

RTL-функции файлов используют системную кодировку для имен файлов. Под Windows это одна из кодовых страниц Windows, в то время как Linux, BSD и macOS обычно используют UTF-8. Модуль FileUtil [библиотеки] LCL предоставляет файловые функции, которые принимают строки UTF-8, как и остальная часть LCL.

// [функциям] AnsiToUTF8 и UTF8ToAnsi нужен менеджер широких строк (widestringmanager) под Linux, BSD, macOS,
// но обычно эти ОС используют UTF-8 в качестве системной кодировки, поэтому [там] менеджер широких строк
// не нужен.
function NeedRTLAnsi: boolean;// true, если системная кодировка не UTF-8
procedure SetNeedRTLAnsi(NewValue: boolean);
function UTF8ToSys(const s: string): string;// как UTF8ToAnsi, но более не зависим от widestringmanager
function SysToUTF8(const s: string): string;// как AnsiToUTF8, но более не зависим от widestringmanager
function UTF8ToConsole(const s: string): string;// преобразовывает строку UTF8 в консольную кодировку (используется Write, WriteLn)

// файловые операции
function FileExistsUTF8(const Filename: string): boolean;
function FileAgeUTF8(const FileName: string): Longint;
function DirectoryExistsUTF8(const Directory: string): Boolean;
function ExpandFileNameUTF8(const FileName: string): string;
function ExpandUNCFileNameUTF8(const FileName: string): string;
function ExtractShortPathNameUTF8(Const FileName : String) : String;
function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;
function FindNextUTF8(var Rslt: TSearchRec): Longint;
procedure FindCloseUTF8(var F: TSearchrec);
function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;
function FileGetAttrUTF8(const FileName: String): Longint;
function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;
function DeleteFileUTF8(const FileName: String): Boolean;
function RenameFileUTF8(const OldName, NewName: String): Boolean;
function FileSearchUTF8(const Name, DirList : String): String;
function FileIsReadOnlyUTF8(const FileName: String): Boolean;
function GetCurrentDirUTF8: String;
function SetCurrentDirUTF8(const NewDir: String): Boolean;
function CreateDirUTF8(const NewDir: String): Boolean;
function RemoveDirUTF8(const Dir: String): Boolean;
function ForceDirectoriesUTF8(const Dir: string): Boolean;

// окружение
function ParamStrUTF8(Param: Integer): string;
function GetEnvironmentStringUTF8(Index: Integer): string;
function GetEnvironmentVariableUTF8(const EnvVar: string): String;
function GetAppConfigDirUTF8(Global: Boolean): string;

// другое
function SysErrorMessageUTF8(ErrorCode: Integer): String;



Прим.перев.: после появления поддержки юникода на уровне компилятора (FPC 2.7.1) вместо AnsiToUTF8, UTF8ToAnsi, SysToUTF8, UTF8ToAnsi для работы с WinAPI рекомендуется использовать функции UTF8ToWinCP и WinCPToUTF8. Подробнее здесь: RTL с кодовой страницей UTF-8 по умолчанию


Пустые имена файлов и двойные разделители путей

Существуют различия в обработке имен файлов/каталогов в Windows по сравнению с Linux/Unix/Unix-подобными системами.

  • Windows позволяет пустые имена файлов. Вот почему FileExistsUTF8 ('..\') проверяет в Windows в родительском каталоге наличие файла без имени.
  • В Linux/Unix/Unix-подобных системах пустой файл сопоставляется с каталогом, а каталоги рассматриваются как файлы. Это означает, что FileExistsUTF8 ('../') в Unix проверяет наличие родительского каталога, что обычно приводит к значению true.

Двойные разделители пути в именах файлов также обрабатываются по-разному:

  • Windows: 'C:\' не то же самое, что 'C:\\'
  • Unix-подобные OS: путь '/usr//' совпадает с '/usr/'. Если '/usr' является каталогом, то даже все три равнозначны.

Это важно при объединении имен файлов. Например:

FullFilename:=FilePath+PathDelim+ShortFilename; // может привести к двум PathDelims, которые дают разные результаты под Windows и Linux
FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // создает только один PathDelim
FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // создает только один PathDelim и делает еще несколько чисток

Прим.перев.: для "нормализации" количества и вида разделителей в путях папок и файлов можно использовать следующие функции из модуля LazFileUtils

function CleanAndExpandDirectory(const Filename: string): string;
function CleanAndExpandFilename(const Filename: string): string;

Функция TrimFilename заменяет разделители двойных путей одиночными и сокращает пути '..'. Например /usr//lib/../src обрезается до /usr/src.

Если вы хотите узнать, существует ли каталог, используйте DirectoryExistsUTF8.

Другая распространенная задача - проверить, существует ли часть пути имени файла. Вы можете получить путь с помощью ExtractFilePath, но он будет содержать разделитель пути.

  • Под Unix-подобной системой вы можете просто использовать FileExistsUTF8 в пути. Например, FileExistsUTF8('/home/user/') вернет true, если каталог /home/user существует.
  • В Windows вы должны использовать функцию DirectoryExistsUTF8, но перед этим вы должны удалить разделитель пути, например, с помощью функции ChompPathDelim.

В Unix-подобных системах корневым каталогом является '/', а использование функции ChompPathDelim создаст пустую строку. Функция DirPathExists работает как функция DirectoryExistsUTF8, но обрезает заданный путь.

Обратите внимание, что Unix/Linux использует символ '~' (тильда) для обозначения домашнего каталога, обычно '/home/jim/' для пользователя с именем jim. Так что '~/myapp/myfile' и '/home/jim/myapp/myfile' идентичны в командной строке и в скриптах. Тем не менее, тильда не будет автоматически расширяться Lazarus'ом. Необходимо использовать ExpandFileNameUTF8('~/myapp/myfile'), чтобы получить полный путь.



Прим.перев.: Если code completion "не находит" выше описанные функции, добавьте в секцию uses модули LazUTF8 и LazFileUtils.


Кодировка текста

Текстовые файлы часто кодируются в текущей кодировке системы. В Windows это обычно одна из кодовых страниц Windows, в то время как Linux, BSD и macOS обычно используют UTF-8. Не существует 100%-го правила, чтобы узнать, какую кодировку использует текстовый файл. Модуль LCL lconvencoding имеет функцию для угадывания кодировки:

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

И он содержит функции для преобразования из одной кодировки в другую:

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

function UTF8BOMToUTF8(const s: string): string; // UTF8 с BOM
function ISO_8859_1ToUTF8(const s: string): string; // Центральная Европа
function CP1250ToUTF8(const s: string): string; // Центральная Европа
function CP1251ToUTF8(const s: string): string; // кириллица
function CP1252ToUTF8(const s: string): string; // latin 1
...
function UTF8ToUTF8BOM(const s: string): string; // UTF8 с BOM
function UTF8ToISO_8859_1(const s: string): string; // Центральная Европа
function UTF8ToCP1250(const s: string): string; // Центральная Европа
function UTF8ToCP1251(const s: string): string; // кириллица
function UTF8ToCP1252(const s: string): string; // latin 1
...

Например, чтобы загрузить текстовый файл и преобразовать его в UTF-8, вы можете использовать:

var
  sl: TStringList;
  OriginalText: String;
  TextAsUTF8: String;
begin
  sl:=TStringList.Create;
  try
    sl.LoadFromFile('sometext.txt'); // осторожно: это изменяет конец строки на конец строки OCи
    OriginalText:=sl.Text;
    TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);
    ...
  finally
    sl.Free;
  end;
end;

А для сохранения текстового файла в системной кодировке вы можете использовать:

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

Конфигурационные файлы

Вы можете использовать функцию GetAppConfigDir из модуля SysUtils, чтобы найти подходящее место для хранения файлов конфигурации в другой системе. Функция имеет один параметр, называемый Global. Если [его значение] - True, то возвращаемый каталог является глобальным каталогом, то есть действительным для всех пользователей в системе. Если параметр Global имеет значение false, тогда каталог является специфическим для пользователя, выполняющего программу. В системах, которые не поддерживают многопользовательские среды, эти два каталога могут быть одинаковыми.

Существует также [функция] GetAppConfigFile, который возвращает соответствующее имя для файла конфигурации приложения. Вы можете использовать это так:

ConfigFilePath := GetAppConfigFile(False);

Ниже приведены примеры вывода функций пути по умолчанию в различных системах:

program project1;

{$mode objfpc}{$H+}

uses
  SysUtils;

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

Вывод в системе GNU/Linux с FPC 2.2.2. Обратите внимание, что использование True является ошибкой, что уже исправлено в 2.2.3:

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

Вы можете заметить, что глобальные файлы конфигурации хранятся в каталоге /etc, а локальные конфигурации хранятся в скрытой папке в домашнем каталоге пользователя. Каталоги, имя которых начинается с точки (.), скрыты в Linux. Вы можете создать каталог в месте, возвращаемом GetAppConfigDir, а затем сохранить там файлы конфигурации.

Light bulb  Примечание: Обычные пользователи не могут писать в каталог /etc. Это могут делать только пользователи с правами администратора.

Вывод на последние версии Windows с FPC 3.0.0 +:

C:\ProgramData\project1\
C:\Users\user\AppData\Local\project1\
C:\ProgramData\project1\project1.cfg
C:\Users\user\AppData\Local\project1\project1.cfg

Обратите внимание, что до FPC 2.2.4 функция использовала каталог, в котором приложение должно было хранить глобальные конфигурации в Windows.

Вывод на Windows 98 с FPC 2.2.0:

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

Вывод на macOS с FPC 2.2.0:

/etc
/Users/user/.config/project1
/etc/project1.cfg
/Users/user/.config/project1/project1.cfg
Light bulb  Примечание: Использование UPX мешает использованию функций GetAppConfigDir и GetAppConfigFile.



Прим.перев.: Очевидно, имеется ввиду библиотека упаковщика исполняемых файлов - UPX (the Ultimate Packer for eXecutables)



Light bulb  Примечание: В macOS файлы конфигурации в большинстве случаев являются файлами предпочтений, которые должны быть файлами XML с окончанием ".plist" и храниться в /Library/Preferences или ~/Library/Preferences с именами, взятыми из поля "Bundle identifier"(Идентификатор пакета) в Info.plist пакета приложения. Использование вызовов Carbon CFPreference ... вероятно, самый простой способ добиться этого. Файлы .config в каталоге User являются нарушением указаний по программированию.

Файлы данных и ресурсов

Очень распространенный вопрос - где хранить файлы данных, которые могут понадобиться приложению, такие как изображения, музыка, файлы XML, файлы базы данных, файлы справки и т.д. К сожалению, нет кроссплатформенной функции, которая бы обеспечивала лучшее место для поиска файлов данных. Решение состоит в том, чтобы реализовать по-разному на каждой платформе, используя [директивы условной компиляции] IFDEF.

Windows

В Windows данные приложения, которые изменяет программа, должны быть помещены не в каталог приложения (например, C:\Program Files\), а в определенном месте (см., например, Как написать приложение для Windows XP, в разделе "Classify Application Data"). Windows Vista и новее активно применяют это (пользователи имеют доступ на запись в эти каталоги только при использовании повышения [прав] или отключения UAC), но используют механизм перенаправления папок для поддержки старых, неправильно запрограммированных приложений. Просто чтение, а не запись данных из каталогов приложений все равно будет работать.

Короче говоря: используйте такую папку:

    OpDirLocal:= GetEnvironmentVariableUTF8('appdata')+'\MyAppName';


См. Советы по программированию Windows: получение специальных папок (Мои документы, Рабочий стол, локальные данные приложения и т.д.).

Unix/Linux

В большинстве Unix-систем (таких как Linux, FreeBSD, OpenBSD, Solaris) файлы данных приложения расположены в фиксированном месте, которое может выглядеть примерно так: /usr/share/app_name или /opt/app_name.

Данные приложения, которые должны быть записаны приложением, часто хранятся в таких местах, как /var/<programname>, с установленными соответствующими разрешениями.

Специфичные для пользователя конфигурации/данные для чтения/записи обычно хранятся где-то в домашнем каталоге пользователя (например, в ~/.myfancyprogram).

Как получить путь к этому домашнему каталогу:

    OpDirLocal:= GetEnvironmentVariableUTF8('HOME')+'/.myappname';

macOS

macOS является исключением среди UNIX. Приложение публикуется в связке - каталоге с расширением "app", которое обрабатывается файловым менеджером как файл (вы также можете сделать "cd path/myapp.app"). Ваши файлы ресурсов должны находиться внутри пакета. Если пакет - "path/MyApp.app", то:

  • исполняемый файл - "path/MyApp.app/Contents/MacOS/myapp"
  • каталог ресурсов - "path/MyApp.app/Contents/Resources"

Сохраните файлы конфигурации в домашний каталог:

    OpDirLocal:= GetEnvironmentVariableUTF8('HOME')+'/.myappname';

Читайте ресурсы с:

    OpDirRes:= ExtractFileDir(ExtractFileDir(Application.ExeName))+'/Resources';
Warning-icon.png

Предупреждение: никогда не используйте paramstr(0) на любой платформе Unix для определения расположения исполняемого файла, так как это соглашение Dos-Windows-OS/2 и имеет несколько концептуальных проблем, которые не могут быть решены с помощью эмуляции на других платформ. Единственное, что paramstr(0) гарантированно возвращает на платформах Unix, это имя, под которым была запущена программа. Каталог, в котором он находится, и имя фактического бинарного файла (в случае, если он был запущен с использованием символической ссылки) не обязательно будут доступны через paramstr(0).

32/64 bit

Обнаружение разрядности ОСи во время выполнения

Хотя вы можете контролировать, компилируете ли вы 32- или 64-битную версию с помощью определений компилятора, иногда вы захотите знать, какова разрядность операционной системы. Например, если вы запускаете 32-битную программу Lazarus в 64-битной Windows, вы можете запустить внешнюю программу из каталога Program Files (x86) для 32-битных программ или вы можете захотеть получить различную информацию пользователя: мне это нужно в моей программе установки Лазаруса LazUpdater, чтобы предложить пользователю выбор 32- и 64-битных компиляторов. Код: пример определения Windows x32-x64.

Обнаружение разрядности внешней библиотеки перед ее загрузкой

Если вы хотите загрузить функции из динамической библиотеки в вашу программу, она должна иметь ту же разрядность, что и ваше приложение. В 64-битной Windows ваше приложение может быть 32-битным или 64-битным, и в вашей системе могут быть 32-битные и 64-битные библиотеки. Поэтому вы можете проверить, является ли разрядность dll такой же, как разрядность вашего приложения, прежде чем загружать dll динамически. Вот функция, которая проверяет разрядность dll, (предоставленная на форуме пользователем GetMem):

uses {..., } JwaWindows;

function GetPEType(const APath: WideString): Byte;
const
  PE_UNKNOWN = 0; // если файл не является корректной dll, возвращается 0
 // PE_16BIT   = 1; // не поддерживается этой функцией
  PE_32BIT   = 2;
  PE_64BIT   = 3;
var
  hFile, hFileMap: THandle;
  PMapView: Pointer;
  PIDH: PImageDosHeader;
  PINTH: PImageNtHeaders;
  Base: Pointer;
begin
  Result := PE_UNKNOWN;
 
  hFile := CreateFileW(PWideChar(APath), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if hFile = INVALID_HANDLE_VALUE then
  begin
    CloseHandle(hFile);
    Exit;
  end;
 
  hFileMap  := CreateFileMapping(hFile, nil, PAGE_READONLY, 0, 0, nil);
  if hFileMap = 0 then
  begin
    CloseHandle(hFile);
    CloseHandle(hFileMap);
    Exit;
  end;
 
  PMapView := MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
  if PMapView = nil then
  begin
    CloseHandle(hFile);
    CloseHandle(hFileMap);
    Exit;
  end;
 
  PIDH := PImageDosHeader(PMapView);
  if PIDH^.e_magic <> IMAGE_DOS_SIGNATURE then
  begin
    CloseHandle(hFile);
    CloseHandle(hFileMap);
    UnmapViewOfFile(PMapView);
    Exit;
  end;
 
  Base := PIDH;
  PINTH := PIMAGENTHEADERS(Base + LongWord(PIDH^.e_lfanew));
  if PINTH^.Signature = IMAGE_NT_SIGNATURE then
  begin
    case PINTH^.OptionalHeader.Magic of
      $10b: Result := PE_32BIT;
      $20b: Result := PE_64BIT
    end;
  end;
 
  CloseHandle(hFile);
  CloseHandle(hFileMap);
  UnmapViewOfFile(PMapView);
end;

//Теперь, если вы компилируете свое приложение для 32-битной и 64-битной windows, вы можете проверить, является ли разрядность dll такой же, как у вашего приложения:
function IsCorrectBitness(const APath: WideString): Boolean;
begin  
  {$ifdef CPU32}
    Result := GetPEType(APath) = 2; //приложение скомпилировано как 32-битное, мы спрашиваем, возвращает ли GetPeType 2
  {$endif}
  {$ifdef CPU64}
    Result := GetPEType(APath) = 3; //приложение скомпилировано как 64-битное, мы спрашиваем, возвращает ли GetPeType 3
  {$endif}
end;

Приведение типов Pointer/Integer

Для указателей под 64-бит требуется 8 байтов вместо 4-х на 32-битных. Тип 'Integer' остается 32-битным на всех платформах для совместимости. Это означает, что вы не можете приводить тип pointers к целым числам и обратно.

FPC задает для этого два типа: PtrInt и PtrUInt. PtrInt представляет собой 32-битное целое число со знаком на 32-битных платформах и 64-битное целое число со знаком на 64-битных платформах. То же самое для PtrUInt, но только - это целое число без знака.

Используйте для кода, который должен работать с Delphi и FPC:

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

Замените все integer(SomePointerOrObject) на PtrInt(SomePointerOrObject).

Порядок байтов

Платформы Intel имеют младший порядок байтов, это означает, что младший байт стоит первым. Например, два байта слова $1234 хранятся как $34 $12 в системах с младшим порядком байтов. В системах со старшим порядком байтов, таких как powerpc, два байта слова $1234 хранятся как $12 $34. Разница важна при чтении файлов, созданных в других системах.


Прим.перев.: подробнее о порядке байтов можно почитать в Википедии


Используйте для кода, который должен работать в обоих [случаях]:

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

И наоборот для ENDIAN_LITTLE.

Системный модуль предоставляет достаточно много функций преобразования порядка байтов, таких как SwapEndian, BEtoN (со старшим порядком байтов в текущий), LEtoN (с младшим порядком байтов в текущий), NtoBE (с текущим порядком байтов в старший) и NtoLE (с текущим порядком байтов в младший).


Libc и другие специальные модули

Избегайте устаревших модулей, таких как "oldlinux" и "libc", которые не поддерживаются вне linux/i386.

Ассемблер

Избегайте использования ассемблера.

Директивы компилятора

{$ifdef CPU32}
...напишите здесь код для 32-битных процессоров
{$ENDIF}
{$ifdef CPU64}
...напишите здесь код для 64-битных процессоров
{$ENDIF}

Проекты, пакеты и пути поиска

Проекты и пакеты Lazarus предназначены для нескольких платформ. Обычно вы можете просто скопировать проект и необходимые пакеты на другой компьютер и скомпилировать их там. Вам не нужно создавать по отдельному проекту для каждой платформы.

Несколько советов для достижения этого.

Компилятор создает для каждого модуля ppu-файл с таким же именем. Этот ppu-файл может быть использован другими проектами и пакетами. Исходные файлы модулей (например, unit1.pas) не должны быть общими. Просто дайте компилятору выходной каталог модуля, в котором нужно создавать файлы ppu. IDE делает это по умолчанию, поэтому здесь вам нечего делать.

Каждый файл модуля должен быть частью одного проекта или пакета. Если файл модуля используется только одним проектом, добавьте его в этот проект. В противном случае добавьте его в пакет. Если вы еще не создали пакет для общих модулей, см. здесь: создание пакета для ваших общих модулей.

Каждый проект и каждый пакет должны иметь разобщенные каталоги - они не должны совместно использовать каталоги. В противном случае вы должны быть экспертом в искусстве поиска путей компилятора. Если вы не являетесь экспертом или если те, кто может использовать ваш проект/пакет, не являются экспертами: не делитесь каталогами между проектами/пакетами.

Платформозависимые модули

Например, модуль wintricks.pas должен использоваться только под Windows. В разделе uses используйте:

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

Если модуль является частью пакета, вы также должны выбрать модуль в редакторе пакетов и снять флажок Use unit(Использовать модуль).

См. также платформозависимые модули

Платформозависимые пути поиска

Когда вы ориентируетесь на несколько платформ и обращаетесь к операционной системе напрямую, вы быстро устаете от бесконечных конструкций IFDEF. Одним из решений, которое часто используется в исходниках FPC и Lazarus, является использование include-файлов. Создайте по одному подкаталогу для [каждой] целевой [ОС]. Например, win32, linux, bsd, darwin. Поместите в каждый каталог включаемый файл с тем же именем. Затем используйте макрос во включаемом пути. Модуль может использовать обычную включающую директиву. Пример для одного include-файла для каждого набора виджетов LCL:

Создайте один файл для каждого набора виджетов, который вы хотите поддерживать:

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

Вам не нужно добавлять файлы в пакет или проект. Добавьте включаемый путь поиска $(LCLWidgetType) в параметры компилятора вашего пакета или проекта.

В вашем модуле используйте директиву:

{$I example.inc}

Вот некоторые полезные макросы и общие значения:

  • LCLWidgetType: win32, gtk, gtk2, qt, carbon, fpgui, nogui
  • TargetOS: linux, win32, win64, wince, freebsd, netbsd, openbsd, darwin (многое другое)
  • TargetCPU: i386, x86_64, arm, powerpc, sparc
  • SrcOS: win, unix

Вы можете использовать макрос $Env() для использования переменных окружения.

И конечно вы можете использовать комбинации. Например, LCL использует:

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

Смотрите здесь полный список макросов: макросы в путях и именах файлов

Аппаратно- / пользователь-зависимые пути поиска

Например, у вас есть две машины Windows, Stan'а и Oliver'а. На [машине] Stan'а ваши модули находятся в C:\units, а на [машине] Oliver'а ваши модули находятся в D:\path. Модули принадлежат пакету SharedStuff, который представляет собой C:\units\sharedstuff.lpk на [машине] Stan'а и D:\path\sharedstuff.lpk на [машине] Oliver'а.

После того, как вы открыли lpk в IDE или в lazbuild, путь автоматически сохраняется в его файлах конфигурации (packagefiles.xml). При компиляции проекта, для которого требуется пакет SharedStuff, среда IDE и lazbuild знают, где он находится. Так что никакой [дополнительной] конфигурации не требуется.

Если вы хотите развернуть пакет на нескольких компьютерах или для всех пользователей компьютера (например, бассейн для студентов), вы можете добавить файл lpl в исходный каталог lazarus. Смотрите packager/globallinks для [получения] примеров.

Различия языковой среды

Некоторые функции из Free Pascal, такие как StrToFloat, ведут себя по-разному в зависимости от текущей языковой среды [ОСи]. Например, в США десятичный разделитель обычно равен ".", но во многих странах Европы и Южной Америки это ",". Это может быть проблемой, поскольку иногда желательно, чтобы эти функции вели себя фиксированным образом, независимо от языковых настроек операционной системы. Примером является формат файла с десятичными точками, который всегда должен интерпретироваться одинаково.

В следующих разделах объясняется, как это сделать.

StrToFloat

Новый набор настроек формата, которые устанавливают фиксированный десятичный разделитель, может быть создан с помощью следующего кода:

// в вашем файле проекта .lpr
uses
...
{$IFDEF UNIX}
clocale 
{ Требуется в Linux/Unix для поддержки форматирования. Должен быть одним из первых (вероятно, после cthreads?)}
{$ENDIF}

и:

// в вашем коде:
var
  FPointSeparator, FCommaSeparator: TFormatSettings;
begin
  // Настройки формата для преобразования строки в число с плавающей точкой
  FPointSeparator := DefaultFormatSettings;
  FPointSeparator.DecimalSeparator := '.';
  FPointSeparator.ThousandSeparator := '#';// отключаем тысячный разделитель 
  FCommaSeparator := DefaultFormatSettings;
  FCommaSeparator.DecimalSeparator := ',';
  FCommaSeparator.ThousandSeparator := '#';// отключаем тысячный разделитель

Позже вы можете использовать настройки этого формата при вызове StrToFloat, например:

// Эта функция работает как StrToFloat, но просто пробует два возможных десятичных разделителя
// Это позволит избежать исключения, если формат строки не соответствует локали
function AnSemantico.StringToFloat(AStr: string): Double;
begin
  if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator)
  else Result := StrToFloat(AStr, FCommaSeparator);
end;

Gtk2 и маскировка исключений FPU

Библиотека Gtk2 изменяет значение по умолчанию маски исключений FPU (модуль [для операций] с плавающей запятой). Следствием этого является то, что некоторые исключения с плавающей точкой не возникают, если приложение использует библиотеку Gtk2. Это означает, что, например, если вы разрабатываете приложение LCL в Windows с набором виджетов win32/64 (который по умолчанию для Windows) и планируете компилировать для Linux (где Gtk2 является набором виджетов по умолчанию), вы должны помнить об этой несовместимости.

После этой темы форума и ответов на этот багрепорт стало ясно, что с этим ничего не поделаешь, поэтому мы должны знать, в чем эти различия.

Поэтому давайте проведем тест:

uses
  ..., math,...

{...}

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

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

Наша простая программа получит значение FPC по умолчанию:

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

Однако в Gtk2 только exOverflow не маскируется.

Следствием этого является то, что исключения EInvalidOp и EZeroDivide не возникают, если приложение ссылается на библиотеку Gtk2! Обычно, деление ненулевого значения на ноль возбуждает исключение EZeroDivide, а деление нуля на ноль возбуждает исключение EInvalidOp. Например, такой код:

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

try
  X := A / B;
  // блок кода 1
except   
  // блок кода 2
end;
// ...

при компиляции в приложении с набором виджетов Gtk2 примет другое направление. На виндовом widgetset'е, когда B равно нулю, будет сгенерировано исключение (EZeroDivide или EInvalidOp, в зависимости от того, равен ли A нулю или нет) и будет выполнен «блок кода 2». На Gtk2 X становится Infinity (положительным бесконечным числом), NegInfinity (отрицательным бесконечным числом) или NaN (не числовым значением) и «блок кода 1» будет выполнен.

Мы можем придумать разные способы преодоления этого несоответствия. В большинстве случаев вы можете просто проверить, равен ли B нулю, и не пытаться делить в этом случае. Однако иногда вам потребуется другой подход. Итак, взгляните на следующие примеры:

uses
  ..., math,...

//...
var
  X, A, B: Double;
  Ind: Boolean;
// ...
try
  X := A / B;
  Ind := IsInfinite(X) or IsNan(X); // на gtk2 мы "падаем" здесь
except   
  Ind := True; // на windows мы "падаем" здесь, когда B равен нулю
end;
if Ind then begin
  // блок кода 2
end else begin
  // блок кода 1
end;
// ...

Или:

uses
  ..., math,...

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

try
  FPUExceptionMask := GetExceptionMask;
  SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // демаскируем
  try
    X := A / B;
  finally
    SetExceptionMask(FPUExceptionMask); // немедленно возвращаем предыдущую маскировку, мы не должны позволять вызывать Gtk2 без маски
  end;
  // блок кода 1
except   
  // блок кода 2
end;
// ...

Будьте осторожны, не делайте что-то подобное (вызовите LCL с все еще удаленной маской):

try
  FPUExceptionMask := GetExceptionMask;
  SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
  try
    Edit1.Text := FloatToStr(A / B); // НЕТ! Настройка присвоение значения тексту Edit'а сводится к внутренним элементам набора виджетов, и Gtk2 API нельзя вызывать без маски!
  finally
    SetExceptionMask(FPUExceptionMask);
  end;
  // блок кода 1
except   
  // блок кода 2
end;
// ...

Но используйте вспомогательную переменную:

try
  FPUExceptionMask := GetExceptionMask;
  SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);
  try
    X := A / B; // Сначала мы устанавливаем вспомогательную переменную X
  finally
    SetExceptionMask(FPUExceptionMask);
  end;
  Edit1.Text := FloatToStr(X); // Теперь мы можем присвоить значение текста Edit'а.
  // code block 1
except   
  // code block 2
end;
// ...

Во всех ситуациях при разработке приложений LCL очень важно знать об этом и иметь в виду, что некоторые операции с плавающей запятой могут идти по-разному с разными наборами виджетов. Тогда вы можете придумать подходящий способ обойти это, но это не должно остаться незамеченным.

Проблемы при переходе с Windows на *nix и т.д.

Проблемы, специфичные для Linux, macOS, Android и другие Unix'ы, описаны здесь. Не все предметы могут применяться ко всем платформам.

В Unix нет "каталога приложений"

Многие программисты используют вызов ExtractFilePath(ParamStr(0)) или Application.ExeName для получения местоположения исполняемого файла, а затем поиска необходимых файлов для выполнения программы (изображений, файлов XML, файлов базы данных и т.д.) на основе расположения исполняемого файла. Это неправильно в Unix. Строка в ParamStr(0) может содержать каталог, отличный от каталога исполняемого файла, и он также зависит от различных программ командной оболочки (sh, bash и т.д.).

Даже если Application.ExeName на самом деле может знать каталог, в котором находится исполняемый файл, этот файл может быть символической ссылкой, так что вы можете вместо этого получить каталог ссылки (в зависимости от версии ядра Linux вы можете получить каталог ссылки или самого бинарного файла программы).

Чтобы избежать этого, прочитайте разделы о файлах конфигурации и файлах данных.

Обхождение без Windows COM Automation

В Windows COM Automation - это мощный способ не только удаленного манипулирования другими программами, но и предоставления другим программам возможности манипулировать вашей программой. С помощью Delphi вы можете сделать вашу программу одновременно клиентом автоматизации COM и сервером автоматизации COM, то есть она может манипулировать другими программами и, в свою очередь, [позволять] манипулировать [собой] другим программам. Например, см. Использование COM Automation для работы с OpenOffice и Microsoft Office.

Альтернатива [COM для] macOS

К сожалению, COM Automation недоступна в macOS и Linux. Однако вы можете имитировать некоторые функции COM Automation в macOS, используя AppleScript.

AppleScript в некотором роде похож на COM Automation. Например, вы можете писать скрипты, которые манипулируют другими программами. Вот очень простой пример AppleScript, который запускает NeoOffice (версия OpenOffice.org для Mac):

  tell application "NeoOffice"
    launch
  end tell

Приложение, разработанное для работы с AppleScript, предоставляет «словарь» классов и команд, которые можно использовать с приложением, аналогично классам сервера Windows Automation. Однако даже такие приложения, как NeoOffice, которые не предоставляют словарь, все равно будут реагировать на команды "launch" (запуск), "activate" (активация) и "quit" (выход). AppleScript может быть запущен из редактора сценариев macOS или Finder или даже преобразован в приложение, которое вы можете поместить на док-станцию, как любое другое приложение. Вы также можете запустить AppleScript из своей программы, как в этом примере:

  fpsystem('myscript.applescript');

Предполагается, что скрипт находится в указанном файле. Вы также можете запускать сценарии на лету из своего приложения с помощью команды macOS OsaScript:

  fpsystem('osascript -e '#39'tell application "NeoOffice"'#39 +
        ' -e '#39'launch'#39' -e '#39'end tell'#39);
        {Note use of #39 to single-quote the parameters}

Однако эти примеры являются эквивалентом следующей команды Open:

  fpsystem('open -a NeoOffice');

Аналогично, в macOS вы можете эмулировать команды оболочки Windows для запуска веб-браузера и запуска почтового клиента с:

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

и

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

что предполагает, [что это] достаточно безопасно, что в системе macOS будут установлены приложения Safari и Mail. Конечно, вы никогда не должны делать подобные предположения, и для двух предыдущих примеров вы можете просто положиться на macOS, чтобы поступить правильно и выбрать веб-браузер пользователя по умолчанию и почтовый клиент, если вы вместо этого используете эти варианты:

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

и

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

Не забудьте включить модуль Unix в вашу секцию uses, если вы используете fpsystem или shell (взаимозаменяемые).

Настоящая сила AppleScript заключается в удаленном управлении программами для создания и открытия документов и автоматизации других действий. Сколько вы можете сделать с [этой] программой, зависит от того, насколько обширным является ее словарь AppleScript (если он есть). Например, программы Microsoft Office X не очень пригодны для использования с AppleScript, тогда как более новые программы Office 2004 полностью переписали словари AppleScript, которые во многих отношениях сравниваются с тем, что доступно через серверы Windows Office Automation.

Альтернативы для Linux

В то время как оболочка 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 (я думаю).

Как и в Windows, многие программы для macOS и Linux состоят из нескольких библиотечных файлов (расширения .dylib и .so). Иногда эти библиотеки разработаны так, что вы также можете использовать их в программах, которые вы пишете. Хотя это может быть способом добавления некоторых функций внешней программы к вашей программе, на самом деле это не то же самое, что запуск самой внешней программы и управление ею. Вместо этого ваша программа просто ссылается на библиотеку внешней программы и использует ее подобно тому, как она использует любую библиотеку программирования.

Альтернативы для функций Windows API

Многие программы Windows широко используют Windows API. В кроссплатформенных приложениях функции Win API в модуле Windows не должны использоваться или должны быть заключены в условную компиляцию (например, {$IFDEF MSWINDOWS}).

К счастью, многие из часто используемых функций Windows API реализованы многоплатформенным способом в модуле lclintf. Это может быть решением для программ, которые в значительной степени зависят от Windows API, хотя лучшее решение - заменить эти вызовы настоящими кроссплатформенными компонентами из LCL. Например, вы можете заменить вызовы функций рисования GDI вызовами методов объекта TCanvas.

Коды клавиш

К счастью, обнаружение кодов клавиш (например, по событиям KeyUp) является переносимым: см. LCL Key Handling.

Установка вашего приложения

См. Развертывание вашего приложения.

Смотри также