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

Автор : Пётр Высочанский

# Вернуться на главную  

В этой статье будет рассмотрено создание программы, работающей в 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

  ; Открытие главного окна
#flag =  #PB_Window_MaximizeGadget|#PB_Window_SizeGadget|#PB_Window_Invisible
OpenWindow(1,Window_X,Window_Y,Window_Width,Window_Height,"Текстовый редактор",#flag)
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 можно здесь

#
 
Материал взят с сайта : http://pure-basic.narod.ru/docs/MultiOS.html

Вернуться на главную

#










Используются технологии uCoz