Разработка кроссплатформенного приложения


В этой статье будет рассмотрено создание программы, работающей в Windows и Linux.
Скорее всего она будет работать на платформах MasOS X и AmigaOS, но проверить это не получилось из-за отсутствия этих операционнок в моём компе.
Не нужно думать что один и тот же скомпилированный исполняемый файл получится запустить на разных платформах - это фантастика!
Кроссплатформенность существует только на уровне исходного текста программы и для создания исполняемого файла для требуемой платформы, нужно воспользоваться компилятором для этой платформы. Демонстрационные версии компиляторов для разных платформ, можно скачать здесь http://www.purebasic.com/download.php
Собственно кроссплатформенность достигается (точнее существует без изрядной правки исходника) благодаря функциям среды, описание которых можно найти в справке, поставляемой с дистрибутивом PureBasic или в он-лайн справке http://www.purebasic.com/documentation/index.html
Примерно половина этих функций - немного облагороженные API функции соответствующей платформы. Но фишка в том, что эти функции среды, компиляторы для разных платформ, преобразуют в различный код. Вот и получается, что один и тот же исходник можно скомпилировать под разные платформы.
Но бывают случаи, когда для каждой платформы нужны выполнить свой код.
Есть два варианта, использовать несколько исходных текстов для разных платформ, что не всегда удобно, или использовать условную компиляцию.
В последнем случае используются операторы CompilerIf, CompilerElse и CompilerEndIf
Код:
 CompilerIf #PB_Compiler_OS = #PB_OS_Windows
здесь должен быть код специально для Windows
CompilerElse
здесь должен быть код для других платформ
CompilerEndIf
Эти операторы действуют только на этапе компиляции, добавляя в исполняемый файл только требуемый код.

Есть ещё операторы CompilerSelect, CompilerCase, CompilerDefault, CompilerEndSelect.

Код:
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Windows
здесь должен быть код спецаиально для Windows
CompilerCase #PB_OS_Linux
здесь должен быть код спецаиально для Linux
CompilerCase #PB_OS_MacOS
здесь должен быть код спецаиально для MacOS
CompilerDefault
здесь должен быть код для остальных платформ
CompilerEndSelect

----------------------------------------------------------------------------

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

Исходный текст редактора
Код:
   ; Эта процедура открывает файл, указанный в переменной File и загружает данные из него в редактор.
Procedure LoadFile(File.s)
If ReadFile(0, File) ; Открытие файла.
FileSize=Lof(0) ; Определение размера файла в байтах.
FormatFile=ReadStringFormat(0) ; Определение кодировки файла (Ascii, UTF8 или Unicode).
*mem=AllocateMemory(FileSize+1) ; Выделение памяти на 1 байт больше размера файла.
ReadData(0, *mem, FileSize) ; Копирование данных из файла в память.
SetGadgetText(1, PeekS(*mem,FileSize, FormatFile)) ; Преобразование кодировки и загрузка текста в редактор.
FreeMemory(*mem) ; Освобождение памяти.
CloseFile(0) ; Закрытие файла.
StatusBarText(1,0,File) ; Отображение пути к файлу в строке состояния.
Else
MessageRequester("Ошибка", "Не удалось открыть файл")
EndIf
EndProcedure

; Эта процедура сохраняет данные из редактора в файл.
Procedure SaveFile()
; Создание стандартного окна сохранения файла.
File.s=SaveFileRequester("Сохраняем файл","","Текстовые файлы (*.txt)|*.txt|Все файлы (*.*)|*.*",0)
If File<>""
If GetExtensionPart(File)="" And SelectedFilePattern()=0 ; Если не заданно расширение файла
File=File+".txt" ; добавляем его
EndIf
If CreateFile(0,File) ; Создание файла на диске
Text.s=GetGadgetText(1) ; Копирование текста из редактора в строковую переменную Text
CompilerIf #PB_Compiler_OS = #PB_OS_Linux ; Это код для операционки Linux
WriteStringFormat(0, #PB_Unicode) ; Запись в файл специальной метки, сообщающей что кодировка Unicode
FormatFile=#PB_Unicode
CompilerElse ; Это код для операционок Windows, MacOS и AmigaOS
FormatFile=#PB_Ascii
CompilerEndIf
WriteString(0, Text, FormatFile) ; Запись текста в файл в требуемой кодировке
CloseFile(0) ; Закрытие файла.
Else
MessageRequester("Ошибка", "Не удалось сохранить файл")
EndIf
EndIf
EndProcedure

ProgPath.s=GetPathPart(ProgramFilename()) ; Получаем путь к исполняемому файлу

Gosub LoadSetting ; Вызов подпрограммы, читающей настройки программы из файла SettingEdit.ini

If Window_X<2 : Window_X=100 : EndIf
If Window_Y<2 : Window_Y=100 : EndIf

; Открытие главного окна
OpenWindow(1,Window_X,Window_Y,Window_Width,Window_Height,"Текстовый редактор",#PB_Window_MinimizeGadget|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget|#PB_Window_Invisible)
SmartWindowRefresh(1,1) ; Активация функции, уменьшающей мерцания окна при изменении его размеров

If CreateMenu(0,WindowID(1)) ; Создание главного меню
MenuTitle("Файл")
MenuItem(1,"Открыть"+Chr(9)+"Ctrl+O")
MenuItem(2,"Сохранить"+Chr(9)+"Ctrl+S")
MenuBar()
MenuItem(3,"Выход")
MenuTitle("Формат")
MenuItem(4,"Шрифт")
MenuHeight=MenuHeight() ; Высота меню
EndIf

If CreateToolBar(1,WindowID(1)) ; Создание панели инструментов
ToolBarStandardButton(1, #PB_ToolBarIcon_Open)
ToolBarToolTip(1,1,"Открыть файл")
ToolBarStandardButton(2, #PB_ToolBarIcon_Save)
ToolBarToolTip(1,2,"Сохранить файл")
ToolBarHeight=ToolBarHeight(1) ; Высота панели инструментов
EndIf

; Регистрация "горячих клавиш", дублирующих меню
AddKeyboardShortcut(1, #PB_Shortcut_Control | #PB_Shortcut_O, 1)
AddKeyboardShortcut(1, #PB_Shortcut_Control | #PB_Shortcut_S, 2)


If CreateStatusBar(1,WindowID(1)) ; Создание строки состояния
AddStatusBarField(#PB_Ignore) ; Создание одного раздела на всю строку состояния
StatusBarHeight = StatusBarHeight(1) ; Высота строки состояния
EndIf

CompilerIf #PB_Compiler_OS = #PB_OS_Linux ; Код для Linux
EditorX=2 ; Верхняя позиция редактора в окне
CompilerElse
EditorX=ToolBarHeight+2 ; Верхней позиция редактора смещена вниз на высоту панели инструментов
CompilerEndIf
; Собственно редактор текста
EditorGadget(1,1,EditorX, Window_Width-2, Window_Height-StatusBarHeight-MenuHeight-ToolBarHeight-4)
SetGadgetColor(1,#PB_Gadget_FrontColor, $950121) ; Цвет текста
SetGadgetColor(1,#PB_Gadget_BackColor, RGB(237, 255, 246)) ; Фон

LoadFont(1, FontName.s, FontSize, FontStyle) ; Шрифт для редактора
SetGadgetFont(1,FontID(1))

File.s=ProgramParameter() ; Командная строка переданная программе при старте
If File<>"" And FileSize(File)>=0 ; Передан путь к файлу
LoadFile(File) ; Загружаем файл
EndIf

; Активация функций, позволяющих открывать файл просто перетащив его в окно программы
EnableWindowDrop(1, #PB_Drop_Files, #PB_Drag_Link)
EnableGadgetDrop(1, #PB_Drop_Files, #PB_Drag_Link)

HideWindow(1,0) ; Отображение окна

Repeat ; Главный цикл Repeat - Until

Event=WaitWindowEvent() ; Получаем идентификатор события в программе

If Event=#PB_Event_SizeWindow ; Размер окна изменился
; Изменение размеров редактора
ResizeGadget(1, #PB_Ignore, #PB_Ignore, WindowWidth(1)-2, WindowHeight(1)-StatusBarHeight-MenuHeight-ToolBarHeight-2)

ElseIf Event=#PB_Event_WindowDrop Or Event=#PB_Event_GadgetDrop ; На окно перетащали файл
If EventDropAction()=#PB_Drag_Link
File=EventDropFiles() ; Получаем путь к файлу
If FileSize(File)>=0 ; Файл существует
LoadFile(File) ; Загружаем файл
EndIf
EndIf

ElseIf Event=#PB_Event_Menu ; Событие в меню
Menu=EventMenu() ; Определение выбранного пункта меню
Select Menu
Case 1 ; Пункт "Открыть"
File.s=OpenFileRequester("Открываем файл","","Текстовые файлы (*.txt)|*.txt|Все файлы (*.*)|*.*",0)
If File<>""
LoadFile(File)
EndIf

Case 2 ; Пункт "Сохранить"
SaveFile()

Case 3 ; Пункт "Выход"
Break

Case 4 ; Пункт "Шрифт"
If FontRequester(FontName.s, FontSize,0,0, FontStyle)
FontName=SelectedFontName()
FontSize=SelectedFontSize()
FontStyle=SelectedFontStyle()
LoadFont(1, FontName, FontSize, FontStyle)
SetGadgetFont(1,FontID(1))
EndIf

EndSelect
EndIf

Until Event=#PB_Event_CloseWindow ; Прерывание главного цикла при закрытии окна
Gosub SaveSetting ; Вызов подпрограммы, сохраняющей настройки программы ф файле SettingEdit.ini
End


LoadSetting: ; Подпрограмма читающая из файла SettingEdit.ini настройки программы
OpenPreferences(ProgPath+"SettingEdit.ini")
PreferenceGroup("Window")
Window_X=ReadPreferenceLong("Window_X", 200)
Window_Y=ReadPreferenceLong("Window_Y", 200)
Window_Width=ReadPreferenceLong("Window_Width", 500)
Window_Height=ReadPreferenceLong("Window_Height", 400)
PreferenceGroup("Editor")
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
FontName.s=ReadPreferenceString("FontName","Lucida Console")
CompilerElse
FontName.s=ReadPreferenceString("FontName","Liberation Mono")
CompilerEndIf
FontSize=ReadPreferenceLong("FontSize", 10)
FontStyle=ReadPreferenceLong("FontStyle", 0)
ClosePreferences()
Return


SaveSetting: ; Подпрограмма сохраняющая в файле SettingEdit.ini настройки программы
If CreatePreferences(ProgPath+"SettingEdit.ini")
PreferenceGroup("Window")
WritePreferenceLong("Window_X", WindowX(1))
WritePreferenceLong("Window_Y", WindowY(1))
WritePreferenceLong("Window_Width", WindowWidth(1))
WritePreferenceLong("Window_Height", WindowHeight(1))
PreferenceGroup("Editor")
WritePreferenceString("FontName", FontName)
WritePreferenceLong("FontSize", FontSize)
WritePreferenceLong("FontStyle", FontStyle)
ClosePreferences()
EndIf
Return

Значит, запускаем PureBasic. При запуске будет создан новый проект, который надо сохранить на диске под любым именем. Далее нужно немного изменить свойства проекта. Для этого в меню Компилятор выбираем пункт Настройки компилятора и в открывшемся окне отмечаем пункт Создать unicode приложение и в выпадающем списке Кодировка исходного файла выбираем пункт UTF-8.
Это необходимо для поддержки кириллицы в Linux, а для Windows это не имеет существенного значения, разве что программа перестанет запускаться в Win9x.


Свойства проекта


После этого можно скопировать исходный текст в редактор кода PureBasic и произвести компиляцию.


Теперь рассмотрим как устроена программа и как она работает.
В начале программы есть две процедуры с именами LoadFile и SaveFile. Несмотря на то, что они находятся в начале программы, их код будет выполнен лишь при непосредственном вызове процедур!

Процедура LoadFile открывает файл и загружает его в редактор. Путь к файлу передаются через аргумент процедуры - строковую переменную File.
Функция ReadFile пытается открыть файл. В случае успешного открытия файла, функция Lof возвращает размер файла в байтах, а функция ReadStringFormat кодировку файла. Программа сама распознаёт и поддерживает кодировки Ascii, UTF8 и Unicode.
Далее функция AllocateMemory выделяет участок памяти, размером, но 1 байт большим, размера файла.
Затем с помощью функции ReadData копируется содержимое файла в эту память.
Далее функция PeekS считывает из памяти текст в требуемой кодировке (её ранее получили с помощью ReadStringFormat) и передаёт текст функции SetGadgetText которая загружает этот текст в редактор. После этого память освобождается, файл закрывается и работа процедуры завершается.

В процедуре SaveFile производится сохранение текста файле. В первую очередь с помощью функции SaveFileRequester создаётся стандартное окно сохранения файла (правда этот стандарт меняется в каждой версии операционки, а про разные платформы я вообще молчу). Если место сохранения файла выбрано, то в строковой переменной File будет полный путь к файлу, включая его имя и расширение. Следующие несколько строк проверяют есть ли у выбранного файла расширение, если нет, то к имени файла добавляется расширение txt. После этого функция CreateFile пытается создать пустой файл. В случае успеха, с помощью функции GetGadgetText считывается весь текст из редактора и помещается в строковую переменную Text. Далее следуют операторы условной компиляции, с помощью которых задаётся кодировка сохраняемого файла. Для Linux будет кодировка Unicode, а для остальных платформ, в том числе и Windows будет кодировка Ascii. Функция WriteString сохраняет текст в файле как одну большую строку в заданной кодировке. Далее файл закрывается и после этого работа процедуры завершается.

Далее находится код, который начнёт исполняться сразу после запуска программы.
В первую очередь с помощью строки ProgPath.s=GetPathPart(ProgramFilename()) определяется путь к запущенному исполняемому файлу. Этот путь нужен для работы с файлом настроек SettingEdit.ini, который находится в одной папке с исполняемым файлом.
Далее с помощью оператора Gosub вызывается подпрограмма, начинающаяся с метки LoadSetting, которая открывает файл с настройками и загружает их.
Далее создаётся невидимое окно (за невидимость отвечает флаг #PB_Window_Invisible в функции OpenWindow).
Затем создаётся меню, панель инструментов, регистрируются "горячие клавиши", создаётся строка состояния.
После этого создаётся RTF редактор при помощи функции EditorGadget. Функция SetGadgetColor задаёт цвет текста и фона редактора.
Далее код
Код:
    File.s=ProgramParameter() ; Командная строка переданная программе при старте
If File<>"" And FileSize(File)>=0 ; Передан путь к файлу
LoadFile(File) ; Загружаем файл
EndIf
проверяет наличие пути к файлу в командной строке. Если есть путь к файлу, то вызывается процедура LoadFile, загружающая файл.

Код
Код:
    EnableWindowDrop(1, #PB_Drop_Files, #PB_Drag_Link)
EnableGadgetDrop(1, #PB_Drop_Files, #PB_Drag_Link)
активирует опцию открытия файла простым перетаскиванием его в окно программы.
Функция HideWindow отображает окно.

Далее находится главный цикл программы созданный операторами Repeat и Until, код которого будет выполнятся большую часть времени работы программы. Выполнение кода этого цикла прервётся при событии закрытия окна.
В этом цикле происходит обработка событий программы. Функция WaitWindowEvent возвращает идентификатор события, который помещается в переменную Event. Далее с помощью операторов If, ElseIf и EndIf анализируются идентификаторы событий и выполняются требуемые действия.
Если изменился размер окна, то появится событие #PB_Event_SizeWindow что приведёт к выполнению функции ResizeGadget, изменяющей размеры редактора.
Событие #PB_Event_WindowDrop или #PB_Event_GadgetDrop произойдёт если перетащить файл на окно программы и "бросить" его там. Эти события приведут к выполнению этого кода
Код:
     If EventDropAction()=#PB_Drag_Link
File=EventDropFiles() ; Получаем путь к файлу
If FileSize(File)>=0 ; Файл существует
LoadFile(File) ; Загружаем файл
EndIf
EndIf
При клике по пункту меню, произойдёт событие #PB_Event_Menu
При закрытии окна произойдёт событие #PB_Event_CloseWindow и условие в операторе Until будет выполнено, а это значит что главный цикл программы будет прерван. Вызов подпрограммы SaveSetting, сохранит текущие настройки программы в файле SettingEdit.ini, а затем, оператор End завершит работу программы. При этом освобождаются все ресурсы программы, в т. ч. закрывается окно, так что нет необходимости программно его закрывать.


Скрин текстового редактора для Windows
Скриншот


Скрин редактора для Linux
Скриншот

Скачать исходный текст редактора и скомпилированные исполняемые файлы для Windows и Linux можно здесь



Учебник                Главная

Сайт создан в системе uCoz