О.
В.
Бартеньев.
Visual
⎯1⎯
Fortran:
новые
возможности
О. В. Бартеньев. Visual Fortran: новые возможности
Посо...
213 downloads
1443 Views
3MB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
О.
В.
Бартеньев.
Visual
⎯1⎯
Fortran:
новые
возможности
О. В. Бартеньев. Visual Fortran: новые возможности
Пособие содержит обширный материал по специальным, расширяющим стандарт Фортрана возможностям Digital Visual Fortran, который, как известно, использует в том числе и все расширения Microsoft Fortran PowerStation 4.0. При изложении материала предполагалось, что, во-первых, читатель знаком с техникой программирования на Фортране и, во-вторых, имеет возможность работать с последними версиями Digital Visual Fortran или Microsoft Fortran PowerStation 4.0 для Интел-совместимых компьютеров. Использование рассмотренных в пособии методов и свойств Фортрана позволит читателю создавать быстро работающие, при необходимости многоязычные, приложения, имеющие удобный интерфейс и наглядно представляющие результаты вычислений. Предназначено для научно-технических работников, преподавателей, студентов и аспирантов вузов.
⎯2⎯
Предисловие В настоящее время на российском книжном рынке литература по Фортрану представлена достаточно широко [1, 2, 6 и 9]. Однако эти издания преимущественно отражают свойства Фортрана, определяемые стандартом 90-го года [1, 6 и 9], или посвящены раскрытию приемов программирования на современном Фортране [1 и 2]. Для создания многих приложений этого явно недостаточно: стандартом Фортрана не предусмотрены средства графического вывода, построения диалогов, создания многониточных программ, разноязычных приложений и др. Данная книга восполняет существующий пробел. Воспользовавшись приведенным в ней материалом, программист сможет организовать дружественный интерфейс (в виде диалоговых окон) между пользователем и выполняющими вычисления процедурами. Такие окна содержат управляющие элементы, поля для ввода и вывода данных. Окна Фортрана не являются окнами WIN32 API (хотя последние также могут быть в нем реализованы). Однако поводов для расстройства нет: диалоги Фортрана обеспечивают полноценный интерфейс для большинства его приложений. Одновременно с диалогами в проектах QuickWin поддерживается многооконный графический вывод, управляемый из программы, из диалогов, из сопровождающего графические окна меню или процедурами, запускаемыми при нажатии на заданные клавиши клавиатуры или кнопки мыши. Имеющиеся в Фортране графические процедуры позволяют отображать как векторные, так и растровые графические данные (включая в том числе и графический текст). Полагаю, что специалистам будет интересна глава, посвященная многониточному программированию, которое необходимо для организации квазипараллельного доступа к последовательным устройствам, например экрану или жесткому диску, и для моделирования нескольких одновременно протекающих процессов. В Фортране многониточное программирование реализовано, так же как и в других языках, например СИ++, процедурами WIN32 API. Часто незначительные изменения в программе могут существенно повысить ее быстродействие. Вопрос в том. какие изменения должны быть выполнены. Ответить на него помогут вам приведенные в главе "Повышение быстродействия программ" сведения. ⎯3⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Материал главы "Компилирование и построение программ" поможет вам научиться создавать приложения из командной строки. Возможно, эта идея не покажется вам привлекательной, однако приобретенные в результате чтения главы знания небесполезны и могут быть использованы при работе с проектом в среде Visual Studio как при его отладке, так и на этапе создания рабочей версии, которая должна быть компактной и быстрой. Есть ситуации, когда надо создать проект из процедур, написанных на Фортране, СИ и Ассемблере. Выход здесь прост, поскольку объектные коды, получаемые в этих языках, совместимы (если речь идет о продуктах Digital и Microsoft); дело за тем, чтобы договориться о правилах вызова разноязычных процедур и обмена данными. Такие договоренности есть, и о них рассказывается в главе "Программирование на нескольких языках". Завершают пособие 4 приложения. Несколько слов о двух последних. Известно, что DOS и Windows кодировки русских букв различаются. Это говорит о необходимости преобразования русских текстов при их передаче из Windows в DOS и обратно, например оператором PRINT или подпрограммой OUTTEXT, выполняющей вывод неграфического текста в приложениях с графикой. Процедуры таких преобразований приведены в прил. 3. Особо остановлюсь на последнем приложении. Оно выпадает из общей канвы книги и касается не специальных, а стандартных средств Фортрана: в нем изложены новые элементы языка, добавленные в него стандартом Фортран 95. Однако представляется, что такой материал необходим, поскольку в отечественных книжных магазинах до сих пор не появилось изданий, отражающих эту тему. Фортран, как и другие языки, встроен в современные операционные системы и имеет доступ ко всем их ресурсам. Это, в частности, видно из глав, посвященных QuickWin и многониточному программированию. Однако спектр этих средств существенно шире. Поэтому издательство “Диалог-МИФИ” планирует продолжить выпуск книг по Фортрану. В ближайшее время выйдет пособие по созданию в Фортране приложений с графикой OpenGL, а вслед - книга по разработке WIN32 API Фортранприложений.
⎯4⎯
1. Использование диалогов 1.1. Постановка задачи Рассмотрим программу, выполняющую табуляцию функции y = = xsinx на отрезке [a, b] с шагом dx. Исполняемый файл создадим как проект QuickWin. Напомним, что такой проект создается в среде MS Developer Visual Studio (VS) Digital Visual Fortran (DVF) 6.0 в результате выполнения цепочки: File - New - Projects - Fortran Standard Graphics or QuickWin Application - задать имя проекта (Project name) и папку его размещения (Location) - выбрать QuickWin (multiple windows) - Finish. program txy ! Программа табуляции функции y = x*sin(x) real(4) :: a, b, dx, x, y ! Объявление имен и типов переменных real(4), parameter :: dxmin = 0.05 ! Минимально допустимый шаг изменения x print *, 'Задайте границы отрезка a и b и шаг вычислений dx' print *, 'Левая граница: ' ! Выводим подсказку для пользователя read *, a ! Вводим с клавиатуры значение a print *, 'Правая граница: ' ! и нажимаем на Enter read *, b ! Так же вводятся и другие данные print *, 'Шаг вычислений: ' read *, dx if(dx < dxmin) stop 'Ошибка при задании шага dx' if(a > b) stop 'Ошибка при задании границ отрезка a и b (a > b)' print *, 'Зависимость y = x*sin(x): ' x=a do while(x <= b) y = x*sin(x) print *, 'x = ', x, ' y = ', y ! Вывод результата x = x + dx end do end program txy ! Завершаем программу txy
Ввод данных в такой программе, во-первых, не нагляден, во-вторых, русский текст в DOS-окне претерпит искажения и, в-третьих, отображаемые в окне данные после перехода на следующую строку окна не могут быть отредактированы. Все эти недостатки можно устранить, создав диалог ввода и контроля данных.
⎯5⎯
О. В. Бартеньев. Visual Fortran: новые возможности
1.2. Построение диалогового окна Диалог, после того как ясен его состав и геометрия расположения полей с данными и текстовых полей, создается в 3 этапа: 1) построение диалогового окна; 2) включение диалога в проект; 3) создание процедур инициализации и обработки полей диалога.
1.2.1. Проект для диалога Реализуем для рассмотренной выше задачи приведенное на рис. 1.1 диалоговое окно.
Рис. 1.1. Проект диалогового окна
В диалоговом окне помимо полей ввода данных предусмотрено поле error для вывода сообщений о возможных ошибках, возникающих, например, когда dx < dxmin и a > b. Построение такого окна выполним в среде VS. Загрузим заготовку для диалога: Insert - Resource - выбрать Dialog для Resource type - New (или OK). В результате появится заготовка для окна с двумя кнопками - OK и Cancel.
1.2.2. Задание параметров диалога Настроим прежде параметры всего окна, щелкнув для этого 2 раза мышью по свободной поверхности окна. В появившемся окне Dialog Properties на вкладке General зададим: 1) в поле ID имя диалога - idd_txy, по которому он будет идентифицироваться в программе; 2) в поле Caption название диалога - “Исходные данные в задаче табуляции функции”, которое отображается на верхней полосе диалога; ⎯6⎯
1. Использование диалогов
3) установим, нажав на кнопку Font, шрифт, например MS Sans Serif, и его размер, например 10; 4) в полях XPos и YPos координаты верхнего левого угла диалога, которые он имеет в дочернем окне QuickWin, например 20 и 10 (координаты задаются в текстовых столбцах и рядах). Выберем затем вкладку Styles и уберем галочку с пункта System menu. Остальные поля этой и других вкладок оставим без изменений (рис. 1.2).
Рис. 1.2. Параметры диалогового окна
Закроем окно Dialog Properties, нажав, например, на клавишу Enter; увеличим привычным образом размеры диалогового окна; нажмем Ctrl+T и просмотрим получившееся окно. Нажмем Esc, с тем чтобы затем продолжить построение.
1.2.3. Задание и обработка статического текста Используя меню с управляющими элементами (рис. 1.3), зададим текстовые поля и поля для ввода и редактирования данных.
Рис. 1.3. Управляющие элементы
Задание текстового поля (Static text) выполняется после выбора управляющего элемента Aa и последующей прорисовки прямоугольника для предполагаемого текста в свободной области диалога. Прорисовка выполняется мышью при нажатой ее левой клавише. (Можно также после выбора элемента управления просто “ударить” мышью по предполагаемому его месту размещения в диалоге.) Выделим получившийся прямоугольник, ⎯7⎯
О. В. Бартеньев. Visual Fortran: новые возможности
ударив по нему мышью, и изменим его положение и размеры, пользуясь мышью или клавиатурой. Так, увеличить размер по оси x можно, нажимая Shift+→. “Ударим” теперь по тексту 2 раза мышью и зададим в окне Text Properties во вкладке General свойство Caption текста: “Задайте границы отрезка и шаг вычислений” (см. рис. 1.1). Остальные свойства оставим без изменений. Аналогичным образом зададим все иные тексты, кроме текста error, приведенные на рис. 1.1. Все тексты, поскольку они неизменяемы, будут иметь один и тот же задаваемый по умолчанию идентификатор: IDC_STATIC. Напомним, что для отмены действия можно использовать Ctrl+Z. Задаваемый подобным образом статический текст не может быть изменен в диалоге, но может быть изменен программно, чем мы и воспользуемся для вывода сообщений об ошибках. Создадим внизу диалога статический текст, задав его ID равным error, и внесем в поле Caption надпись error text (см. рис. 1.1). Кроме того, добьемся отображения границ поля, проставив галочки (√) во вкладке Styles рядом с пунктами Sunken и Border. Начальное значение текста с ошибкой зададим в подпрограмме rundial инициализации диалога (приведена в разд. 1.2.8) в виде пустой строки, применив функцию DLGSET: flag = dlgset(dxy, error, '')
! В случае удачи вернет .TRUE.
dxy - определяемая в программе переменная диалога, содержащая параметры окна. error - ID-имя элемента управления, последний параметр задает пустую строку. Функция DLGSET имеет логический тип и вернет .TRUE. в случае успеха. Переменная диалога dxy (имя переменной, разумеется, может быть произвольным) имеет тип dialog. (Тип определен в модуле DFLOGM в случае DVF и в модуле DIALOGM в случае FPS.) Значение переменной диалога определяется при его инициализации, выполняемой функцией DLGINIT. Например, для диалога с ID-именем idd_txy в приводимой ниже программе переменную диалога определим так: type(dialog) dxy flag = dlginit(idd_txy, dxy)
! dxy - переменная диалога idd_txy
Функция DLGINIT имеет логический тип и вернет .TRUE. в случае успеха. ⎯8⎯
1. Использование диалогов
Заметим, что примененная для статического текста функция DLGSET используется и для изменения значений и свойств других элементов управления. Вывод сообщения об ошибке в поле error диалога, когда dx < dxmin, выполним в программе так: flag = dlgset(dxy, error, 'Нужно увеличить шаг!') ! Ошибка: dx > dxmin
1.2.4. Обработка редактируемых полей Выберем контрольный элемент ab| и, применяя ту же технику, что и при создании статического текста, разместим в диалоге 3 редактируемых поля (Edit Box). Используем первое поле для ввода левой границы отрезка изменения аргумента x, второе - для ввода его правой границы, а третье для ввода шага изменения x. Присвоим полям имена: первому - aval, второму - bval и третьему - dxval. Имя поля задается в связанном с ним окне Edit Properties изменения свойств поля, которое появляется, если по полю дважды щелкнуть мышью. По умолчанию ID-имя первого поля имеет значение IDC_EDIT1, которое мы изменим на aval. Аналогичные операции повторим и для двух других редактируемых полей. Перемещение по редактируемым полям и другим элементам управления диалога выполняется при помощи мыши, клавиш Tab, Shift+Tab, Enter (Ввод и перемещение) и других клавиш. Редактируемые поля содержат символьные данные. Для передачи из программы в символьные поля числовых данных выполняются преобразования “число - строка”. При вводе числовых данных из символьного редактируемого поля диалога выполняются преобразования “строка - число”. Изменение значения поля при открытом диалоге можно выполнить из программы, применив функцию DLGSET. Например, после вызова flag = dlgset(dxy, aval, '-1.0') aval
! Изменяем из программы значение поля
в поле aval появится значение -1.0. Передача данных из поля в программу выполняется в результате вызова функции DLGGET. Например, вызов flag = dlgget(dxy, aval, string) aval
! Передаем в строку string значение поля
обеспечит передачу значения поля aval в символьную переменную string. Функция DLGGET имеет логический тип и вернет .TRUE. в случае успеха. ⎯9⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Напомним, что последующее преобразование “строка - число” выполняется так: read(string, *, iostat = ios) a
! Преобразование “строка - число”
Параметр ios равен нулю при успешном преобразовании. Если же строка string не может быть преобразована в вещественное число, например если она состоит из букв, то ios отличен от нуля. Преобразование “число - строка” можно выполнить, например, оператором write(string, *, iostat = ios) a
! Преобразование “число - строка”
С каждым редактируемым полем, например aval (и любым другим изменяемым элементом управления), может быть связана пользовательская подпрограмма обработки введенных данных, например indata, которая должна обладать атрибутом EXTERNAL. Эта связь устанавливается в результате применения функции DLGSETSUB, например: ! Связываем подпрограмму indata с элементом aval flag = dlgsetsub(dxy, aval, indata)
Подпрограмма обработки мгновенно вызывается, если изменено значение редактируемого поля, и должна иметь следующий интерфейс: subroutine indata(dlg, control_name, callbacktype)
То есть подпрограмма обработки всегда в качестве входных данных получает значение переменной диалога, ID-имя элемента управления (одна и та же подпрограмма может быть связана с разными элементами управления) и вид воздействия на элемент управления, например DLG_CLICKED - удар мышью, DLG_CHANGE - изменение значения элемента управления или DLG_DBLCLICK - двойной удар мышью. Элементы, воспринимающие несколько воздействий, могут быть связаны с разными подпрограммами. Свяжем подпрограмму indata с полями диалога aval, bval и dxval и используем ее для ввода данных диалога, выявления возможных ошибок и выдачи соответствующих сообщений в поле error диалога idd_txy.
1.2.5. Кнопки OK и Cancel Кнопки OK и Cancel появляются в каждом вновь создаваемом диалоге, и с ними связаны задаваемые по умолчанию процедуры. Можно изменить как ID-имена этих кнопок, так и их названия. Разумеется, каждая из этих кнопок может быть удалена из диалога. Изменим устанавливаемые по умолчанию ID-имена кнопок, задав для кнопки OK вместо имени IDOK ID⎯ 10 ⎯
1. Использование диалогов
имя yes, а для кнопки Cancel вместо имени IDCANCEL зададим ID-имя no. Все это выполним по той же схеме, что и для рассмотренных выше элементов управления. Свяжем с кнопками OK и Cancel подпрограмму what, которая при нажатии на OK: •
проверяет корректность задания данных и анализирует возможные ошибки;
•
выводит в поле error диалога сообщения об ошибках;
•
присваивает переменной flag значение .TRUE., если введенные данные не содержат ошибок, и закрывает диалог. Подпрограмма what присваивает переменной flag значение .FALSE. и закрывает диалог, если нажата кнопка Cancel. Связь кнопок с подпрограммой выполним так: flag = dlgsetsub(dxy, yes, what) flag = dlgsetsub(dxy, no, what) Cancel
! Свяжем подпрограмму what с кнопкой OK ! Свяжем подпрограмму what с кнопкой
1.2.6. Меню диалога Помимо приведенного на рис. 1.3 меню с управляющими элементами (Controls) при работе с диалогом можно пользоваться меню Dialog (рис. 1.4).
Рис. 1.4. Меню Dialog
Это меню позволяет изменять положение и геометрию выбранных полей диалога, задавать сетку в проектируемом окне, линейку измерения его размеров, просматривать (Ctrl+T) диалог. Если выбрано одно поле, то его можно отцентрировать как по вертикали (Ctrl+F9), так и по горизонтали (Ctrl+Shift+F9). Если выбраны несколько полей, то они могут быть: •
выравнены влево (Align Left - Ctrl+←);
•
выравнены вправо (Align Right - Ctrl+→);
•
выравнены вверх (Align Top - Ctrl+↑);
•
выравнены вправо (Align Bottom - Ctrl+↓); ⎯ 11 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
•
отцентрированы по вертикали и горизонтали;
•
сделаны одной ширины (Make Same Width);
•
сделаны одной высоты (Make Same Height);
•
сделаны одного размера (Make Same Size).
Меню диалога можно убрать, нажав, например, на крест (×) (рис. 1.4). При необходимости его впоследствии можно восстановить, найдя в главном меню пункт Toolbars и поставив галочку (√) после выбора Toolbars рядом с пунктом Dialog. Аналогичным образом можно активизировать и ранее закрытое меню Controls.
1.2.7. Доступ к файлам хранения диалога При сохранении диалога создается файл ресурсов, которому мы присвоим имя xyd.rc и который расположим в папке, содержащей исходный код. При сохранении диалога в папке с файлом xyd.rc появляются или обновляются файлы resource.h и resource.fd, содержащие параметры диалога и других ресурсов. Файл ресурсов может содержать диалоги, меню и другие ресурсы, однако пока в нем будет только один диалог - idd_txy. Файл xyd.rc, чтобы программа получила к нему доступ, необходимо вставить в проект. Выполним в FPS цепочку: Insert - File into Project - задать Resource files для типа файлов - выбрать файл xyd.rc - Add. В DVF 6.0 файл ресурсов вставляется в специальную папку Resource Files окна File View: выбрав эту папку, ударим по правой клавише мыши, выберем Add Files to Folder и добавим xyd.rc в папку. Выполним компиляцию проекта. В среде MS Developer Visual Studio (VS) - в окне работы с проектом (View window) - появится вкладка Resource view, переместившись в которую можно просмотреть и состав файла ресурсов xyd.rc, и отдельно каждый из его компонентов. Существующее диалоговое окно будет загружено в среду VS, если дважды щелкнуть мышью по его ID-имени, отображаемому на вкладке Resource view. Появившееся окно доступно для редактирования. На вкладке File View после компиляции проекта появится подраздел Dependencies, содержащий ссылку на файл resource.fd. Отметим, что пользователь должен воздержаться от ручного редактирования этого файла.
⎯ 12 ⎯
1. Использование диалогов
1.2.8. Работа с диалогом в программе Программу реализуем по следующей схеме: •
выполним инициализацию (подпрограмма rundial);
•
отобразим, обратившись к функции DLGMODAL, диалог на экране;
•
введем данные и выполним их проверку на предмет наличия ошибок (подпрограмма indata);
•
нажмем OK или Cancel;
•
при выборе OK выполним дополнительные проверки введенных данных (см. подпрограмму what) и при отсутствии ошибок, закрыв диалог, перейдем к вычислениям; при наличии ошибок выведем соответствующие сообщения;
диалога
и
его
отдельных
полей
•
при выборе Cancel закроем диалог и завершим программу. Программа устроена так, что при наличии ошибок в данных кнопка OK диалога станет неактивной и, следовательно, не может быть нажата. Факты наличия ошибок фиксируются массивом is_ok(1:5). Следующее ветвление переводит кнопку OK в активное или неактивное состояние: if(all(if_ok)) then ! Если все элементы is_ok равны .TRUE. flag = dlgset(dlg, yes, .true., dlg_enable) ! Ошибок в данных нет - кнопка OK активна else ! Если есть хотя бы одна ошибка flag = dlgset(dlg, yes, .false. , dlg_enable) ! в данных,то кнопка OK недоступна
Отметим, что в приведенных вызовах функции DLGSET индекс DLG_ENABLE может быть опущен. Текст программы: ! Выполним обмен данными между программными компонентами, ! используя use-ассоциирование module abdx ! Обязательная ссылка при работе с диалогами на модуль DIALOGM в случае FPS ! или DFLOGM в случае DVF use dialogm ! Обязательное включение файла с параметрами ресурсов (с параметрами диалога) include 'resource.fd' ! Тип dialog определен в модуле DIALOGM (FPS) или DFLOGM (DVF) type(dialog) :: dxy ! Задаем переменную диалога real(4) :: a, b, dx ! Отрезок и шаг вычислений real(4), parameter :: dxmin = 0.05 ! Минимально допустимый шаг
⎯ 13 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
logical(4) :: flag, if_ok(5) integer(4) :: status, ios character(50) :: string end module abdx
! Промежуточные данные
program txy ! Главная программа use abdx real(4) :: x, y call rundial( ) ! Инициализация диалога idd_txy if(flag) then ! Если в диалоге нажата кнопка OK и нет ошибок x=a ! Подготовка к вычислениям do while(x <= b) ! Цикл табуляции функции y = x*sin(x) ! При записи цикла используем правило рельефа print *, 'x = ', x, ' y = ', y ! Вывод результата x = x + dx end do else ! В диалоге нажата кнопка Cancel print *, 'Task is canceled' end if end program txy subroutine rundial( ) ! Инициализация и запуск диалога idd_txy use abdx external indata, what ! Процедуры, связанные с диалогом flag = dlginit(idd_txy, dxy) ! Инициализация диалога if(.not. flag) stop 'Dialog idd_txy not found' a = 1.0; b = 3.0; dx = 0.2; if_ok = .true. ! Инициализация данных и полей диалога write(string, '(f5.1)') a ! Преобразование “число - строка” flag = dlgset(dxy, aval, adjustl(string)) ! Инициализация поля aval write(string, '(f5.1)') b flag = dlgset(dxy, bval, adjustl(string)) write(string, '(f5.1)') dx flag = dlgset(dxy, dxval, adjustl(string)) flag = dlgset(dxy, error, '') ! Очистим поле error flag = dlgsetsub(dxy, aval, indata) ! Свяжем с полями диалога aval, bval и dxval flag = dlgsetsub(dxy, bval, indata) ! подпрограмму indata flag = dlgsetsub(dxy, dxval, indata) flag = dlgsetsub(dxy, yes, what) ! Свяжем с кнопками OK и Cancel flag = dlgsetsub(dxy, no, what) ! подпрограмму what !! ! Между этими строками будет размещена вставка, приведенная в разд. 1.3.2.5 !! status = dlgmodal(dxy) ! Отобразим диалог на экране
⎯ 14 ⎯
1. Использование диалогов
end subroutine rundial subroutine indata(dlg, c_name, cbtype) ! Передача данных из полей диалога use abdx ! и контроль введенных данных type(dialog) :: dlg ! Подпрограмма indata связана с полями integer(4) :: c_name, cbtype, i ! aval, bval и dxval диалога dxy character(50) :: errtext ! Строка, содержащая текст о найденной ошибке real(4) :: temp ! Оператор i = cbtype применен для подавления предупреждения компилятора ! о наличии неиспользуемой переменной i = cbtype flag = dlgget(dlg, c_name, string) ! Передаем данные из поля диалога в строку string read(string, *, iostat = ios) temp ! Преобразование строка - вещественное число” select case(c_name) case(aval) if(ios == 0) then a = temp; errtext = ''; if_ok(1) = .true. else errtext = 'Ошибка при задании левой границы'; if_ok(1) = .false. end if case(bval) if(ios == 0) then b = temp; errtext = ''; if_ok(2) = .true. else errtext = 'Ошибка при задании правой границы'; if_ok(2) = .false. end if case(dxval) if(ios == 0) then dx = temp; errtext = ''; if_ok(3) = .true. else errtext = 'Ошибка при задании шага'; if_ok(3) = .false. end if end select flag = dlgset(dlg, error, errtext) ! Вывод сообщения об ошибке или пустой строки if(all(if_ok)) then flag = dlgset(dlg, yes, .true.) ! Ошибок в данных нет - кнопка OK активна else ! Если есть хотя бы одна ошибка в данных, flag = dlgset(dlg, yes, .false.) ! то кнопка OK недоступна end if end subroutine indata
⎯ 15 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
subroutine what(dlg, c_name, cbtype) ! Подпрограмма связана с элементами use abdx ! yes и no (кнопки OK и Cancel) диалога dxy type(dialog) :: dlg integer(4) :: c_name, cbtype, i i = cbtype if(c_name == yes) then ! Контроль возможных ошибок задания данных if(dx < dxmin) then flag = dlgset(dlg, error, 'Нужно увеличить шаг!') return end if if(b - a < dx) then flag = dlgset(dlg, error, 'Правая граница должна быть больше левой') return end if flag = .true. ! flag равен .TRUE., если нажата кнопка OK else ! и нет ошибок в данных flag = .false. ! flag равен .FALSE., если нажата кнопка Cancel end if call dlgexit(dlg) ! Закрываем диалог end subroutine what
Замечание. Наименование кнопок OK и Cancel можно изменить на Да и Отменить, выполнив при инициализации в подпрограмме rundial операторы flag = dlgset(dxy, yes, 'Да', dlg_title) flag = dlgset(dxy, no, 'Отменить', dlg_title)
Помимо рассмотренных ранее функций для диалога DLGINIT, DLGSET, DLGGET и DLGSETSUB программа содержит функцию DLGMODAL и подпрограмму DLGEXIT. Функция DLGMODAL типа INTEGER(4) в случае успеха отображает диалог на экране и передает управление диалогу. В случае неудачи функция вернет -1. Функция имеет синтаксис: result = DLGMODAL(dlg) dlg - переменная типа dialog, связанная посредством функции DLGINIT с конкретным диалогом. Подпрограмма DLGEXIT закрывает диалог и имеет синтаксис: CALL DLGEXIT(dlg)
⎯ 16 ⎯
1. Использование диалогов
Закрытому посредством DLGEXIT диалогу можно передать управление, применив функцию DLGMODAL, если, правда, не применена подпрограмма CALL DLGUNINIT(dlg) удаляющая диалог из памяти. После применения этой подпрограммы доступ к диалогу станет возможным, если вновь выполнена его инициализация функцией DLGINIT.
1.3. Усовершенствование программы табуляции функции 1.3.1. Вывод сообщения Заменим сообщение об отмене вычислений print *, 'Task is canceled'
на аналогичное сообщение, но теперь уже выводимое в диалоговом окне (рис. 1.5).
Рис. 1.5. Окно для вывода сообщений
Вставим для этого в проект новый диалог (Insert - Resource - ...) и дадим ему имя idd_say. Удалим из него (вкладка Styles) системное меню (System menu) и полосу для заголовка (Title bar). Полю с текстом дадим ID-имя say, а свойство Caption оставим без изменений. Кнопку Cancel удалим. Свойства кнопки OK оставим без изменений. В программе в модуль abdx добавим имя переменной диалога: type(dialog) :: dre сообщений
! Задаем переменную диалога - окна
Инициализацию диалога выполним в подпрограмме rundial: flag = dlginit(idd_say, dre) if(.not. flag) stop 'Dialog idd_say not found'
Обращение к диалогу выполним в главной программе: if(flag) then ошибок
! Если в диалоге нажата кнопка OK и нет
⎯ 17 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
... ! Здесь следует кусок текста из главной программы else ! В диалоге нажата кнопка Cancel flag = dlgset(dre, say, 'Задача отменена') ! Вывод сообщения в поле say диалога status = dlgmodal(dre) ! Активизируем диалог idd_say end if end program txy ! Завершаем программу txy
Созданный диалог позволяет выводить любые сообщения как на русском, так и на иных языках. Нажатие на OK приведет к закрытию диалога idd_say. Заметим, что имя включенного в проект нового диалога отобразится в окне (View window) на вкладке Resource view. Параметры диалога idd_say, как и параметры диалога idd_txy, будут сохранены в файлах resource.h и resource.fd.
1.3.2. Задание числа итераций Предусмотрим в диалоге idd_txy в качестве альтернативы возможность задания числа итераций n. Добавим для этого в диалог две радиокнопки, дав им ID-имена: первой - use_dx, второй - use_n. (Радиокнопки добавляются после выбора Radio Button (•) в меню Controls.) Эти кнопки должны быть объединены в группу, что достигается добавлением в диалог Group Box, прямоугольник которого охватывает обе кнопки (рис. 1.6).
Рис. 1.6. Изменения, которые внесены в диалог для задания числа итераций
1.3.2.1. Зачем группировать радиокнопки Группировка обеспечивает механизм переключения кнопок: в каждый момент времени активна (включена) только одна кнопка. Дадим группе ID-
⎯ 18 ⎯
1. Использование диалогов
имя rgroup. Заголовок группы и необходимые наименования радиокнопок зададим в программе так: flag = dlgset(dxy, rgroup, 'Задать') ! 'Задать'- заголовок группы ! 'шаг'- наименование первой радиокнопки flag = dlgset(dxy, use_dx, 'шаг', dlg_title) ! 'итерации' - наименование второй радиокнопки flag = dlgset(dxy, use_n, 'итерации', dlg_title)
1.3.2.2. Элементы управления, задающие число итераций Добавим также 2 элемента управления для задания числа итераций: горизонтальную шкалу с указателем (Horizontal Scroll Bar) с ID-именем n_scroll, редактируемое поле (Edit Box) с ID-именем nval. Предусмотрим и статический текст с ID-именем ntext для заголовка. Вывод текста выполним в программе так: flag = dlgset(dxy, ntext, 'Число итераций')
Каждый из элементов (шкала n_scroll и редактируемое поле nval) позволяет задать число итераций. При этом изменение одного из элементов должно привести к изменению другого элемента. В программе при изменении положения указателя на шкале это достигается так (переменная n хранит число итераций): flag = dlgget(dlg, n_scroll, n, dlg_position) ! Читаем новое значение n на шкале write(string, *) n ! Преобразование “число - строка” ! Изменяем значение редактируемого поля nval flag = dlgset(dlg, nval, adjustl(string))
Аналогичным образом осуществляется взаимодействие "редактируемое поле - шкала" (см. нижеприводимую подпрограмму indata2). Для шкалы могут быть программно заданы 4 целочисленных значения: •
верхняя граница изменения значения контролируемой шкалой переменной (нижняя граница всегда равна единице), например:
flag = dlgset(dxy, n_scroll, 20, dlg_range) ! Верхняя граница равна 20
•
позиция указателя шкалы, например:
flag = dlgset(dxy, n_scroll, 10, dlg_position)
! Позиция указателя равна 10
или без индекса DLG_POSITION: flag = dlgset(dxy, n_scroll, 10)
•
! Позиция указателя равна 10 (центр шкалы)
величина большого шага - величина, на которую изменяется контролируемая шкалой переменная, если ударить мышью рядом с указателем (по умолчанию это значение равно 10), например: ⎯ 19 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
flag = dlgset(dxy, n_scroll, 5, dlg_bigstep) ! Большой шаг равен 5
•
величина маленького шага - величина, на которую изменяется контролируемая шкалой переменная, если ударить мышью по стрелке шкалы (по умолчанию эта величина равна единице), например:
flag = dlgset(dxy, n_scroll, 2, dlg_smallstep)
! Малый шаг равен 2
Помимо задания значений может быть изменено и состояние шкалы, как, впрочем, и других элементов управления, из активного в неактивное и наоборот, например: flag = dlgset(dlg, n_scroll, .true., dlg_enable) flag = dlgset(dlg, n_scroll, .false., dlg_enable)
! Шкала n_scroll активна ! Шкала n_scroll неактивна
В неактивном состоянии шкала, как и другие элементы управления, недоступна для редактирования в диалоговом окне. 1.3.2.3. Устройство элементов управления Из примеров можно понять устройство элементов управления. Они являются структурами, и для изменения значений отдельных компонентов структуры Фортран использует функцию DLGSET и индексы, определенные в модуле DIALOGM константы, например DLG_RANGE, DLG_POSITION, DLG_BIGSTEP и др. Индексы могут быть применены и с функциями DLGGET и DLGSETSUB. Полный перечень индексов и функций, с которыми они применяются, приведен ниже. 1.3.2.4. Использование радиокнопок Назначение радиокнопок двойственное. Во-первых, при выборе кнопки use_dx (верхней кнопки) - делать активным (доступным) редактируемое поле dxval и пассивными (недоступными) шкалу n_scroll и редактируемое поле nval и, наоборот, при выборе кнопки use_n (нижней кнопки) - делать активными шкалу n_scroll и редактируемое поле nval и пассивным редактируемое поле dxval. Во-вторых, в зависимости от выбранной радиокнопки изменяется значение логической переменной is_n, и если выбран режим задания числа итераций - is_n есть истина, то перед циклом табуляции функции в главной программе будет прежде вычислен шаг dx: if(is_n) dx = (b - a) / float(n) x=a ...
! В диалоге было задано число итераций, ! поэтому прежде нужно вычислить dx
Переменную is_n объявим в модуле abdx: logical(4) :: isn. Изменение состояния элементов (активный - пассивный) при переключении радиокнопок достигается в программе следующими операторами: ⎯ 20 ⎯
1. Использование диалогов
select case(c_name) ... case(use_dx) ! Выбрана радиокнопка use_dx flag = dlgset(dlg, n_scroll, .false., dlg_enable) ! Недоступны элементы n_scroll flag = dlgset(dlg, nval, .false., dlg_enable) ! и nval flag = dlgset(dlg, dxval, .true., dlg_enable) ! Элемент dxval будет активным is_n = .false. case(use_n) ! Выбрана радиокнопка use_dx flag = dlgset(dlg, n_scroll, .true., dlg_enable) ! Активны элементы n_scroll и nval flag = dlgset(dlg, nval, .true., dlg_enable) flag = dlgset(dlg, dxval, .false., dlg_enable) ! Элемент dxval будет недоступным is_n = .true. end select
1.3.2.5. Изменения в тексте программы Дополним программу табуляции функции, приведенную в разд. 1.2.8, внешней подпрограммой indata2, которую свяжем с вновь добавленными элементами диалога: nval, n_scroll, use_dx и use_n. subroutine indata2(dlg, c_name, cbtype) ! Подпрограмма связана с элементами use abdx ! nval, n_scroll, use_dx и use_n диалога dxy type(dialog) :: dlg integer(4) :: c_name, cbtype, i i = cbtype select case(c_name) case(nval) ! Выполнено изменение элемента nval flag = dlgget(dlg, nval, string) ! Передаем значение nval в строку string read(string, *, iostat = ios) n ! Преобразование “строка - число” if(ios /= 0) then ! Если возникла ошибка передачи данных flag = dlgset(dlg, error, 'Ошибка при задании числа итераций') flag = dlgset(dlg, yes, .false.); if_ok(4) = .false. return else ! Ошибки нет. Очищаем поле error диалога flag = dlgset(dlg, error, '') flag = dlgset(dlg, yes, .true.); if_ok(4) = .true. end if if(n <= nmax) then ! Проверка соблюдения ограничений flag = dlgset(dlg, n_scroll, n) flag = dlgset(dlg, error, '') flag = dlgset(dlg, yes, .true.); if_ok(5) = .true. else ! Нарушено ограничение по числу итераций flag = dlgset(dlg, error, 'Число итераций больше допустимого значения') flag = dlgset(dlg, yes, .false.); if_ok(5) = .false. return
⎯ 21 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
end if case(n_scroll) ! Изменено положение указателя на шкале flag = dlgget(dlg, n_scroll, n, dlg_position) ! Читаем новое значение n на шкале write(string, *) n ! Преобразование “число - строка” flag = dlgset(dlg, nval, adjustl(string)) ! Изменяем значение редактируемого поля nval case(use_dx) ! Выбрана радиокнопка use_dx flag = dlgset(dlg, n_scroll, .false., dlg_enable) ! Недоступны элементы n_scroll flag = dlgset(dlg, nval, .false., dlg_enable) ! и nval flag = dlgset(dlg, dxval, .true., dlg_enable) ! Элемент dxval будет активным is_n = .false. case(use_n) ! Выбрана радиокнопка use_dx flag = dlgset(dlg, n_scroll, .true., dlg_enable) ! Активны элементы n_scroll и nval flag = dlgset(dlg, nval, .true., dlg_enable) flag = dlgset(dlg, dxval, .false., dlg_enable) ! Элемент dxval будет недоступным is_n = .true. end select ! Нужно будет вычислить шаг dx end subroutine indata2
Переменную is_n объявим в модуле abdx: integer(4) :: is_n
Объявим подпрограмму indata2 в подпрограмме rundial как внешнюю: external indata2
Кроме того, в подпрограмме rundial разместим операторы, выполняющие инициализацию новых управляющих элементов диалога, и функции, связывающие эти элементы с подпрограммой indata2. n = 10; is_n = .false. ! is_n равен .FALSE., если задается шаг dx flag = dlgset(dxy, n_scroll, nmax, dlg_range) ! Верхняя граница равна 20 ! Позиция указателя равна 10 (центр шкалы) flag = dlgset(dxy, n_scroll, n) flag = dlgset(dxy, n_scroll, 5, dlg_bigstep) ! Большой шаг равен 5 flag = dlgset(dxy, n_scroll, 2, dlg_smallstep) ! Малый шаг равен 2 write(string, *) n flag = dlgset(dlg, nval, adjustl(string)) ! Начальное значение редактируемого поля nval flag = dlgset(dxy, n_scroll, .false., dlg_enable) ! Первоначально шкала n_scroll flag = dlgset(dxy, nval, .false., dlg_enable) ! и поле nval неактивны flag = dlgset(dxy, ntext, 'Число итераций') ! Заголовок для элементов n_scroll и nval flag = dlgsetsub(dxy, n_scroll, indata2) ! Связываем элементы n_scroll и nval flag = dlgsetsub(dxy, nval, indata2) ! с подпрограммой indata 2 flag = dlgset(dxy, rgroup, 'Задать') ! 'Задать' - заголовок группы flag = dlgset(dxy, use_dx, 'шаг', dlg_title) ! 'шаг'- наименование первой радиокнопки
⎯ 22 ⎯
1. Использование диалогов
flag = dlgset(dxy, use_n, 'итерации', dlg_title) ! 'итерации' наименование второй радиокнопки flag = dlgset(dxy, use_dx, .true.) ! Первоначально выбрана кнопка use_dx flag = dlgsetsub(dxy, use_dx, indata2) ! Связываем элементы n_scroll и nval flag = dlgsetsub(dxy, use_n, indata2) ! с подпрограммой indata 2
-
Приведенные операторы размещаются между двумя двойными восклицательными знаками подпрограммы rundial, находящейся в разд. 1.2.8. В главной программе перед циклом DO WHILE добавим одну строку: if(is_n) dx = (b - a) / float(n)
! В диалоге было задано число итераций
1.4. Управляющие элементы диалога •
Управляющие элементы, используемые в диалоговых перечислены в табл. 1.1. Таблица 1.1. Процедуры для диалога Управляющий элемент
окнах,
Описание
Статический текст
Изменяемый из программы текст
Редактируемое поле
Элемент, значение которого можно изменить с клавиатуры
Группа
Элемент, используемый для объединения элементов, например радиокнопок, в группу взаимодействующих элементов
Переключатель
Элемент, имеющий 2 состояния: включен, выключен
Радиокнопка
Элемент, имеющий 2 состояния: включен, выключен. Радиокнопка включается в группу с другими радиокнопками. В этой группе в каждый момент времени может быть включена только одна радиокнопка
Кнопка
Имеет 2 состояния: нажата, отпущена
Список (открытый, с редактируемым полем, падающий)
Элемент, позволяющий отобразить и выбрать (в случае открытого списка) несколько значений
Шкала
Элемент, значение которого изменяется с некоторым шагом
Управляющие элементы обладают рядом свойств, которые могут быть заданы в связанном с управляющим элементом окне Properties. Детальное ⎯ 23 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
описание свойств станет доступным, если, находясь в окне задания свойств элемента, нажать F1. Для программиста также важно помнить, что: •
свойства и значение каждого управляющего элемента могут быть изменены из программы;
•
с управляющим элементом может быть связана пользовательская подпрограмма, которая обрабатывает оказываемые на него воздействия;
•
на управляющие элементы, как правило, можно оказывать несколько воздействий (мышью или с клавиатуры).
1.5. Процедуры для работы с диалогом Прежде чем приступить к подробному описанию других управляющих элементов, приведем в табл. 1.2 перечень процедур, которые применяются при работе с диалогом. В FPS интерфейсы функций размещены в модуле DIALOGM.MOD, а в DVF 6.0 - в модуле DFLOGM.MOD. Таблица 1.2. Процедуры для диалога Процедура
Тип
Назначение
DLGEXIT
Подпрограмма
Закрывает открытый диалог
DLGGET
LOGICAL(4)
Передает из диалога в программу значение компонента управляющего элемента
DLGGETCHAR
”
Передает из диалога в программу значение символьного компонента управляющего элемента
DLGGETINT
”
Передает из диалога в программу значение целого компонента управляющего элемента
DLGGETLOG
”
Передает из диалога в программу значение логического компонента управляющего элемента
DLGINIT
“
Инициализация диалога
DLGMODAL
INTEGER(4)
Отображает диалог на экране и передает управление диалогу
DLGSET
LOGICAL(4)
Устанавливает значение компонента управляющего элемента
⎯ 24 ⎯
1. Использование диалогов
DLGSETCHAR
”
Устанавливает значение символьного компонента управляющего элемента
DLGSETINT
”
Устанавливает значение целого компонента управляющего элемента
DLGLOG
”
Устанавливает значение логического компонента управляющего элемента
DLGSETRETURN
Подпрограмма
Устанавливает значение, возвращаемое функцией DLGMODAL
DLGSETSUB
LOGICAL(4)
Связывает подпрограмму с управляющим элементом
DLGUNINIT
Подпрограмма
Удаляет диалог из памяти
Имя DLGGET является родовым, а имена DLGGETCHAR, DLGGETINT и DLGGETLOG - специфическими, поэтому всегда вместо последних трех имен можно применить родовое имя DLGGET. То же справедливо и для имен DLGSET DLGSETCHAR, DLGSETINT и DLGSETLOG, среди которых родовым является имя DLGSET.
1.6. Управляющие индексы С функциями DLGGET (DLGGETCHAR, DLGGETINT, DLGGETLOG), DLGSET (DLGSETCHAR, DLGSETINT, DLGSETLOG) и DLGSETSUB могут использоваться управляющие индексы. Так, при работе со шкалой могут быть использованы контрольные индексы: DLG_RANGE DLG_POSITION DLG_BIGSTEP DLG_SMALLSTEP DLG_ENABLE, которые позволяют изменять (в случае DLGSET) или читать (в случае DLGGET) значения различных компонентов связанной со шкалой переменной, например: flag = dlgset(dxy, n_scroll, 20, dlg_range) ! Верхняя граница равна 20 flag = dlgset(dxy, n_scroll, 10, dlg_position) ! Позиция указателя равна 10 flag = dlgset(dxy, n_scroll, 5, dlg_bigstep) ! Большой шаг равен 5 flag = dlgset(dxy, n_scroll, 2, dlg_smallstep) ! Малый шаг равен 2 flag = dlgset(dlg, n_scroll, .true., dlg_enable) ! Шкала n_scroll активна flag = dlgset(dlg, n_scroll, .false., dlg_enable) ! Шкала n_scroll неактивна
Функции DLGSET и DLGGET имеют синтаксис: result = DLGSET(dlg, controlid, value [, index]) result = DLGGET(dlg, controlid, value [, index])
⎯ 25 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Индекс нужно задавать, если управляющий элемент имеет несколько компонентов одного типа и имя индекса не является именем, которое задается по умолчанию. Так, в случае шкалы для целочисленного компонента по умолчанию задается индекс DLG_POSITION, и поэтому он всегда может быть опущен. В случае, например, задания состояния "активен - неактивен" редактируемого поля может быть опущен индекс DLG_ENABLE, поскольку связанная с полем переменная имеет только один компонент логического типа. Перечень применяемых с функциями DLGSET и DLGGET индексов приведен в табл. 1.3. В последней графе таблицы указаны и элементы управления, с которыми эти индексы могут быть использованы. В столбце Тип компонентов указан тип, с которым индекс применяется. Таблица 1.3. Индексы, применяемые с функциями DLGSET и DLGGET Индекс
Тип компонента
Интерпретация
Элементы управления Шкала
DLG_BIGSTEP
INTEGER(4)
Большой шаг - величина, на которую изменяется контролируемая шкалой переменная, если ударить мышью рядом с указателем шкалы (по умолчанию это значение равно 10)
DLG_ENABLE
LOGICAL(4)
Элемент управления активен Все элементы (доступен), если value = = .TRUE., и неактивен, если value = .FALSE.
DLG_NUMITEMS
INTEGER(4)
Общее число строк в списке или, если вместо DLG_NUMITEMS задано целое число от 1 до n, номер элемента выбранного элемента списка
Все виды списков
Позиция указателя шкалы
Шкала
DLG_POSITION DLG_RANGE
” INTEGER(4)
Шкала Верхняя граница значения контролируемой шкалой переменной (нижняя граница всегда равна единице)
⎯ 26 ⎯
1. Использование диалогов
”
DLG_SMALLSTEP
Маленький шаг - величина, на которую изменяется контролируемая шкалой переменная, если ударить мышью по стрелке шкалы (по умолчанию эта величина равна единице)
”
Переключатель, радиокнопка
DLG_STATE
LOGICAL(4)
Изменяемое пользователем состояние элемента управления
DLG_STATE
INTEGER(4)
Номер выбранной строки или Список без редактируемостроки, которая будет го поля выбрана в результате применения функции DLGSET
DLG_STATE
CHARACTER(*) Значение элемента
Редактируемое поле, все виды списков
управления DLG_TITLE
DLG_DEFAULT
”
Любой
Текст заголовка, связанного с Текст, группа, элементом управления кнопка переключатель, радиокнопка Имеет тот же эффект, что и при отсутствии индекса. В этом случае используется задаваемый по умолчанию индекс (см. табл. 1.4)
Все элементы
С каждым типом данных параметра value функций DLGSET и DLGGET связан индекс, задаваемый по умолчанию, список которых приведен в табл. 1.4. Если с функцией DLGSET или DLGGET следует применить задаваемый по умолчанию индекс, то он может быть опущен. Таблица 1.4. Задаваемые по умолчанию индексы функций DLGSET и DLGGET Элементы управления
Тип value
Статический текст, группа, кнопка, редактируемое поле, шкала, все виды списков
LOGICAL(4)
⎯ 27 ⎯
Индекс по умолчанию DLG_ENABLE
О. В. Бартеньев. Visual Fortran: новые возможности
Переключатель, радиокнопка Редактируемое поле, все виды списков
”
DLG_STATE
CHARACTER(*)
DLG_STATE
”
DLG_TITLE
Статический текст, группа, кнопка, переключатель, радиокнопка Шкала
INTEGER(4)
Все виды списков
”
DLG_POSITION DLG_NUMITEMS
Индексы также применяются и с функцией DLGSETSUB, имеющей синтаксис: result = DLGSETSUB(dlg, controlid, value [, index]) Параметр index, так же как для функций DLGSET и DLGGET, является необязательным и задается, когда с элементом управления могут быть связаны несколько подпрограмм. Так, с элементом редактируемое поле (Edit box) можно связать подпрограмму, например subchange, которая будет вызываться после того, как значение элемента изменено пользователем и изменения отображены на экране. Также с редактируемым полем можно связать подпрограмму, например subupdate, которая будет вызываться после того, как значение элемента изменено пользователем, но изменения еще не отображены на экране. В первом случае значение параметра index равно DLG_CHANGE, а во втором - DLG_UPDATE. Причем значение DLG_CHANGE в случае редактируемого поля задается по умолчанию и, следовательно, может быть опущено: ! Первые два вызова эквивалентны result = dlgsetsub(dlg, aval, subchange, dlg_change) result = dlgsetsub(dlg, aval, subchange) ! Вызов subupdate будет выполняться до обновления элемента aval на экране result = dlgsetsub(dlg, aval, subupdate, dlg_update)
Применяемые с функцией DLGSETSUB индексы приведены в табл. 1.5. Индексы, задаваемые по умолчанию, приведены в табл. 1.6. Таблица 1.5. Индексы, применяемые с функцией DLGSETSUB Индекс DLG_CHANGE
Интерпретация
Элементы управления
Подпрограмма, связанная с элементом управления, вызывается после того, как значение элемента изменено пользователем и изменения отображены на экране
Редактируемое поле, шкала, список с редактируемы м полем
⎯ 28 ⎯
1. Использование диалогов
DLG_UPDATE
Подпрограмма, связанная с элементом управления, вызывается после того, как значение элемента изменено пользователем и изменения отображены на экране
То же
DLG_CLICKED
Подпрограмма, связанная с элементом управления, вызывается после того, как по элементу ударили мышью
Кнопка, переключатель , радиокнопка
DLG_DBLCLICK
Подпрограмма, связанная с элементом управления, вызывается после того, как по элементу дважды ударили мышью
Все виды списков
DLG_SELCHANGE
Подпрограмма, связанная с элементом управления, вызывается после того, как в списке выбрана другая строка
То же
DLG_DEFAULT
Имеет тот же эффект, что и при отсутствии индекса. В этом случае используется задаваемый по умолчанию индекс
Все элементы
Таблица 1.6. Задаваемые по умолчанию индексы функции DLGSETSUB Элементы управления
Индекс по умолчанию
Редактируемое поле, шкала
DLG_CHANGE
Все виды списков
DLG_SELCHANGE
Кнопка, переключатель, радиокнопка
DLG_CLICKED
1.7. Применение списков Можно задать 3 вида списков с текстовыми данными: •
открытый список (List Box), который далее будем называть просто списком;
•
список с редактируемым полем (Simple List или Drop Down List);
•
список без редактируемого поля (Drop List).
1.7.1. Открытые списки Рассмотрим прежде, как работать с открытыми списками. Разработаем для программы табуляции функции диалоговое окно с ID-именем results, в ⎯ 29 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
котором создадим список с ID-именем list. Отобразим в списке результаты расчета, выводимые прежде оператором print *, 'x = ', x, ' y = ', y
! Вывод результата
В списке предусмотрим расширенный множественный выбор, при котором в списке могут быть выбраны одновременно более одного элемента (каждый элемент списка отображается на отдельной строке), причем выбор может выполняться как при нажатой клавише Shift (в этом случае выбираются подряд идущие элементы), так и при нажатой клавише Ctrl (в этом случае порядок выбора элементов произволен). Задание расширенного множественного выбора выполним так: щелкнем дважды мышью по списку, выберем вкладку Styles, в открывающемся списке Selection выберем пункт Extended. Кроме того, отменим на этой же вкладке режим принудительной сортировки элементов списка (Sort). Добавим также в диалог 4 поля со статическим текстом: первое - для вывода заголовка "Выбрано элементов", второе с ID-именем nsel - для вывода числа выбранных в списке элементов, третье - для вывода заголовка "Средняя величина y" и четвертое - с ID-именем avery - для вывода среднего значения табулируемой функции. Поля nsel и avery, предназначенные для вывода значений, заключим в рамку. Макет диалога, содержащий также и поля со статическим текстом для заголовков, приведем на рис 1.7.
Рис. 1.7. Диалог для отображения и обработки результатов табуляции функции
На рис. 1.8 приведем диалог, в списке которого выбраны 3 элемента, и результаты расчета среднего значения y этих элементов. Расчеты и отображение их результатов выполним в связанной со списком list модульной подпрограмме treatlist. Напомним, что модульные процедуры имеют явный интерфейс и, следовательно, обладают атрибутом EXTERNAL. ⎯ 30 ⎯
1. Использование диалогов
Рис. 1.8. Рабочий вид диалога results
Подпрограмму свяжем со списком так, чтобы она обрабатывала 2 вида воздействий на список: расширенный множественный выбор и двойной удар мышью по любому элементу списка. Связь "подпрограмма - элемент диалога - воздействие" устанавливается в результате вызовов: ! Обработка выбора элемента списка flag = dlgsetsub(dli, list, treatlist, dlg_selchange) ! Обработка двойного щелчка мышью flag = dlgsetsub(dli, list, treatlist, dlg_dblclick)
При обработке первого воздействия вычисляется и отображается среднее значение y для выбранных элементов списка. При втором воздействии на список - двойном ударе мышью - вычисляется и отображается в поле avery среднее значение y для всех элементов списка. Число элементов списка определяется после вызова: flag = dlgget(dli, list, n, dlg_numitems) dli
! n - число элементов в списке list диалога
Инициализацию и вызов диалога выполним во внешней подпрограмме shores, в которой для передачи числа элементов списка list в диалог dli используем вызов: ! Передадим в диалог n - число элементов в списке flag = dlgset(dli, list, n, dlg_numitems)
А формирование отображаемого в диалоге списка выполним в цикле: do i = 1, n ! Цикл формирования списка list из n элементов write(string, '(a, f7.3, a, f8.3)') 'x = ', xres(i), '; y = ', yres(i) flag = dlgset(dli, list, adjustl(string), i) ! Добавляем очередной элемент в список end do
⎯ 31 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Приведем теперь программу, использующую все 3 разработанных диалога: idd_txy, idd_say и results. module abdx use dialogm include 'resource.fd' type(dialog) :: dxy, dre idd_say real(4) :: a, b, dx real(4), parameter :: dxmin = 0.05 logical(4) :: flag, is_n, if_ok(5) integer(4) :: status, ios, n integer(4), parameter :: nmax = 20 real(4), dimension(nmax) :: xres, yres функции character(50) :: string
! Задаем переменные диалогов idd_txy и ! Отрезок и шаг вычислений ! Минимально допустимый шаг ! Промежуточные данные !
Массивы
с
результатами
табуляции
contains subroutine treatlist(dli, c_name, cbtype) type(dialog) :: dli мыши integer(4) :: c_name, cbtype, i real(4) :: average i = c_name; average = 0.0 select case(cbtype) case(dlg_selchange) i=0 do flag = dlgget(dli, list, ival, i + 1) if(ival == 0) exit i=i+1 average = average + yres(ival) end do case(dlg_dblclick) average = sum(yres) flag = dlgget(dli, list, i, dlg_numitems) end select if(i > 0) then average = average / i write(string, *) i flag = dlgset(dli, nsel, adjustl(string)) write(string, '(f8.3)') average flag = dlgset(dli, avery, adjustl(string)) end if
! Подпрограмма вызывается при выборе ! элемента списка и при двойном ударе ! по элементу списка. В первом случае ! вычисляется средняя величина y для ! выбранных элементов, во втором - средняя ! величина y для всех элементов списка. ! Выбран элемент списка
! Двойной удар мыши по элементу списка ! Передадим в программу (в переменную i) ! из диалога число элементов в списке list ! Вычислим среднюю величину y ! и отобразим результат в диалоге ! Преобразование “число - строка” ! Передаем в диалог число выбранных ! элементов ! Передаем в диалог результаты вычислений
⎯ 32 ⎯
1. Использование диалогов
end subroutine treatlist end module abdx program txy ! Главная программа use abdx real(4) :: x, y call rundial( ) ! Инициализация диалога idd_txy if(flag) then ! Если в диалоге нажата кнопка OK и нет ошибок if(is_n) dx = (b - a) / float(n) ! В диалоге было задано число итераций, x=a ! поэтому прежде нужно вычислить dx n=0 ! Вычислим число итераций do while(x <= b) ! Цикл табуляции функции n=n+1 y = x*sin(x) ! При записи цикла используем правило рельефа xres(n) = x ! Запомним результаты расчета для yres(n) = y ! последующего вывода в списке диалога results x = x + dx end do call shores( ) ! Вывод и обработка результатов расчета else ! В диалоге нажата кнопка Cancel flag = dlgset(dre, say, 'Задача отменена') ! Вывод сообщения в поле say диалога status = dlgmodal(dre) ! idd_say. Активизируем диалог idd_say end if end program txy subroutine shores( ) ! Инициализация диалога results; вывод use abdx ! и обработка результатов табуляции функции type(dialog) :: dli integer(4) :: i flag = dlginit(results, dli) if(.not. flag) stop 'Dialog results not found' flag = dlgset(dli, idcancel, 'Закрыть', dlg_title) ! Заменим имя Cancel кнопки idcancel flag = dlgset(dli, tabtit, 'Результаты расчета') ! на имя Закрыть flag = dlgset(dli, titn, 'Выбрано элементов') flag = dlgset(dli, tity, 'Средняя величина y') flag = dlgset(dli, nsel, '0') flag = dlgset(dli, avery, '0') flag = dlgset(dli, list, n, dlg_numitems) ! Передадим в диалог число элементов в списке
⎯ 33 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
do i = 1, n ! Цикл формирования списка list из n элементов write(string, '(a, f7.3, a, f8.3)') 'x = ', xres(i), '; y = ', yres(i) flag = dlgset(dli, list, adjustl(string), i) ! Добавляем очередной элемент в список end do ! Будем обрабатывать 2 воздействия на список: ! выбор элементов и двойной удар мышью flag = dlgsetsub(dli, list, treatlist, dlg_selchange) flag = dlgsetsub(dli, list, treatlist, dlg_dblclick) status = dlgmodal(dli) end subroutine shores subroutine rundial( ) ...
! Инициализация диалогов idd_txy и idd_say
subroutine indata(dlg, c_name, cbtype) aval, ...
! Подпрограмма indata связана с полями ! bval и dxval диалога idd_dxy
! Подпрограмма indata2 связана с элементами nval, n_scroll, use_dx ! и use_n диалога idd_dxy subroutine indata2(dlg, c_name, cbtype) ... subroutine what(dlg, c_name, cbtype) no ...
! Подпрограмма связана с элементами yes и ! (кнопки OK и Cancel) диалога idd_dxy
Итак, работая со списком, можно задать число элементов в списке и значения этих элементов, например: flag = dlgset(dli, list, 2, dlg_numitems) ! В списке 2 элемента flag = dlgset(dli, list, 'Николаев Е.А.', 1) ! Добавим в список list два элемента flag = dlgset(dli, list, 'Попов Ф. В.', 2)
Можно затем изменить число элементов в списке (увеличить или уменьшить), например (индекс DLG_NUMITEMS можно опустить): flag = dlgset(dli, list, 3) ! Теперь в списке 3 элемента flag = dlgset(dli, list, 'Сергеев М. К.', 3) ! Добавление третьего элемента
Применив функцию DLGGET, можно передать из диалога в программу число элементов списка и его первый выбранный элемент (одновременно в случае Multiple или Extended списка в нем можно выбрать более одного элемента): flag = dlgget(dli, list, n, dlg_numitems) flag = dlgget(dli, list, string, dlg_state) элемент
! Передадим в n число элементов в списке ! Передадим в string первый выбранный
⎯ 34 ⎯
1. Использование диалогов
Использовав цикл, можно считать номера всех выбранных элементов списка и их значения (см. текст вышеприведенной подпрограммы testlist). Можно также считать и конкретный элемент списка, задав при вызове DLGGET его номер: ! Передадим в string третий элемент списка list flag = dlgget(dli, list, string, dlg_state, 3)
Номер nseli первого выбранного элемента списка определится в результате вызова: flag = dlgget(dli, list, nseli, 1) списка
! nseli номер первого выбранного элемент
Тогда после вызова flag = dlgget(dli, list, string, dlg_state, nseli)
строка string будет содержать значение первого выбранного элемента. Могут быть заданы режимы одиночного (Single), множественного (Multiple) с использованием клавиши Shift и расширенного (Extended) выбора элементов списка. Открытый список воспринимает 2 вида воздействий: выбор элемента списка, двойной удар мышью по элементу списка. Каждое воздействие может быть обработано связанной со списком подпрограммой.
1.7.2. Списки с редактируемым полем Можно задать 2 вида списков с редактируемым полем: простой (Simple) и падающий (Drop Down). Простой список является смесью открытого списка и редактируемого поля: пользователь может выбрать элемент из открытого списка или ввести значение в редактируемое поле списка. (Напомним, что элементы списка имеют символьный тип.) Поскольку ввод данных может выполняться двумя способами, то подпрограмма, соединенная со списком, должна обрабатывать оба способа ввода данных. То есть необходимо выполнить 2 вызова функции DLGSETSUB, например: ! cosimp - ID-имя списка диалога dli flag = dlgsetsub(dli, cosimp, treatcom, dlg_selchange) flag = dlgsetsub(dli, cosimp, treatcom, dlg_update)
или flag = dlgsetsub(dli, cosimp, treatcom, dlg_change)
В подобного рода списках, в отличие от открытого списка, одновременно может быть выбран только один элемент. Значение выбранного элемента ⎯ 35 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
помещается в редактируемое поле списка. При вводе данных в редактируемое поле списка автоматически осуществляется поиск элемента списка, и если элемент найден, то он выбирается из списка. Значение выбранного элемента или значение, введенное в редактируемое поле, будет передано в строку string после вызова: flag = dlgget(dli, cosimp, string, dlg_change)
Создадим тестовый диалог comtest, в котором разместим 2 вида списков с редактируемым полем - простой с ID-именем cosimp и падающий с IDименем codropdown, а также рассматриваемый ниже падающий список без редактируемого поля с ID-именем codrop. При создании списков в окне Combo Box Properties на вкладке Styles выберем, во-первых, вид списка и, во-вторых, зададим свойство No integral height. Остальные свойства списков оставим без изменений. В каждом из списков разместим одну и ту же информацию - произвольный перечень фамилий (см. текст приводимой ниже программы). Также зададим в диалоге 3 текстовых поля с ID-именами sistring, drodowstring и dropstring. В первом поле будем отображать выбираемое или вводимое в редактируемое поле значение списка cosimp, во втором - выбираемое или вводимое в редактируемое поле значение списка codropdown, в третьем - выбираемое значение списка codrop. Для работы с диалогом создадим следующую программу: module drive use dialogm include 'resource.fd' type(dialog) :: dli logical(4) :: flag integer(4) :: status, num Drop character(50) :: string
! Задаем переменную dli диалога comtest ! num - номер выбранного элемента списка
contains диалогом
! treatcom - подпрограмма, связанная с
subroutine treatcom(dlg, c_name, cbtype) type(dialog) :: dlg мыши integer(4) :: c_name, cbtype select case(c_name) case(cosimp) flag = dlgget(dlg, cosimp, string) call seecase( ) flag = dlgset(dlg, sistring, string) case(codropdown)
! Подпрограмма вызывается при выборе ! элемента списка и при двойном ударе ! по элементу списка ! Работаем со списком Simple ! Передаем данные из диалога в программу ! Формируем строку для текстового поля ! Передаем данные из программы в диалог ! Работаем со списком Drop Down
⎯ 36 ⎯
1. Использование диалогов
flag = dlgget(dlg, codropdown, string) call seecase( ) flag = dlgset(dlg, drodowstring, string) case(codrop) ! Работаем со списком Drop flag = dlgget(dlg, codrop, string) ! Читаем num - номер выбранного элемента списка flag = dlgget(dlg, codrop, num, dlg_state) call seecase( ) flag = dlgset(dlg, dropstring, string) write(string, *) num ! Вывод num (после преобразований) в поле listitem flag = dlgset(dlg, listitem, adjustl(string)) end select contains subroutine seecase( ) диалога select case(cbtype) case(dlg_selchange) string = 'SC ' // string case(dlg_change) string = 'CH ' // string case(dlg_update) string = 'UP ' // string end select end subroutine seecase
! Формируем строку для текстового поля ! Добавим перед строкой сообщение ! SC, или CH, или UP для отображения ! производимого на список воздействия
end subroutine treatcom end module drive program comdrive ! Главная программа use drive integer(4), parameter :: n = 8 ! Число элементов в списке character(30), dimension(n) :: names ! Массив фамилий flag = dlginit(comtest, dli) if(.not. flag) stop 'Dialog comtest not found' names(1) = 'Николаев С. А.'; names(2) = 'Новиченко Д. Е.' names(3) = 'Норенков П. Г.'; names(4) = 'Норкиросян И. Т.' names(5) = 'Норкушев М. Л.'; names(6) = 'Якушкин Д. И.' names(7) = 'Андреев К. С.'; names(8) = 'Константинов Ю. Ф.' flag = dlgset(dli, cosimp, n, dlg_numitems) ! Установим число строк в списке cosimp do i = 1, n ! Формирование списка cosimp flag = dlgset(dli, cosimp, names(i), i) end do ! Установим число строк в списке codropdown
⎯ 37 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
flag = dlgset(dli, codropdown, n, dlg_numitems) do i = 1, n ! Формирование списка codropdown flag = dlgset(dli, codropdown, names(i), i) end do flag = dlgset(dli, codrop, n, dlg_numitems) ! Установим число строк в списке codrop do i = 1, n ! Формирование списка codrop flag = dlgset(dli, codrop, names(i), i) end do flag = dlgset(dli, sistring, '') ! Инициализация текстовых элементов диалога flag = dlgset(dli, drodowstring, '') flag = dlgset(dli, codrop, 2, dlg_state) ! Выберем в списке codrop второй элемент flag = dlgset(dli, dropstring, names(8)) ! Учтем, что после сортировки на второй flag = dlgset(dli, listitem, '2') ! строке списка будет восьмой элемент массива flag = dlgsetsub(dli, cosimp, treatcom, dlg_selchange) flag = dlgsetsub(dli, cosimp, treatcom, dlg_update) flag = dlgsetsub(dli, codropdown, treatcom, dlg_selchange) flag = dlgsetsub(dli, codropdown, treatcom, dlg_change) flag = dlgsetsub(dli, codrop, treatcom, dlg_selchange) status = dlgmodal(dli) end program comdrive
Результаты работы приведем на рис. 1.9.
Рис. 1.9. Тестовый диалог для списков вида Simple, Drop Down и Drop
1.7.3. Список без редактируемого поля В падающем списке без редактируемого поля (Drop List) можно выбрать или найти по первому символу значение элемента списка. Со списком Drop List, так же как и с рассмотренными выше списками, в функциях DLGSET и DLGGET могут быть применены индексы DLG_NUMITEMS, DLG_ENABLE, DLG_STATE (для символьного ⎯ 38 ⎯
1. Использование диалогов
компонента). Дополнительно, применив с целочисленным параметром индекс DLG_STATE, можно выбрать заданный своим номером элемент списка, например: flag = dlgset(dli, codrop, 2, dlg_state)
! Выберем в списке codrop второй элемент
или вернуть номер выбранного элемента: ! Читаем num - номер выбранного элемента списка flag = dlgget(dlg, codrop, num, dlg_state)
Список вида Drop может реагировать на 2 воздействия: выбор элемента и двойной удар мышью. В первом случае для связи списка с подпрограммой используется индекс DLG_SELSHANGE, во втором - DLG_DBLCLICK.
1.8. Выход из диалога Открытый диалог, если в нем оставлены задаваемые по умолчанию кнопки OK и Cancel, может быть закрыт в результате нажатия на одну из этих кнопок. При нажатии на OK активизирующая диалог функция DLGMODAL вернет IDOK, а при нажатии на Cancel - IDCANCEL. Возвращаемое значение может быть использовано в вызвавшей диалог программе, например: integer(4) :: status ... status = dlgmodal(dli) if(status == idok) then ... else ... end if
! Подготовка к открытию диалога ! Активизация диалога dli ! Если диалог закрыт в результате нажатия на OK ! Некоторый код ! Некоторый код
Диалог можно закрыть, нажав на Esc или на крестик (×) системного меню. Однако такая возможность существует, если в диалоге есть элемент с ID-именем IDCANCEL, в противном случае и Esc, и попытка выхода через системное меню будут проигнорированы. При необходимости можно выйти из диалога, применив в связанной с диалогом подпрограмме подпрограмму DLGEXIT(dlg). В этом случае функция DLGMODAL вернет ID-значение управляющего элемента, после воздействия на который подпрограмма DLGEXIT закрыла диалог. Отметим, что DLGEXIT передаст управление вызвавшей диалог программе только после того, как будут выполнены следующие после DLGEXIT операторы. ⎯ 39 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
1.9. Изменение возвращаемой диалогом величины Можно сделать так, что функция DLGMODAL вернет значение, отличное от ID-номера управляющего элемента, вызвавшего выход из диалога. Для этого, использовав подпрограмму DLGSETRETURN, следует задать свое собственное возвращаемое функцией DLGMODAL значение, например: type(dialog) :: dlg integer(4) :: alt_return ... alt_return = 500 call dlgsetreturn(dlg, alt_return) call dlgexit(dlg) ...
! Устанавливаем возвращаемое значение ! Закрываем диалог
Заметим, что ID-номера созданных в диалоге элементов можно просмотреть в FD-файле диалога. Напомним, что по умолчанию FD-файл получает имя resource.fd.
⎯ 40 ⎯
2. Вывод графических данных В настоящей главе дано описание графических процедур DVF, действие которых проиллюстрировано большим числом примеров. Приводимые графические процедуры могут быть использованы как в проектах со стандартной графикой (Standard Graphics), так и в проектах QuickWin. Проект со стандартной графикой является однооконным, а проект QuickWin - многооконным графическим проектом, в котором также можно реализовать некоторые возможности Win32 Application Programming Interface (API). Однако этим графические возможности DVF не ограничиваются. В DVF могут быть созданы проекты с любыми свойствами Windows-приложений, использующих в том числе и библиотеку OpenGL, содержащую графические функции создания трехмерных изображений и анимаций.
2.1. Графический дисплей В современных ЭВМ используются растровые дисплеи (существуют также векторные дисплеи). Графические возможности растрового дисплея определяются размером экрана, зерном экрана, частотой регенерации и разрешением. Размер экрана дисплея может варьироваться от 14 до 21 дюйма и более. Вывод изображения выполняется в результате подсветки электронным лучом отдельных регулярно расположенных точек экрана. Такая точка является наименьшим элементом графической информации и называется видеопикселем или просто пикселем. Число таких точек по горизонтали и вертикали экрана определяет его разрешение. Стандартными являются разрешения 640 * 480 (первая цифра указывает на число точек по горизонтали, вторая - по вертикали), 800 * 600, 1024 * 768, 1280 * 1024 и более высокие разрешения. На одном дисплее могут быть установлены разные разрешения. Управление разрешением выполняется графическим адаптером. Луч последовательно “пробегает” все точки экрана, выполняя заданную программой для каждой точки подсветку. Затем цикл обхода повторяется. Полный цикл обхода всех точек экрана приводит к воспроизведению изображения и называется регенерацией изображения. Число регенераций в секунду называется частотой регенерации. Низкая частота регенерации
⎯ 41 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
приводит к заметному для человеческого глаза миганию изображения. Хорошим показателем можно считать частоту регенерации не менее 60 Гц. Расстояние между соседними точками экрана определяет его зерно. Размеры зерна варьируются от 0,31 до 0,21 мм и менее. Понятно, что чем меньше зерно, тем выше качество изображения.
2.2. Растровое изображение В графике экран растрового дисплея представляется в виде прямоугольной сетки на дискретной плоскости с шагом по осям x и y, равным единице. Такая модель называется растровой плоскостью или растром (рис. 2.1, а). Массив прямоугольных ячеек плоскости называется растровым массивом. Каждый квадрат сетки соответствует одному пикселю экрана. Точка инициализации пикселя находится в центре квадрата сетки. Инициализации точки растра с координатами (i, j) соответствует закраска квадрата сетки, в центре которого эта точка расположена (рис. 2.1, б).
i, j
б
а
Рис. 2.1. Модель растрового экрана: а - растровая плоскость; б - инициализация точки растра
Растровое изображение создается путем закраски ячеек растрового массива в тот или иной цвет. Растровое изображение можно сравнить с изображением на листе клетчатой бумаги, получаемым в результате закраски отдельных клеточек листа. Каждому пикселю растра могут быть независимым образом заданы цвет, интенсивность и другие характеристики. При создании черно-белых изображений для хранения информации о цвете одного пикселя достаточно 1 бита памяти, записывая в соответствующую ячейку памяти 1, если элемент изображения закрашен в черный цвет, и 0 - если в белый. ⎯ 42 ⎯
2. Вывод графических данных
Цветное изображение создается комбинацией базовых цветов, в качестве которых могут быть использованы, например, красный - зеленый синий (RGB) или голубой - пурпурный - желтый - черный (CMYK). Задавая различную интенсивность при наложении базовых цветов, можно воспроизвести все различимые человеческим глазом цвета и оттенки. При работе с палитрой, содержащей 256 цветов, для хранения информации о цвете одного пикселя потребуется уже 1 байт (8 бит) памяти. В системе цветов RGB каждый пиксель содержит 3 точки, каждая из которых “отвечает” за свой цвет: красный, зеленый или синий. Комбинируя эти цвета и их интенсивность, можно управлять цветом каждого пикселя и, следовательно, всего изображения. Человеческий глаз не в состоянии различить отдельные точки пикселя в силу их маленького размера и воспринимает как целое получаемое в результате наложения цветов изображение.
2.3. Видеоадаптер Данные, подлежащие воспроизведению на экране монитора, формируются программой, функционирующей на центральном процессоре ЭВМ. Далее они передаются видеоадаптеру, который размещает эти данные в видеопамяти, преобразовывает и передает устройству управления лучом электронно-лучевой трубке. Таким образом, видеоадаптер является устройством сопряжения центрального процессора ЭВМ и устройства отображения. Видеоадаптеры могут обеспечить разные режимы отображения данных, которые разделяются на текстовые и графические. Причем, как правило, видеоадаптер поддерживает несколько графических режимов. Режим отображения данных на экране называется видеорежимом. В DVF видеорежим устанавливается при задании конфигурации видеоокна функцией SETWINDOWCONFIG. Различные видеорежимы отличаются количеством выводимых на экран данных и цветов. Единицами измерения количества выводимых данных являются: в текстовом режиме - символ (литера), в графическом видеопиксель (или просто пиксель). Например, видеоадаптер SVGA позволяет установить текстовый режим, в котором на экран выводится 37 строк по 100 символов в каждой (100 * 37), и графический режим, обеспечивающий разрешение 800 * 600 пикселей. Возможны, разумеется, и иные режимы работы видеоадаптера.
⎯ 43 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
В текстовом режиме 100 * 37 символ имеет размер 8 * 8 пикселей. Матрица 8 * 8 пикселей, используемая для отображения символа на экране, называется знакоместом. Символ передается в видеоадаптер в виде 2байтовой последовательности. Первый байт - код символа. Второй - его атрибуты: цвет символа и фона. В графическом режиме отображается 2 типа объектов: литеры (символы) и геометрические объекты. При отображении литер используются шаблоны (шрифты). В видеоадаптер для отображения строки литер следует передать коды (ASCII) литер, атрибуты строки (высота, угол наклона, цвет и др.), а также имя шрифта (имя файла, в котором размещены формы литер). При отображении литеры будет, во-первых, найдена форма литеры в указанном шрифте, выполнено преобразование (масштабирование, изменение угла наклона) и отображение преобразованной формы литеры на экране монитора в соответствии с ее атрибутами. Построение геометрических объектов выполняется по заданным точкам. Для геометрических объектов так же, как и для литер, могут быть определены атрибуты: цвет, тип и толщина линии и др.
2.4. Видеоокно и окна вывода В DVF в проектах с графикой вывод и текстовых и графических данных выполняется в видеоокно, параметры которого устанавливаются функцией SETWINDOWCONFIG. В проекте со стандартной графикой получаемое в начальный момент изображение отображается на полном экране без меню и вертикальной и горизонтальной полос прокрутки экрана. Затем видеоокно получает необходимые для него меню и полосы прокрутки; размеры и положение видеоокна могут быть изменены стандартным для Windowsприложений способом. В QuickWin каждое создаваемое (дочернее) видеоокно всегда располагается внутри обрамляющего окна. Размеры и положение обрамляющего и любого дочернего видеоокна могут быть изменены не только пользователем, но и из программы. По умолчанию открываемое видеоокно ассоциируется при выводе с устройствами *, 0 и 6, а при вводе - с устройствами * и 5. В приложениях QuickWin оператором OPEN видеоокно можно подсоединить и к нестандартному устройству. Вывод текстовых данных в видеоокно выполняется операторами PRINT, WRITE и подпрограммой OUTTEXT, чтение - оператором READ. ⎯ 44 ⎯
2. Вывод графических данных
Внутри видеоокна могут быть созданы прямоугольные области, называемые окнами вывода. По умолчанию окном вывода является все видеоокно, на которое и направляется как графический, так и текстовый вывод. С этим окном связана физическая система координат (разд. 2.6). Единицей измерения значений текстовых координат является знакоместо, а графических - пиксель. Затем программист внутри видеоокна может выделить окна для вывода текстовых и графических данных. Каждое такое окно имеет свою систему координат. Причем для вывода графических данных в DVF могут быть созданы окна c вещественной системой координат, что существенно упрощает работу с реальными физическими объектами. Число создаваемых окон вывода произвольно. Замечание. Далее, как правило, мы будем употреблять слово “окно” вместо более громоздкого слова “видеоокно”. Рассматриваемые ниже графические процедуры относятся к процедурам стандартной графики. Работать с ними можно и в проектах QuickWin. В каждом программном компоненте, где вызываются графические процедуры, необходимо выполнить ссылку на модуль DFLIB (USE DFLIB). Этот модуль содержит интерфейсы к графическим процедурам, необходимые для работы с графикой именованные константы и определения производных типов данных. Например, интерфейс к процедуре очистки экрана выглядит так: INTERFACE SUBROUTINE clearscreen[C,ALIAS:"__FQclearscreen"](area) INTEGER(2) area END SUBROUTINE END INTERFACE
2.5. Задание конфигурации видеоокна Установка конфигурации окна не является обязательной процедурой, но при необходимости может быть выполнена функцией flag4 = SETWINDOWCONFIG(wc) wc - переменная производного типа windowconfig, содержащая параметры окна. Тип windowconfig определен в модуле DFLIB: TYPE windowconfig INTEGER(2) numxpixels INTEGER(2) numypixels INTEGER(2) numtextcols INTEGER(2) numtextrows
! Число пикселей по оси x ! Число пикселей по оси y ! Число доступных столбцов текста ! Число доступных рядов текста
⎯ 45 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
INTEGER(2) numcolors ! Число индексов цвета INTEGER(4) fontsize ! Размер устанавливаемого по умолчанию ! шрифта. Принимает значение QWIN$EXTENDFONT ! в случае многобитовых символов. ! С многобитовыми шрифтами используется ! параметр extendfontsize CHARACTER(80) title ! Заголовок окна (CИ-строка) INTEGER(2) bitsperpixel ! Число бит на пиксель ! Следующие 3 параметра введены для поддержки многобитовых символов ! (например, символов японского языка). Параметр fontsize при пере! ходе к многобитовым шрифтам должен иметь значение QWIN$EXTENDFONT CHARACTER(32) extendfontname ! Любой непропорциональный шрифт INTEGER(4) extendfontsize ! Принимает то же значение, что и ! fontsize, но используется при ! работе с многобитовыми шрифтами INTEGER(4) extendfontattributes ! Атрибуты многобитовых шрифтов END TYPE windowconfig ! (жирный или курсив)
Функция возвращает значение стандартного логического типа, равное .TRUE., если конфигурация установлена, и .FALSE. - в противном случае. Если некоторым числовым компонентам windowconfig задать значение 1, то функция SETWINDOWCONFIG установит для них наивысшее возможное в вашей системе разрешение, сохраняя значения других, отличных от -1 компонентов. Можно задать реальные значения влияющих на размер окна компонентов: число пикселей по x и y, число столбцов и рядов текста и размер шрифта - и затем вызвать SETWINDOWCONFIG. Можно вообще не использовать SETWINDOWCONFIG. В этом случае окно выберет наилучшее из возможных разрешений и размер шрифта 8 * 16. Число доступных цветов зависит от видеоадаптера. Если используется SETWINDOWCONFIG, то следует задать значения каждого компонента wc (-1 или свое собственное значение и СИ-строку для заголовка окна). Если же SETWINDOWCONFIG используется и часть компонентов предварительно не определена, то это может привести к тому, что эти компоненты примут нежелательные значения. Если задана конфигурация, которая не может быть установлена, то SETWINDOWCONFIG возвращает .FALSE. и вычисляет значения параметров, с которыми возможна работа и которые близки к заданным значениям. Для установки окна с вычисленными параметрами необходимо выполнить второй вызов SETWINDOWCONFIG, например: status = setwindowconfig(wc)
! status - переменная типа LOGICAL(4)
⎯ 46 ⎯
2. Вывод графических данных
if(.not. status) status = setwindowconfig(wc)
Если заданы значения всех влияющих на размер окна параметров: numxpixels, numypixel, numtextcols и numtextrows, то размер шрифта вычисляется по этим значениям. По умолчанию устанавливается шрифт Courier New с размером 8 * 16. В случае стандартного графического приложения, если задано разрешение, совпадающее со значениями, задаваемыми графическим адаптером (или -1 для параметров, влияющих на размер окна), приложение начинает выполняться на всем экране. В противном случае приложение начинает выполняться в окне. Для переключений “полный экран - окно” и “окно - полный экран” можно использовать ALT + ENTER. По умолчанию курсор в окне отсутствует. Однако после вызова функции result2 = DISPLAYCURSOR($GCURSORON) он становится видимым. Пример: use dflib type(windowconfig) wc logical :: res = .false. wc.numxpixels = 800; wc.numypixels = 600 wc.numtextcols = -1; wc.numtextrows = -1 wc.numcolors = -1; wc.fontsize = -1 wc.title = "Первое графическое окно"c res = setwindowconfig(wc) if(.not. res) res = setwindowconfig(wc)
Значения параметров окна возвращаются функцией flag4 = GETWINDOWCONFIG(wc) wc - переменная типа windowconfig, содержащая конфигурацию активного дочернего графического окна. Функция возвращает значение стандартного логического типа, равное .TRUE., в случае успешного выполнения, и .FALSE. - в противном случае (если нет активного дочернего окна). Если для задания параметров окна функция SETWINDOWCONFIG не была использована, то GETWINDOWCONFIG возвращает wc с устанавливаемыми по умолчанию значениями компонентов. Число возвращаемых цветов зависит от графического адаптера. Заголовок окна Graphic1. Все эти значения могут быть изменены функцией SETWINDOWCONFIG. ⎯ 47 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Поле bitsperpixel в типе windowconfig доступно только для чтения, в то время как значения всех иных полей могут быть изменены. Пример. Вывести параметры графического окна. use dflib type(windowconfig) wc logical :: res = .false. wc.numxpixels = -1; wc.numypixels = -1 wc.numtextcols = -1; wc.numtextrows = -1 wc.numcolors = -1; wc.fontsize = -1 wc.title= "Первое графическое окно"c res = setwindowconfig(wc) res = getwindowconfig(wc) print *,'Resolution:', wc.numxpixels, ' *', wc.numypixels ! 800*600 print *,'Text screen:', wc.numtextcols, ' *', wc.numtextrows ! 100*37 print *, 'Numcolors:', wc.numcolors ! 16 print *, 'Fontsize:', wc.fontsize ! 524304 (8*16) print *, 'Bit per pixel:', wc.bitsperpixel ! 1 end
2.6. Системы графических координат. Окно вывода Растровая плоскость расположена в физической системе координат, определяемой техническими средствами. Начало физической системы координат - левый верхний угол окна. Ось абсцисс направлена слева направо; ось ординат - сверху вниз (рис. 2.2). Физические координаты целочисленны. Наименьшие координаты пикселя xmin = 0 и ymin = 0. Наибольшие координаты определяются установленным разрешением. Так, при разрешении 800 * 600 наибольшие координаты пикселя xmax = = 799 и ymax = 599. Используемый при задании координат тип данных INTEGER(2).
⎯ 48 ⎯
2. Вывод графических данных
0
xp
x Физическая система координат
x1, y1
Окно вывода yp Видовая система координат x2, y2 y
Рис. 2.2. Системы графических координат
Точка начала отсчета координат пикселей может быть перемещена в любую точку окна xp, yp. Эта процедура называется заданием видового порта. Так, после перемещения точки отсчета в центр видеоокна минимальные координаты пикселя при разрешении 800 * 600 будут равны (-400, -300), а максимальные - (399, 299). Начало координат видового порта задается подпрограммой CALL SETVIEWORG(xp, yp, t) xp, yp - выражения типа INTEGER(2), задающие начало координат нового видового порта в физической системе координат (рис. 2.2). t - переменная типа xycoord, содержащая значения xcoord, ycoord начала координат предыдущего видового порта. Тип xycoord определен в модуле DFLIB: TYPE xycoord INTEGER(2) xcoord INTEGER(2) ycoord END TYPE xycoord
! x-координата ! y-координата
Подпрограмма SETVIEWORG перемещает начало координат (0, 0) в точку физической системы координат (xp, yp). Получаемая таким образом система координат называется видовой системой координат. Задаваемые в видовой системе координаты называются видовыми координатами. По умолчанию при инициализации графического режима начало координат текущего видового порта совпадает с началом физической системы координат. Пример. Нарисовать отрезок прямой в физической системе координат. Координаты отрезка (40, 20) и (400, 200). ⎯ 49 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
use dflib ! Режим 800*600, 16 цветов integer(2) :: status2, xa = 40, ya = 20, xb = 400, yb = 200 integer(4) :: status4 type(xycoord) xy ! Для вызова подпрограммы MOVETO status4 = setbkcolorrgb(#c0c0c0) ! Цвет фона - серый call clearscreen($gclearscreen) ! Заливка окна цветом фона status2 = setcolor(9_2) ! Текущий цвет - ярко-синий call moveto(xa, ya, xy) ! Перемещение в точку (xa, ya) status2 = lineto(xb, yb) ! Вывод отрезка end
Если бы мы работали в видовой системе координат, начало которой расположено, например, в центре окна, то та же самая линия имела бы координаты (-360, -280) и (0, -100) и для ее вывода должны были бы быть выполнены операторы call setvieworg(400_2, 300_2, xy) call moveto(-360_2, -280_2, xy) status2 = lineto(0_2, -100_2)
По умолчанию областью вывода графических данных является вся растровая плоскость (все видеоокно). Однако полезно иметь возможность выделять в окне разные области вывода данных. В графических системах выделяются прямоугольные области вывода, называемые окнами вывода. Выделение окна вывода графических данных реализуется подпрограммой CALL SETCLIPRGN(x1, y1, x2, x2) x1, y1 - координаты верхнего левого угла окна вывода (см. рис. 2.2). x2, y2 - координаты нижнего правого угла окна вывода. Тип параметров x1, y1, x2, y2 - INTEGER(2). Функция ограничивает область вывода данных. Физические координаты (x1, y1) и (x2, y2) вершин окна задаются относительно начала координат текущего видового порта. Графические объекты или их части, выходящие за границы окна вывода, не отображаются. Замечание. Функция SETCLIPRGN ограничивает вывод только графических элементов, перечень которых дан в разд. 2.10. Ограничение области вывода текста при помощи OUTTEXT, WRITE и PRINT выполняется подпрограммой SETTEXTWINDOW. Видовой порт (видовую систему координат) и одновременно окно вывода можно задать одной подпрограммой CALL SETVIEWPORT(x1, y1, x2, y2) x1, y1 - координаты верхнего левого угла окна вывода. ⎯ 50 ⎯
2. Вывод графических данных
x2, y2 - координаты нижнего правого угла окна вывода. Тип параметров x1, y1, x2, y2 - INTEGER(2). Подпрограмма переопределяет графический видовой порт и задает окно вывода так же, как это выполняется в результате задания окна вывода подпрограммой SETCLIPRGN и последующей установки начала координат видового порта в левый верхний угол окна. То есть начало видовой системы координат после вызова call setviewport(x1, y1, x2, y2)
будет совпадать с верхним левым углом окна вывода. Координаты (x1, y1) и (x2, y2) задаются в физической системе координат. Все последующие преобразования окна вывода посредством функции SETWINDOW выполняются в видовой, а не в физической системе координат. В рассмотренных системах мы имели дело с целочисленными координатами, диапазон изменения которых определялся разрешением дисплея и заданным окном. В DVF, однако, есть возможность задать окно вывода, связав его с оконной системой координат (ОСК), в которой используются вещественные координаты. Задаваемые в оконной системе координаты называются оконными координатами. ОСК может совпадать с мировой системой координат, т. е. с системой, в которой мы работаем с реальными объектами. Окно вывода, в котором можно работать с вещественными координатами, задается в видовой системе координат функцией result2 = SETWINDOW(finvert, wx1, wy1, wx2, wy2) finvert - параметр типа LOGICAL(2), определяющий направление координат (сли finvert равен .TRUE., то ось y увеличивается от нижней границы видового порта к верхней; если finvert равен .FALSE., то ось y увеличивается от верхней границы видового порта к нижней, т. е. так же, как и в видовой системе координат). wx1, wy1 - верхний левый угол окна в ОСК. wx2, wy2 - нижний правый угол окна в ОСК. Тип параметров wx1, wy1, wx2, wy2 - REAL(8). Тип функции INTEGER(2). Функция возвращает отличное от нуля значение при успешном открытии окна и 0 в противном случае. Функция SETWINDOW определяет ОСК, в которой координаты выводимых объектов ограничены значениями параметров wx1, wy1, wx2, wy2. В общем случае начало координат ОСК может находиться за пределами окна вывода. ⎯ 51 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
В ОСК могут быть использованы для вывода графических элементов только функции, имена которых завершаются символами _W (например, ARC_W, RECTANGLE_W или LINETO_W). Задание ОСК выполняется относительно текущего видового порта. Пример. Построить график функции y = sin(x) на отрезке от -π до π. Пусть размеры окна по осям x и y равны соответственно XE и YE. Используем для вывода графика расположенный в центре окна видовой порт, ширина и высота которого равны соответственно XE/2 и YE/2. В этом видовом порте зададим ОСК, начало которой расположено в центре видеоокна, а ось y направлена снизу вверх (рис. 2.3). 3XE/4
XE/4
−π 3YE/4
x
Физическая система координат
1
YE/4
XE
π -1
Окно вывода
Видеоокно
YE y
Оконная система координат
Рис. 2.3. Оконная система координат для графика y = sin(x)
use dflib ! Рисуем график y = sin(x) integer(2) :: status2, XE, YE ! Режим 800*600, 16 цветов integer(4) :: status4 real(8), parameter :: pi = 3.14159265_8 real(8) :: dx, x, y logical(2) :: finv = .true., res type(windowconfig) wc status4 = setbkcolorrgb(#ff0000) ! Цвет фона - синий call clearscreen($gclearscreen) ! Заливка экрана цветом фона res = getwindowconfig(wc) XE = wc.numxpixels ! numxpixels - число пикселей по оси x YE = wc.numypixels ! numxpixels - число пикселей по оси y call axis( ) ! Рисуем оси координат ! Задание видового порта размером XE/2 * YE/2 в центре видеоокна call setviewport(XE/4_2, YE/4_2, 3_2*XE/4_2, 3_2*YE/4_2) ! Оконная система координат (ОСК)
⎯ 52 ⎯
2. Вывод графических данных
status2 = setwindow(finv, -pi, dble(-1), pi, dble(1)) status2 = setcolor(10_2) ! График светло-зеленым цветом dx = pi/dble(XE/2) ! Шаг по оси x x = -pi do while(x <= pi) ! Изменение x в ОСК y = dsin(x) ! Значение y в ОСК status2 = setpixel_w(x, y) ! Вывод пикселя в OСК x = x + dx end do contains subroutine axis( ) ! Вывод осей координат type(xycoord) xy status2 = setcolor(7_2) ! Оси координат - белым цветом call moveto(int2(XE/4 - 10), int2(YE/2), xy) status2 = lineto(3_2*XE/4_2 + 10_2, YE/2_2) ! Ось x call moveto(int2(XE/2), int2(YE/4 - 10), xy) status2 = lineto(XE/2_2, 3_2*YE/4_2 + 10_2) ! Ось y end subroutine end
2.7. Очистка и заполнение экрана цветом фона Видеоокно, текущий видовой порт и текущее текстовое окно вывода очищаются после применения подпрограммы CALL CLEARSCREEN(area) area - определенная в модуле DFLIB константа, указывающая на тип очищаемой области и принимающая значения: $GCLEARSCREEN - очистка всего видеоокна; $GVIEWPORT- очистка текущего видового порта; $GWINDOW - очистка текущего текстового окна, заданного посредством SETTEXTWINDOW. Помимо очистки заданная область заполняется цветом текущего фона. Пример. Залить видеоокно белым цветом, изобразить в его центре прямоугольник фиолетового цвета, в центре прямоугольника задать текстовое окно белого цвета, в котором темно-серыми буквами написать `TOP`. use dflib integer(2) :: status2 integer(4) :: status4 type(rccoord) rc
! Режим 800*600, 16 цветов ! и 100*37 для текста ! Для вызова SETTEXTPOSITION
⎯ 53 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
status4 = setbkcolor(15) ! Белый фон call clearscreen($gclearscareen) ! Заливка текстового окна status2 = setcolor(5_2) ! Фиолетовый цвет status2 = rectangle($gfillinterior, 200, 100, 600, 500) call settextwindow(15, 40, 25, 60) ! Задание текстового окна call clearscreen($gwindow) ! Заливка текстового окна status2 = settextcolor(8) ! Темно-серый цвет call settextposition(6, 10, rc) call outtext('TOP') end
Замечание. Текстовое окно всегда задается относительно верхнего левого угла видеоокна. Позиция текста задается относительно верхнего левого угла текстового окна.
2.8. Управление цветом 2.8.1. Система цветов RGB. Цветовая палитра Количество доступных для воспроизведения цветов определяется, с одной стороны, возможностями дисплея, видеоадаптера, используемой системой цветов, а с другой - установленным видеорежимом. В ПЭВМ преимущественно используется система цветов RGB, в которой любой цвет получается в результате изменения интенсивности и смешения базовых цветов: красного, зеленого и синего. Конструктивно такая возможность обеспечивается устройством электронно-лучевой трубки, в которой каждый видеопиксель содержит 3 точки: красную, зеленую и синюю. Подсвечивая с различной интенсивностью эти точки, современные видеоадаптеры и дисплеи могут воспроизвести миллионы цветов и оттенков. Однако в большинстве приложений можно обойтись 256 или 16 цветами. При загрузке видеорежима компьютер создает индексированную палитру цветов, т. е. такой набор цветов, в котором каждому цвету (его значению) присвоен свой номер. Число цветов в палитре можно определить после обращения к функции GETWINDOWCONFIG. Цветовая палитра устанавливает соответствие между значением цвета в системе цветов RGB и номером цвета в палитре. Поясним понятие “значение цвета”. Для описания каждого цвета RGB используется 3 байта (24 RGB-бита). Каждый байт хранит интенсивность красного, зеленого и синего цветов: синий байт
зеленый байт
⎯ 54 ⎯
красный байт
2. Вывод графических данных
bbbbbbbb
gggggggg
rrrrrrrr
Синий цвет наибольшей интенсивности получается, если в RGB-байтах установлено значение #FF0000 (шестнадцатеричное FF соответствует двоичному коду 11111111). Наибольшей интенсивности зеленого цвета соответствует значение #00FF00, а красного - #0000FF. При наложении трех базовых цветов максимальной интенсивности (значение #FFFFFF) получится ярко-белый цвет. Всего на трех RGB-байтах может быть задано 256 * 256 * 256 = 16’777’216 различных цветов и оттенков. Значением цвета называется число, содержащееся в 24 RGB-битах, т. е. в битах задания цвета. В табл. 2.1 приведены значения цветов для 16цветовой палитры RGB. Таблица 2.1. RGB-значения цветов Цвет
RGB-значение
Цвет
RGB-значение
Черный
#000000
Ярко-белый
#FFFFFF
Светло-красный
#000080
Красный
#0000FF
Светло-зеленый
#008000
Зеленый
#00FF00
Светло-желтый
#008080
Желтый
#00FFFF
Светло-синий
#800000
Синий
#FF0000
Светлофиолетовый
#800080
Фиолетовый
#FF00FF
Светлобирюзовый
#808000
Бирюзовый
#FFFF00
Светло-серый
#808080
Серый
#C0C0C0
Значение цвета типа INTEGER(4) по его известным RGB-компонентам возвращает функция result4 = RGBTOINTEGER(red, green, blue) red - параметр типа INTEGER(4), задающий значение интенсивности красного компонента цвета. green - параметр типа INTEGER(4), задающий значение интенсивности зеленого компонента цвета. blue - параметр типа INTEGER(4), задающий значение интенсивности синего компонента цвета. Значения параметров red, green и blue находятся в диапазоне от 0 до 255. ⎯ 55 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Пример. Нарисовать квадрат фиолетового цвета на бирюзовом фоне. use dflib ! Режим 800*600, 16 цветов integer(2) :: st2 integer(4) :: ctm, st4 ctm = rgbtointeger(0, 255, 255) ! Значение бирюзового цвета st4 = setbkcolorrgb(ctm) ! Цвет фона call clearscreen($gclearscreen) ! Заполнение экрана цветом фона ctm = rgbtointeger(200, 0, 200) ! Оттенок фиолетового цвета st4 = setcolorrgb(ctm) ! Цвет вывода графических данных st2 = rectangle($gfillinterior, 300, 200, 500, 400) end
Подпрограмма CALL INTEGERTORGB(rgb, red, green, blue) по значению цвета rgb типа INTEGER(4) возвращает значения его RGBкомпонентов: red, green, blue. Тип возвращаемых значений - INTEGER(4). Например: use dflib integer(4) :: r, g, b call integertorgb(13107400, r, g, b) print *, r, g, b end
!
200
0
200
2.8.2. Цветовая палитра VGA В режиме VGA с 16 цветами и разрешением 640 * 480 пикселей по умолчанию устанавливается приведенная в табл. 2.2 индексированная палитра цветов. Таблица 2.2. Шестнадцатицветовая палитра VGA №
Цвет
Значение
№
Цвет
Значение
0
Черный
#000000
8 Серый
#202020
1
Синий
#200000
9 Светло-синий
#3F0000
2
Зеленый
#002000
10 Светло-зеленый
#003F00
3
Голубой
#202000
11 Светло-голубой
#3F3F00
4
Красный
#000020
12 Светло-красный
#00003F
5
Фиолетовый
#200020
13 Светло-фиолетовый #3F003F
6
Коричневый
#002020
14 Желтый
#003F3F
7
Белый
#303030
15 Ярко-белый
#3F3F3F
⎯ 56 ⎯
2. Вывод графических данных
В отличие от полной RGB-палитры при задании значения цвета в режиме VGA используется только 6 наименее значащих битов каждого из RGB-байтов: синий байт 00bbbbbb
зеленый байт 00gggggg
красный байт 00rrrrrr
Поэтому в режиме VGA можно задать только 64 * 64 * 64 = 262144 цветов (256 K). Причем одновременно может быть задано не более 256 цветов. Палитра из 256 цветов может быть задана при разрешении 320 * 200 пикселей. При разрешении 640 * 480 пикселей в режиме VGA можно одновременно задать 2 или 16 цветов. Число цветов в палитре задается компонентом wc.numcolors в типе windowconfig и последующим выполнением функции SETWINDOWCONFIG. Соответствие между индексом (номером) цвета в палитре и его значением можно изменить, обратившись к функциям result4 = REMAPPALETTERGB(index, color) или result4 = REMAPALLPALETTERGB(colors) color - значение цвета, которому будет присвоен номер цвета палитры, заданный параметром index. index - номер цвета в палитре, устанавливаемый в color. colors - массив, содержащий значения цветов. Тип параметров color, index, colors - INTEGER(4). Функция REMAPPALETTERGB: •
возвращает при успешном завершении предыдущее значение цвета для заданного номера и -1 в случае неудачи;
•
приписывает номер цвета значению цвета, поддерживаемому в текущем видеорежиме. Функция REMAPALLPALETTERGB:
•
возвращает 0 при успешном выполнении и -1 в случае ошибки;
•
переназначает одновременно один или более доступных в установленном видеорежиме номеров цветов. Обе функции немедленно обновляют текущие цвета объектов в соответствии с новой палитрой. Тип функций - INTEGER(4). Функция REMAPALLPALETTERGB может изменить значения цветов не более чем для 236 индексов. Двадцать индексов резервируется для нужд ⎯ 57 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
системы. Если задано значение цвета, не находящегося в палитре, то при выводе графических примитивов выполняется аппроксимация значения и из палитры выбирается наиболее близкий к заданному цвет. Изменение палитры не может быть выполнено на адаптерах с числом цветов 64 K, SVGA-адаптерах и машинах, использующих полную систему RGB-цветов. На машинах с VGA если вы, работая в многооконном режиме QuickWin, изменили значения всех цветов палитры и использовали ее для графического вывода в одном окне, то в другом дочернем окне попытка изменения палитры приведет к изменению цвета в первом дочернем окне. Замечание. На машине, поддерживающей более 256 цветов, нельзя выполнить анимацию путем изменения палитры. Windows 95 и Windows NT создают логическую палитру, соответствующую палитре, которая установлена аппаратно. Для палитры в 256 цветов и менее выполняется (при помощи функций REMAPPALETTERGB и REMAPALLPALETTERGB) ее непосредственное изменение. Для палитры с числом цветов более 256 изменения выполняются в неиспользованной при графическом выводе части палитры. Таким образом, текущие цвета не изменяются и, следовательно, палитровая анимация невозможна. Для значений цветов в 16-цветовой палитре VGA в модуле DFLIB определены именованные константы: $BLACK, $BLUE, $GREEN, $CYAN, $RED, $MAGENTA, $BROWN, $WHITE, $GRAY, $LIGHTBLUE, $LIGHTGREEN, $LIGHTCYAN, $LIGHTRED, $LIGHTMAGENTA, $YELLOW, $BRIGHTWHITE. Все графические режимы адаптера VGA воспроизводятся на аналоговых VGA-мониторах. На монохромных мониторах цвета воспроизводятся как оттенки серого цвета. Пример. Продемонстрировать различные оттенки синего, зеленого, красного, серого, бирюзового, фиолетового и салатового цветов. Выведем первоначально 8 горизонтальных полос, закрасив их в цвета загружаемой по умолчанию палитры. Затем, изменяя палитру, выведем поочередно требуемую картину цветов. В программе учтено, что серый цвет получается в результате смешения красного, зеленого и синего одинаковой интенсивности. Бирюзовый - в результате смешения цветов синего и зеленого с равной интенсивностью, и т. д. use dflib integer(2) :: st2, dy = 20, y = 0, k, ic integer(4) :: st4, i, pal(0:15) = 0
⎯ 58 ⎯
2. Вывод графических данных
logical(4) :: res type(windowconfig) wc wc.numxpixels = 640; wc.numypixels = 480 wc.numtextcols = -1; wc.numtextrows = -1 wc.numcolors = 256; wc.fontsize = -1 wc.title = 'Palette test'c res = setwindowconfig(wc) if(.not. res) stop 'Cannot run palette test' do k = 8, 15 ! Цикл формирования горизонтальных полос st2 = setcolor(k) ! k - номер (индекс) цвета в палитре st2 = rectangle($gfillinterior, 0, y, 639, y + dy) y = y + dy enddo read * ! Просмотр результата до нажатия Enter do ic = 1, 7 ! Цикл вывода оттенков разных цветов k=8 ! Изменяем значение для цвета с номером k do i = 140, 255, 15 ! Формирование значений цветов select case(ic) case(1) pal(k) = rgbtointeger(0, 0, i) ! Синий case(2) pal(k) = rgbtointeger(0, i, 0) ! Зеленый case(3) pal(k) = rgbtointeger(i, 0, 0) ! Красный case(4) pal(k) = rgbtointeger(i, i, i) ! Серый case(5) pal(k) = rgbtointeger(0, i, i) ! Бирюзовый case(6) pal(k) = rgbtointeger(i, 0, i) ! Фиолетовый case(7) pal(k) = rgbtointeger(i, i, 0) end select k=k+1 end do st4 = remapallpalettergb(pal) ! Изменение цветовой палитры read * ! Ожидаем нажатия Enter end do end
Вариант последовательного изменения цветов полос. ... do ic = 1, 7
⎯ 59 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
k=8 do i = 140, 255, 15 select case(ic) case(1) st4 = remappalettergb(k, rgbtointeger(0, 0, i)) ! Синий case(2) st4 = remappalettergb(k, rgbtointeger(0, i, 0)) ! Зеленый case(3) st4 = remappalettergb(k, rgbtointeger(i, 0, 0)) ! Красный case(4) st4 = remappalettergb(k, rgbtointeger(i, i, i)) ! Серый case(5) st4 = remappalettergb(k, rgbtointeger(0, i, i)) ! Бирюзовый case(6) st4 = remappalettergb(k, rgbtointeger(i, 0, i)) ! Фиолетовый case(7) st4 = remappalettergb(k, rgbtointeger(i, i, 0)) end select k=k+1 end do read * ! Ожидаем нажатия Enter end do ...
2.8.3. Не RGB-функции управления цветом Для управления цветом в DVF существует 2 рода функций: •
функции, позволяющие задать любой из RGB-цветов (RGB-функции) и устанавливающие цвет по его значению;
•
функции, устанавливающие цвет по его номеру (индексу) в цветовой палитре (не RGB-функции). К первым относятся функции SETTEXTCOLORRGB, SETBKCOLORRGB и SETCOLORRGB. Ко вторым - функции SETTEXTCOLOR, SETBKCOLOR и SETCOLOR. Число цветов, которые можно задать не RGB-функциями, зависит от установленной конфигурации окна, но не может в любом случае превышать 256. 2.8.3.1. Управление цветом фона Задание текущего цвета фона по его номеру в цветовой палитре выполняется функцией result4 = SETBKCOLOR(index) ⎯ 60 ⎯
2. Вывод графических данных
index - параметр типа INTEGER(4), равный номеру цвета устанавливаемого фона. Функция возвращает значение типа INTEGER(4), равное номеру цвета старого фона. По умолчанию устанавливается черный цвет фона. Номер текущего цвета фона возвращается функцией типа INTEGER(4): result4 = GETBKCOLOR( ) При выводе текста подпрограммой OUTTEXT функция SETBKCOLOR задает фон, на котором выводится текст. Если же после вызова функции SETBKCOLOR вызвать подпрограмму CLEARSCREEN, то выполнится очистка заданной области и ее заполнение заданным цветом фона. Пример 1. Вывести в текстовом видеорежиме строку “Проба фона”, используя разные цвета текста и фона. use dflib integer(2) :: status2 integer(4) i, status4 character(15) :: st = 'Background test' type(rccoord) rc do i = 0, 15 status4 = setbkcolor(i) status2 = settextcolor(int2(15 - i)) call settextposition(int2(10 + i), 40, rc) call outtext(st) enddo end
! Пусть сумма номеров цвета текста ! и фона равняется 15 ! Режим 800*600, 16 цветов ! и 100*37 в текстовом режиме ! Цвет фона ! Цвет текста
Пример 2. Просмотреть ярко-белый квадрат на разном фоне. use dflib ! Режим 800*600, 16 цветов integer(2) :: status2 integer(4) :: status4, i status2 = setcolor(15_2) ! Ярко-белый цвет do i = 1, 14 status4 = setbkcolor(i) ! Цвет фона call clearscreen($gclearscreen) ! Заполнение экрана цветом фона status2 = rectangle($gfillinterior, 350, 250, 450, 350) call sleepqq(1500) ! Задержка enddo end
⎯ 61 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
2.8.3.2. Управление цветом неграфического текста Номер цвета текста (см. табл. 2.2), выводимого подпрограммой OUTTEXT и операторами WRITE и PRINT, устанавливается функцией result2 = SETTEXTCOLOR(index) index - параметр типа INTEGER(2), задающий номер (индекс) цвета. По умолчанию устанавливается цвет с номером 15, который, если не была изменена палитра, ассоциируется с ярко-белым цветом. Функция возвращает значение типа INTEGER(2), которое равно номеру предыдущего цвета текста. Установленный функцией SETTEXTCOLOR цвет не воспринимается подпрограммой OUTGTEXT. В графическом режиме диапазон номеров цветов зависит от выбранного видеорежима и может быть определен после вызова функции GETWINDOWCONFIG. Функции SETTEXTCOLOR и SETCOLOR работают в графическом режиме в одном и том же диапазоне цветов. Номер заданного SETTEXTCOLOR цвета возвращается функцией result2 = GETTEXTCOLOR( ) Тип возвращаемого результата - INTEGER(2). Пример. Вывести текущую конфигурацию видеоокна в текстовом окне. use dflib logical(4) :: res integer(2) :: status2 integer(4) :: status4 character(15) :: st type(windowconfig) :: w type(rccoord) rc ! Режим 800*600, 16 цветов res = getwindowconfig(w) ! и 100*37 для текста status4 = setbkcolorrgb(#ff0000) ! Цвет фона видеоокна - синий call clearscreen($gclearscreen) ! Заливка экрана цветом фона call settextwindow(15, 35, 19, 65) ! Задание текстового окна status4 = setbkcolorrgb(#800000) ! Цвет фона окна - голубой call clearscreen($gwindow) ! Заливка окна цветом фона status2 = settextcolor(14) ! Цвет текста - желтый write(st, '(i3, a, i3)') w.numxpixels, ' * ', w.numypixels call outtext('Resolution: ' // st) ! 800*600 call settextposition(2, 1, rc) write(st, '(i3, a, i3)') w.numtextcols, ' *', w.numtextrows call outtext('Text screen size: ' // st) ! 100*37 call settextposition(3, 1, rc) call outtext('Number of colors: ' // stval(w.numcolors)) ! 16
⎯ 62 ⎯
2. Вывод графических данных
call outtext('Bits per pixel: ' // stval(w.bitsperpixel))
!1
contains character(5) function stval(val) integer(2) :: val, i = 2 i=i+1 call settextposition(i, 1, rc) write(stval, '(i3)') val end function end
! Номер ряда в текстовом окне ! Изменяем позицию вывода ! Преобразование “число - строка” ! устанавливает возвращаемое значение
2.8.3.3. Управление цветом графических примитивов Текущий цвет выводимых графических элементов: не RGB-пикселя, отрезка прямой, прямоугольника, многоугольника, эллипса, дуги, сектора, графического текста и не RGB-заливки - может быть задан не RGBфункцией result2 = SETCOLOR(index) index - номер устанавливаемого цвета (параметр типа INTEGER(2)). Функция при успешном выполнении возвращает значение типа INTEGER(2), равное номеру предыдущего цвета или -1 в случае неудачи. Устанавливаемый по умолчанию текущий цвет имеет максимальный номер в текущей палитре. Номер текущего цвета можно определить, обратившись к функции result2 = GETCOLOR( ) Тип функции - INTEGER(2).
2.8.4. RGB-функции управления цветом 2.8.4.1. Управление RGB-цветом фона Текущий цвет фона по его значению устанавливается функцией result4 = SETBKCOLORRGB(color) color - параметр типа INTEGER(4), равный RGB-значению цвета устанавливаемого фона. Функция возвращает величину типа INTEGER(4), равную RGBзначению цвета старого фона. Значение типа INTEGER(4) текущего цвета фона возвращается функцией result4 = GETBKCOLORRGB( ) При выводе текста посредством подпрограммы OUTTEXT функция SETBKCOLORRGB задает фон, на котором выводится текст. ⎯ 63 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Если же после вызова функции SETBKCOLORRGB вызвать подпрограмму CLEARSCREEN, то выполнится очистка заданной области и ее заполнение заданным RGB-цветом фона. Пример. Заполнить поочередно видеоокно оттенками фиолетового цвета. use dflib integer(4) :: oldcolor, k do k = 120, 255, 15 ! Фиолетовый - смесь красного и синего oldcolor = setbkcolorrgb(rgbtointeger(k, 0, k)) call clearscreen($gclearscreen) call sleepqq(1500) ! Задержка enddo end
2.8.4.2. Управление RGB-цветом неграфического текста Значение RGB-цвета текста, выводимого подпрограммой OUTTEXT и операторами WRITE и PRINT, устанавливается функцией result4 = SETTEXTCOLORRGB(color) color - параметр типа INTEGER(4), задающий значение цвета. Функция возвращает величину типа INTEGER(4), которая равна значению предыдущего цвета текста. Установленный SETTEXTCOLORRGB цвет не оказывает влияния на работу подпрограммы OUTGTEXT. Номер заданного SETTEXTCOLORRGB цвета возвращается функцией result4 = GETTEXTCOLORRGB( ) Тип возвращаемого результата - INTEGER(4). 2.8.4.3. Управление RGB-цветом графических примитивов Текущий цвет выводимых графических элементов: отрезка прямой, прямоугольника, многоугольника, эллипса, дуги, сектора, графического текста - может быть задан RGB-функцией result4 = SETCOLORRGB(color) color - RGB-значение типа INTEGER(4) устанавливаемого цвета. Функция при успешном выполнении возвращает значение типа INTEGER(4), равное значению предыдущего цвета. Значение текущего цвета можно определить, вызвав функцию result4 = GETCOLORRGB( ) Тип возвращаемого результата INTEGER(4). ⎯ 64 ⎯
2. Вывод графических данных
Пример. Создать 10 вертикальных полос из различных оттенков серого цвета. use dflib ! Режим 800*600 integer(4) :: st4, k = 105 ! k задает интенсивность серого цвета integer(2) :: x = 0, col ! Серый цвет - наложение оттенков do col = 1, 10 ! красного, зеленого и синего цветов st4 = setcolorrgb(rgbtointeger(k, k, k)) st4 = rectangle($gfillinterior, x, 0, x + 80, 599) x = x + 80 k = k + 15 enddo end
2.9. Текущая позиция графического вывода Графические примитивы текст и отрезок прямой выводятся начиная от текущей позиции. После перехода в графический режим текущая позиция находится в центре видеоокна. Изменение текущей позиции выполняется подпрограммами CALL MOVETO(x, y, t) или CALL MOVETO_W(wx, wy, wt) x, y - видовые координаты новой позиции типа INTEGER(2). wx, wy - оконные координаты новой позиции типа REAL(8). t - видовые координаты предшествующей позиции (переменная типа xycoord). Тип xycoord определен в модуле DFLIB: TYPE xycoord INTEGER(2) xcoord INTEGER(2) ycoord END TYPE xycoord
! x-координата ! y-координата
wt - оконные координаты предшествующей позиции (переменная типа wxycoord). Тип wxycoord определен в модуле DFLIB: TYPE wxycoord REAL(8) wx REAL(8) wy END TYPE wxycoord
! x-оконная координата ! y-оконная координата
При изменении текущей позиции изображение не меняется. Подпрограмма MOVETO изменяет текущую позицию в видовой, а MOVETO_W - в оконной системе координат. ⎯ 65 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Замечания: 1. В графическом режиме могут быть определены две позиции вывода: позиция вывода, не зависящего от шрифта текста (выводится подпрограммой OUTTEXT), устанавливаемая в текстовом окне функцией SETTEXTPOSITION, и текущая позиция графического вывода, задаваемая в видовой или оконной системе координат. 2. Текущая позиция графического вывода меняется после вывода двух графических примитивов: отрезка прямой и текста. Координаты текущей позиции можно получить, обратившись к подпрограммам CALL GETCURRENTPOSITION(t) или GETCURRENTPOSITION_W(wt) t и wt - переменные производного типа xycoord и wxycoord, содержащие соответственно видовые и оконные координаты текущей позиции. Описание типов xycoord и wxycoord приведено выше. Когда создается окно, текущая графическая позиция находится в его центре. Затем текущая позиция графического вывода может быть изменена графическими процедурами LINETO, MOVETO и OUTGTEXT.
2.10. Графические примитивы Большинство графических объектов может быть изображено при помощи небольшого числа простых графических элементов, называемых графическими примитивами. Процедуры графической библиотеки DVF, позволяющие рисовать графические примитивы, приведены в табл. 2.3. Графические элементы изображаются в заданной программистом системе координат и в заданном окне вывода. Таблица 2.3. Процедуры вывода графических элементов Процедуры
Отображаемые графические элементы
SETPIXEL, SETPIXEL_W
Пиксель (закраска пикселя)
SETPIXELS (подпрограмма)
Группа пикселей
SETPIXELRGB, SETPIXELRGB_W
Пиксель (закраска пикселя RGB-цветом)
SETPIXELSRGB (подпрограмма)
Группа RGB-пикселей
⎯ 66 ⎯
2. Вывод графических данных
LINETO, LINETO_W
Отрезок прямой
RECTANGLE, RECTANGLE_W
Прямоугольник
POLYGON, POLYGON_W
Многоугольник
ELLIPSE, ELLIPSE_W
Эллипс или окружность
ARC, ARC_W
Дуга окружности
PIE, PIE_W
Сектор окружности
FLOODFILLRGB, FLOODFILRGB_W и FLOODFILL, FLOODFIL_W
Заполнение (заливка) замкнутой области заданным цветом и по заданному шаблону
OUTGTEXT (подпрограмма)
Зависимый от шрифта (графический) текст
Для графического примитива могут быть установлены следующие характеристики (атрибуты): •
тип линии (например, сплошная, пунктирная, штрихпунктирная);
•
цвет линии;
•
способ вывода (для отрезков, прямоугольников и многоугольников);
•
шаблон и цвет заполнения (для замкнутых геометрических фигур).
Кроме того, вывод примитива может выполняться на заранее установленном фоне, т. е. после заливки окна вывода заданным цветом. Цвет фона задается функциями SETBKCOLORRGB и SETBKCOLOR. Цвет графических примитивов - функциями SETCOLORRGB и SETCOLOR. Закраска видеоокна, видового порта или окна вывода цветом фона выполняется подпрограммой CLEARSCREEN. Более подробно вопросы управления цветом рассмотрены в разд. 2.8. По умолчанию цвет фона - черный, цвет линий - белый. Функции, оканчивающиеся на _W, применимы только в оконной системе координат. И наоборот, функции, не имеющие этого окончания, используются в видовой или физической системе координат. Текущий цвет графических примитивов (кроме примитивов, выводимых SETPIXELS, SETPIXELRGB, SETPIXELSRGB) устанавливается функциями SETCOLORGB и SETCOLOR.
2.10.1. Вывод пикселей Для закраски пикселей существует 2 рода процедур:
⎯ 67 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
•
не RGB-процедуры, закрашивающие пиксель цветом, задаваемым по его номеру в цветовой палитре (разд. 2.8.2);
•
RGB-процедуры, закрашивающие пиксель любым RGB-цветом. Не RGB-функции закраски пикселя текущим цветом: result2 = SETPIXEL(x, y)
или result2 = SETPIXEL_W(wx, wy) x, y - видовые координаты пикселя - INTEGER(2). wx, wy - оконные координаты пикселя - REAL(8). Функция возвращает значение типа INTEGER(2), которое в случае успеха равно номеру цвета, в который был окрашен пиксель до выполнения функции, или -1 в случае ошибки (при задании координат за пределами окна вывода). Номер цвета вывода пикселя задается функцией SETCOLOR. Номер цвета пикселя можно узнать, обратившись к функциям result2 = GETPIXEL(x, y) или result2 = GETPIXEL_W(wx, wy) Смысл и тип параметров x, y и wx, wy такой же, какой имеют и одноименные параметры функций SETPIXEL и SETPIXEL_W. Функция возвращает значение типа INTEGER(2), которое равно номеру цвета пикселя в случае успеха или -1 в противном случае. Неудача может произойти, если указанные координаты находятся за пределами окна вывода. Соответствие между номером цвета и его значением можно изменить функциями REMAPPALETTERGB или REMAPALLPALETTERGB. Группу пикселей можно отобразить, вызвав не RGB-подпрограмму CALL SETPIXELS(n, x, y, color) n - параметр типа INTEGER(4), задающий число отображаемых пикселей и число элементов в массивах x, y и color. x, y - массивы типа INTEGER(2), содержащие x и y координаты отображаемых пикселей. Элементы x(1) и y(1) содержат координаты первого пикселя, x(2) и y(2) - второго и т. д. color - массив типа INTEGER(2), содержащий номера цветов (разд. 2.8.2) отображаемых пикселей.
⎯ 68 ⎯
2. Вывод графических данных
Номера цветов выведенной группы пикселей возвращаются параметром color подпрограммы CALL GETPIXELS(n, x, y, color) Параметры n, x, y имеют тот же смысл, что и в подпрограмме SETPIXELS. color - массив типа INTEGER(2), в который записываются номера цветов пикселей, заданных массивами x и y.
⎯ 69 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Пример: use dflib ! Режим 800*600, 16 цветов integer(4), parameter :: n = 1000 integer(2) :: x(n), y(n), color(n), i, sv = 1, k = 1 real(4) :: ran do i = 1, n sv = -sv ! Обеспечим чередование знака call random(ran) x(i) = 400 + sv * int2(100*ran) call random(ran) y(i) = 300 + sv * int2(100*ran) color(i) = k k=k+1 if(k == 16) k = 1 end do call setpixels(n, x, y, color) ! Закраска n пикселей end
Не RGB-процедуры обеспечивают доступ к загружаемой цветовой палитре, которая в лучшем случае может содержать 256 цветов (число цветов в палитре можно определить, вызвав функцию GETWINDOWCONFIG). RGB-процедуры вывода пикселя позволяют вывести пиксель любого возможного на данном графическом адаптере RGB-цвета. RGB-функции закраски пикселя: result4 = SETPIXELRGB(x, y, color) или result4 = SETPIXELRGB_W(wx, wy, color) x, y - видовые координаты типа INTEGER(2) выводимого пикселя. wx, wy - оконные координаты типа REAL(8) выводимого пикселя. color - значение типа INTEGER(4) RGB-цвета (разд. 2.8.1), в который закрашивается пиксель. Функция возвращает значение типа INTEGER(4), которое равно предыдущему значению RGB-цвета. Если пиксель находится за пределами области вывода, то он игнорируется. Значение RGB-цвета пикселя можно узнать, обратившись к функциям result4 = GETPIXELRGB(x, y) или ⎯ 70 ⎯
2. Вывод графических данных
result4 = GETPIXELRGB_w(wx, wy) Смысл и тип параметров x, y и wx, wy такой же, что и у одноименных параметров функций SETPIXELRGB и SETPIXELRGB_W. Функция возвращает значение типа INTEGER(4), которое равно RGBзначению цвета пикселя в случае успеха или -1 в противном случае (например, если координаты находятся за пределами окна вывода). Пример: use dflib integer(2) :: x, y = 100 integer(4) :: color, oldcolor, i do i = 1, 3 select case(i) case(1) color = #0000ff case(2) color = #00ff00 case(3) color = #ff0000 end select y = y + 100 do x = 100, 700, 2 oldcolor = setpixelrgb(x, y, color) enddo enddo end
! Режим 800*600, 16 цветов
! Красный цвет ! Зеленый цвет ! Синий цвет ! Вывод пикселей
Массив RGB-пикселей выводится подпрограммой CALL SETPIXELSRGB(n, x, y, color) Параметры n, x, y имеют тот же смысл, что и в подпрограмме SETPIXELS. color - массив типа INTEGER(4), содержащий RGB-значения цветов отображаемых пикселей (разд. 2.8.1). Значения RGB-цветов выведенной группы пикселей возвращаются параметром color подпрограммы CALL GETPIXELSRGB(n, x, y, color) Параметры n, x, y имеют тот же смысл, что и в подпрограмме SETPIXELS. color - массив типа INTEGER(4), в который записываются значения RGB-цветов пикселей, заданных массивами x и y.
⎯ 71 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
2.10.2. Вывод отрезка прямой линии Функции вывода отрезка прямой: result2 = LINETO(x, y) или result2 = LINETO_W(wx, wy) x, y - видовые координаты конечной точки отрезка - INTEGER(2). wx, wy - оконные координаты конечной точки отрезка - REAL(8). Функция возвращает значение типа INTEGER(2), которое, если отрезок нарисован, отлично от нуля или равно нулю в противном случае. Отрезок выводится от текущей позиции до конечной точки, задаваемой параметрами x, y или wx, wy. При выводе отрезка используется текущий цвет, задаваемый функциями SETCOLORRGB или SETCOLOR, текущий способ вывода линий, задаваемый функцией SETWRITEMODE, и текущий тип линии, задаваемый подпрограммой SETLINESTYLE (разд. 2.12). Способ вывода влияет на получаемый при выводе цвет отрезка (разд. 2.14). По умолчанию отрезки отображаются текущим цветом. Отрезки можно выводить, применяя разные маски вывода, которые задают тип линии, например штриховая линия, штрихпунктирная, пунктирная и т. д. По умолчанию выводятся сплошные линии. В случае успешного выполнения LINETO или LINETO_W текущей становится соответствующая конечной точке отрезка позиция (x, y или wx, wy). Замечание. Если область, границы которой образованы выведенными функцией LINETO отрезками прямых, заполняется посредством FLOODFILLRGB или FLOODFILL (разд. 2.13), то для отображения границ области необходимо использовать сплошной тип линии.
2.10.3. Вывод прямоугольника Вывод прямоугольника выполняется функциями result2 = RECTANGLE(control, x1, y1, x2, y2) или result2 = RECTANGLE_W(control, wx1, wy1, wx2, wy2) control - флаг заполнения прямоугольника - INTEGER(2). x1, y1 - видовые координаты левого верхнего угла прямоугольника. x2, y2 - видовые координаты правого нижнего угла прямоугольника. Тип параметров x1, y1, x2, y2 - INTEGER(2). ⎯ 72 ⎯
2. Вывод графических данных
wx1, wy1 - оконные координаты левого верхнего угла прямоугольника. wx2, wy2 - оконные координаты правого нижнего угла прямоугольника. Тип параметров wx1, wy1, wx2, wy2 - REAL(8). Функция возвращает значение типа INTEGER(2), которое отлично от нуля в случае успеха или равно нулю в случае неудачи. Границы прямоугольника рисуются с использованием текущего цвета, текущего способа вывода и текущего типа линии (разд. 2.12). Параметр control может принимать значения определенных в модуле DFLIB именованных констант $GFILLINTERIOR и $GBORDER. Если control равен $GFILLINTERIOR, то осуществляется заполнение прямоугольника с использованием текущего цвета и шаблона (маски) заполнения, устанавливаемого подпрограммой SETFILLMASK (разд. 2.13). По умолчанию осуществляется сплошное заполнение замкнутой области. Если в control установлено значение $GBORDER, то выводятся только границы прямоугольника с использованием текущих цвета, способа вывода и типа линии. Замечание. Заполнение прямоугольника цветом, отличным от цвета границы, может быть выполнено посредством функции FLOODFILLRGB или FLOODFILL, причем такое заполнение возможно, если прямоугольник выведен с использованием сплошного типа линии.
2.10.4. Вывод многоугольника Произвольной формы многоугольник выводится функциями result2 = POLYGON(control, ppoints, cpoints) или result2 = POLYGON_W(control, wppoints, cpoints) Параметр control принимает те же значения и имеет тот же смысл и тип, что и одноименный параметр функции RECTANGLE. ppoints - массив типа xycoord, состоящий из элементов, задающих ломаную линию. cpoints - параметр типа INTEGER(2), задающий число точек ломаной линии. wppoints - массив типа wxycoord из элементов, задающих ломаную линию. Типы xycoord и wxycoord определены в модуле DFLIB. Их описание дано в разд. 2.9. Функция возвращает значение типа INTEGER(2), которое отлично от нуля при успешном выполнении или равно нулю в противном случае. ⎯ 73 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Границы многоугольника вычерчиваются с использованием текущего цвета, текущего способа вывода и текущего типа линии (разд. 2.8 и 2.12). Параметр cpoints указывает число используемых точек массива ppoints или wppoints. Последняя выводимая точка массива всегда соединяется отрезком прямой с первой точкой. Замечание. Заполнение многоугольника цветом, отличным от цвета границы, может быть выполнено посредством функций FLOODFILLRGB или FLOODFILL, причем такое заполнение возможно, если многоугольник выведен с использованием сплошного типа линии. Пример. Изобразить песочные часы, заполнив их пылинками песка. use dflib ! Режим 800*600, 16 цветов integer(1) :: smask(8) = (/ 128, 4, 32, 2, 16, 8, 0, 64 /) integer(2) :: status2 type(xycoord) :: sclock(6) sclock.xcoord = (/ 300, 500, 410, 500, 300, 390 /) sclock.ycoord = (/ 10, 10, 300, 590, 590, 300 /) status2 = setcolor(14_2) call setfillmask(smask) ! Задаем шаблон заполнения status2 = polygon($gfillinterior, sclock, 6) end
Пояснение. Для имитации заполнения часов пылинками песка создаем шаблон (маску) заполнения (массив smask). Текущая маска заполнения устанавливается подпрограммой SETFILLMASK (разд. 2.13).
2.10.5. Вывод эллипса и окружности Изображение эллипса и окружности выполняется функциями result2 = ELLIPSE(control, x1, y1, x2, y2) или result2 = ELLIPSE_W(control, wx1, wy1, wx2, wy2) control - флаг типа INTEGER(2) заполнения эллипса. x1, y1 - видовые координаты типа INTEGER(2) левого верхнего ограничивающего эллипс прямоугольника. x2, y2 - видовые координаты типа INTEGER(2) правого нижнего ограничивающего эллипс прямоугольника. wx1, wy1 - оконные координаты типа REAL(8) левого верхнего ограничивающего эллипс прямоугольника. wx2, wy2 - оконные координаты типа REAL(8) правого нижнего ограничивающего эллипс прямоугольника. ⎯ 74 ⎯
угла угла угла угла
2. Вывод графических данных
Функция возвращает значение типа INTEGER(2), которое отлично от нуля при успешном выполнении и равно нулю в противном случае. Граница эллипса вычерчивается с использованием текущего цвета. Такие атрибуты, как способ вывода и тип линии, для эллипса не устанавливаются. Вывод эллипса всегда выполняется с применением сплошного типа линии. Параметр control принимает те же значения и имеет тот же смысл, что и одноименный параметр функций RECTANGLE и POLYGON. Замечание. Заполнение эллипса цветом, отличным от цвета границы, может быть выполнено посредством функций FLOODFILLRGB или FLOODFILL.
2.10.6. Вывод дуги эллипса и окружности Дуга эллипса или окружности выводится функциями result2 = ARC(x1, y1, x2, y2, x3, y3, x4, y4) или result2 = ARC_W(wx1, wy1, wx2, wy2, wx3, wy3, wx4, wy4) x1, y1 - видовые координаты типа INTEGER(2) левого верхнего угла ограничивающего прямоугольника. x2, y2 - видовые координаты типа INTEGER(2) правого нижнего угла ограничивающего прямоугольника. x3, y3 - начальный вектор - INTEGER(2). x4, y4 - конечный вектор - INTEGER(2). wx1, wy1 - оконные координаты типа REAL(8) левого верхнего угла ограничивающего прямоугольника. wx2, wy2 - оконные координаты типа REAL(8) правого нижнего угла ограничивающего прямоугольника. wx3, wy3 - начальный вектор - REAL(8). wx4, wy4 - конечный вектор - REAL(8). Функция возвращает значение типа INTEGER(2), которое отлично от нуля при успешном выполнении или равно нулю в случае неудачи. Центр дуги находится в центре ограничивающего прямоугольника (рис. 2.4). Начальный вектор - это вектор, проходящий через центр дуги и точку x3, y3 (wx3, wy3) (рис. 2.4). Конечный вектор - это вектор, проходящий через центр дуги и точку x4, y4 (wx4, wy4) (рис. 2.4). ⎯ 75 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
0
x1 = x4
x3
x2
y1 = y3
y4 y2
Рис. 2.4. Параметры функции ARC в видовой системе координат
Дуга вычерчивается с использованием текущего цвета. Такие атрибуты, как способ вывода и тип линии, для дуги не устанавливаются. Дуга рисуется как часть эллипса от точки пересечения эллипса с начальным вектором до точки пересечения эллипса с конечным вектором. Координаты точек пересечения эллипса с начальным и конечным векторами возвращаются функцией GETARCINFO. Вывод дуги выполняется против часовой стрелки.
2.10.7. Вывод сектора Сектор выводится функциями result2 = PIE(control, x1, y1, x2, y2, x3, y3, x4, y4) или result2 = PIE_W(control, wx1, wy1, wx2, wy2, wx3, wy3, wx4, wy4) Параметр control принимает те же значения и имеет тот же смысл и тип, что и одноименный параметр функций RECTANGLE, POLYGON и ELLIPSE. Смысл и тип параметров x1, y1, x2, y2, x3, y3, x4, y4, wx1, wy1, wx2, wy2, wx3, wy3, wx4, wy4 такой же, что и у одноименных параметров функции ARC. Функция возвращает значение типа INTEGER(2), которое отлично от нуля при успешном выполнении или равно нулю в случае неудачи. В отличие от дуги конечные точки сектора соединены с его центром. Центр сектора, как и центр дуги, совпадает с центром ограничивающего прямоугольника. Таким образом, сектор является замкнутой областью и, следовательно, может быть заполнен текущим цветом с использованием текущей маски заполнения. Граница сектора выводится против часовой стрелки с использованием текущего цвета. Так же как и для дуги, способ вывода и тип линии для сектора не устанавливаются. ⎯ 76 ⎯
2. Вывод графических данных
Замечание. Заполнение сектора цветом, отличным от цвета границы, может быть выполнено посредством функций FLOODFILLRGB или FLOODFILL.
2.10.8. Координаты конечных точек дуги и сектора Координаты конечных точек последней выведенной дуги или сектора можно определить, обратившись к функции result2 = GETARCINFO(pstart, pend, ppaint) pstart - координаты начальной точки дуги - тип xycoord. pend - координаты конечной точки дуги - тип xycoord. ppaint - координаты точки начала заливки - тип xycoord. Тип xycoord определен в модуле DFLIB и приведен в разд. 2.6. Функция возвращает значение типа INTEGER(2), которое отлично от нуля, если вывод дуги или сектора был выполнен успешно (с момента последней очистки окна, или выбора видового порта, или установки нового графического видеорежима), или которое равно нулю в случае неудачи. При успешном завершении функция GETARCINFO заносит в параметры pstart и pend (переменные типа xycoord) видовые координаты начальной и конечной точки дуги. Дополнительно в параметр ppaint типа xycoord устанавливаются координаты, которые можно использовать для задания начальной точки заполнения сектора. Эта информация полезна, если необходимо произвести заполнение сектора цветом, который отличается от цвета границ. Последовательность вывода сектора может быть, например, такой: вывести незаполненный сектор; вызвать функцию GETARCINFO; изменить текущий цвет, применив функцию SETCOLORRGB; вызвать функцию FLOODFILLRGB, использовав установленные в ppaint координаты в качестве координат начальной точки заполнения сектора.
2.10.9. Пример вывода графических примитивов Задать в видеоокне 4 окна вывода (рис. 2.5) и вывести: в первом окне дугу, во втором - сектор, и выполнить затем его заливку, в третьем - эллипс, в четвертом - заполненный прямоугольник.
⎯ 77 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Рис. 2.5. Расположение геометрических фигур в видеоокне
use dflib integer(2) :: s2, XE = 800, YE = 600 type(xycoord) :: ps, pe, pp
! Режим 800*600, 16 цветов
! Задание первого видового порта и заливка окна вывода цветом фона call preps(int2(0), int2(0), int2(XE/2), int2(YE/2), 1_2, 14_2) ! Вывод дуги s2 = arc(XE/8, YE/8, 3*XE/8, 3*YE/8, XE/4, YE/8, XE/8, YE/4) ! Задание второго видового порта и заливка окна вывода цветом фона call preps(int2(XE/2), int2(0), int2(XE), int2(YE/2), 7_2, 11_2) ! Вывод сектора s2 = pie($gborder,XE/8,YE/8,3*XE/8,3*YE/8,3*XE/8,YE/8,3*XE/8,3*YE/8) s2 = getarcinfo(ps, pe, pp) ! pp - начальная точка заливки s2 = setcolor(8_2) ! Цвет заливки темно-серый s2=floodfill(pp%xcoord, pp%ycoord, 11_2) ! Заливка сектора ! Задание третьего видового порта и заливка окна вывода цветом фона call preps(int2(0), int2(YE/2), int2(XE/2), int2(YE), 7_2, 11_2) ! Вывод эллипса s2 = ellipse($gborder, XE/8, YE/8, 3*XE/8, 3*YE/8) ! Задание четвертого видового порта и заливка окна вывода цветом фона call preps(int2(XE/2), int2(YE/2), int2(XE), int2(YE), 1_2, 14_2) ! Вывод закрашенного прямоугольника s2 = rectangle($gfillinterior, XE/8, YE/8, 3*XE/8, 3*YE/8) contains subroutine preps(x1, y1, x2, y2, c1, c2) integer(2) :: x1, y1, x2, y2, status2 integer(2) :: c1, c2 call setviewport(x1, y1, x2, y2) status2 = setcolor(c1) status2 = floodfill(2_2, 2_2, 1_2) status2 = setcolor(c2) end subroutine
! Окно вывода ! Цвет заливки ! Заливка окна ! Цвет объекта
⎯ 78 ⎯
2. Вывод графических данных
end
Пояснения: 1. Заливка выполняется методом заполнения замкнутой области с затравкой * . Для заливки внутри области указывается произвольная точка и цвет границы области. Если затравочная точка указана вне замкнутой области, то будут залиты все пиксели, находящиеся за пределами области. Метод применим также и для заливки областей с отверстиями. Главное, чтобы границы области и отверстий имели один и тот же цвет. 2. Поскольку первоначально окно не содержит графических объектов, то для его заливки можно указать любой цвет границы области (третий параметр функции FLOODFILL).
2.11. Вывод текста В графическом режиме можно выводить 2 вида текста: с использованием и без использования шрифтов. Последний вид текста может быть выведен также и в текстовом режиме.
2.11.1. Вывод текста без использования шрифтов Выводимый без использования шрифта текст имеет постоянную для заданного видеорежима высоту и ширину, определяемые размером знакоместа. Каждый символ выводится в некоторую позицию окна (знакоместо). Число таких позиций зависит от видеорежима и может быть определено в результате вызова функции GETWINDOWCONFIG. Результаты устанавливаются в компоненты переменной, например t, типа windowconfig: t.numtextcols и t.numtextrows (разд. 2.5). Единицами измерения координаты позиции вывода текста являются ряд (строка) и столбец видеоокна (текстового окна), на пересечении которых позиция расположена. Позиция, расположенная в левом верхнем углу видеоокна или текущего окна, имеет координаты (1, 1). В любом видеорежиме текст может быть выведен подпрограммой CALL OUTTEXT(text) text - строка выводимого текста. Строка выводится вместе с хвостовыми пробелами. * Стартовая точка заполнения называется затравочной точкой, а используемый алгоритм - алгоритмом заполнения области с затравкой.
⎯ 79 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Вывод текста начинается от текущей позиции. После вывода строки текущей становится первая следующая за строкой позиция. Подпрограмма OUTTEXT выводит только строки символов, поэтому вывод числовых или логических данных подпрограммой возможен только после записи в строку их символьного представления. Вывод текста посредством OUTTEXT, WRITE и PRINT может быть ограничен окном, задание которого выполняется подпрограммой CALL SETTEXTWINDOW(r1, c1, r2, c2) r1, c1, - параметры типа INTEGER(2), определяющие левый верхний ряд и столбец окна текста. r2, c2 - параметры типа INTEGER(2), определяющие правый нижний ряд и столбец окна текста. Координаты окна r1, c1, r2, c2 задаются относительно верхнего левого угла видеоокна. После задания текстового окна отсчет текущей позиции выполняется уже относительно его верхнего левого угла. Текст в окне выводится начиная с верхнего левого угла окна. Если окно заполнено полностью, то последующие строки пишутся на месте последней строки. Замечание. Подпрограмма SETTEXTWINDOW не оказывает влияния на работу подпрограммы OUTGTEXT, осуществляющей вывод графического зависимого от шрифта текста. В заданном текстовом окне текущая позиция начала вывода текста посредством OUTTEXT, WRITE и PRINT изменяется подпрограммой CALL SETTEXTPOSITION(row, column, t) row, column - параметры типа INTEGER(2), задающие новую позицию вывода текста. Позиция вывода текста задается относительно левого верхнего угла окна. t - переменная типа rccoord, содержащая координаты прежней позиции вывода текста. Тип rccoord определен в модуле DFLIB: type rccoord integer(2) row integer(2) col end type rccoord
! Координаты ряда позиции ! Координаты столбца позиции
Текстовая позиция с координатами (1, 1) определяет левый верхний угол текстового окна. Вывод текста посредством OUTTEXT, WRITE и PRINT начинается от текущей текстовой позиции. Позиция вывода зависящего от шрифта текста не зависит от текущей текстовой позиции, а определяется текущей позицией графического вывода, возвращаемой подпрограммой GETCURRENTPOSITION. ⎯ 80 ⎯
2. Вывод графических данных
Замечание. При совместном использовании подпрограмм SETTEXTPOSITION, OUTTEXT и оператора WRITE (PRINT) следует подавлять вывод оператором WRITE (PRINT) символов конца строки и новой строки (CHAR(13) и CHAR(10)), используя для этого преобразования \ или $ или опцию ADVANCE = 'NO'. Текущая позиция вывода текста возвращается подпрограммой CALL GETTEXTPOSITION(t) t - переменная типа rccoord. Текстовое окно можно заполнить фоном. Цвет фона задается функциями SETBKCOLORRGB и SETBKCOLOR. Закраска видеоокна или окна вывода цветом фона выполняется подпрограммой CLEARSCREEN (разд. 2.7). Цвет выводимого посредством OUTTEXT, WRITE и PRINT текста устанавливается функциями SETTEXTCOLORRGB и SETTEXTCOLOR. В ранее определенном текстовом окне можно организовать прокрутку (перемещение вверх или вниз) выведенного текста, применив подпрограмму CALL SCROLLTEXTWINDOW(rows) rows - параметр типа INTEGER(2), задающий число строк прокрутки текста, т. е. число, определяющее, на сколько строк видеоокна переместится вверх или вниз заданный текст. Если rows > 0, то организовывается прокрутка вверх. Если rows < 0 - вниз. Если текстовое окно не задано, то подпрограмма организовывает прокрутку текста на всем видеоокне. Перемещенные в результате прокрутки за пределы окна данные теряются. По умолчанию выводимый OUTTEXT текст, если его длина превышает ширину окна, переносится на следующую строку. Однако обратившись к функции result2 = WRAPON(option) можно, задав параметр option, равный $GWRAPOFF, установить режим, при котором не уместившиеся в окне символы отсекаются. Можно затем вернуться к режиму переноса символов. Для этого нужно вызвать WRAPON с option, равным $GWRAPON. Тип option - INTEGER(2). Функция возвращает значение типа INTEGER(2), которое равно предыдущему значению option. WRAPON не оказывает влияния на работу подпрограммы OUTGTEXT.
⎯ 81 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
2.11.2. Вывод зависимого от шрифта текста Вывод текста с использованием шрифтов выполняется в графическом видеорежиме. Использование шрифтов позволяет выводить текст разной высоты под различными углами наклона литер и самого текста. Для вывода использующего шрифты текста следует провести подготовительные операции: выполнить инициализацию шрифтов и установить текущий шрифт. Инициализация шрифтов выполняется функцией result2 = INITIALIZEFONTS( ) Функция возвращает значение типа INTEGER(2), равное числу инициализированных шрифтов. Шрифты должны быть инициализированы до выполнения использующих шрифты функций (GETFONTINFO, GETGTEXTEXTENT, SETFONT, OUTGTEXT). Установленный шрифт оказывает влияние только на вывод, выполняемый подпрограммой OUTGTEXT, и не влияет на форму вывода операторов WRITE, PRINT и подпрограммы OUTTEXT. Инициализация шрифтов должна выполняться для каждого окна до вызова функции SETFONT. Установка текущего шрифта вывода текста и характеристик шрифта выполняется функцией result2 = SETFONT(options) options - параметр символьного типа, задающий имя шрифта и его характеристики. Функция возвращает номер шрифта при успешном выполнении либо -1 в случае ошибки. Тип результата - INTEGER(2). Функция загружает и делает текущим указанный параметром options шрифт с заданными характеристиками. Параметр options содержит приведенные ниже коды характеристик шрифта. Позиция задания кодов и регистр не имеют значения. t'fontname' - тип шрифта, например: "t'courier'", "t'helv'", "t'script'", "t'tms rmn'", "t'modern'", "t'roman'"... hy - высота символа в пикселях (y - число пикселей). wx - ширина символа в пикселях (x - число пикселей). f - выбираются только фиксированные шрифты (каждый символ имеет одинаковую ширину).
⎯ 82 ⎯
2. Вывод графических данных
p - выбираются только пропорциональные шрифты (ширина символа зависит от его насыщенности, например символы j и a имеют в случае пропорционального шрифта разную ширину). Опции f и p не могут быть использованы одновременно. v - выбор векторного шрифта, например: Roman, Modern, Script (шрифт задается как множество отрезков прямых). r - выбор растрового (битового) шрифта, например: Courier, Helvetica, Palatino (шрифт задается как множество пикселей). Опции v и r не могут быть использованы одновременно. e - вывод текста в формате жирного шрифта. Этот параметр игнорируется, когда вывод в формате жирного шрифта невозможен. u - вывод подчеркнутого текста. Параметр игнорируется, если шрифт не допускает подчеркивания. i - вывод курсива (наклонного текста). Параметр игнорируется, если шрифт не поддерживает курсива. b - выбор шрифта, наиболее полно удовлетворяющего заданным параметрам. nx - выбор шрифта, для которого число x меньше или равно возвращаемого функцией INTIALIZEFONTS значения. Эта опция не используется совместно с другими вышеприведенными опциями. Число задаваемых опций произвольно (кроме случая использования опции nx, которая применяется отдельно). Если заданы взаимно исключающие опции, например f и p или r и v, то они игнорируются. Если опция b задана и по крайней мере один шрифт инициализирован, то SETFONT всегда устанавливает шрифт и возвращает 0, что означает удачное завершение. Приоритеты опций при выборе наиболее подходящего шрифта таковы (приведены в порядке убывания): •
высота символа (опция hy);
•
тип шрифта;
•
ширина символа (опция wx);
•
фиксированный или пропорциональный шрифт (опции f и p).
Если заданы несуществующие высота или ширина шрифта и указана опция b, то функция SETFONT выберет ближайший подходящий шрифт. При этом меньшая высота имеет приоритет над большей. Если заданы несуществующие высота или ширина, то в случае векторного шрифта (задана опция v или задан тип векторного шрифта) ⎯ 83 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
SETFONT выполнит масштабирование шрифта. Битовые шрифты не масштабируются. Если задана опция nx, то SETFONT проигнорирует все иные параметры и установит шрифт, номер которого соответствует x. Если задана высота шрифта и отсутствует его ширина (или наоборот), то SETFONT вычислит пропущенное значение, исходя из пропорций между шириной и высотой. Характеристики текущего шрифта возвращаются функцией result2 = GETFONTINFO(font) font - переменная типа fontinfo. Тип задан в модуле DFLIB: TYPE fontinfo INTEGER(4) type INTEGER(4) ascent INTEGER(4) pixwidth INTEGER(4) pixheight INTEGER(4) avgwidth CHARACTER(32) xfacename LOGICAL(1) italic LOGICAL(1) emphasized LOGICAL(1) underline подчеркнутым END TYPE fontinfo
! 1 - TrueType, 0 - BitMap ! Расстояние в пикселях от базовой линии ! до верхушки символов ! Ширина символа в пикселях ! (0 для пропорционального шрифта) ! Высота символа в пикселях ! Средняя ширина символа в пикселях ! Имя шрифта ! .TRUE., если текущий шрифт - курсив ! .TRUE., если текущий шрифт выводится ! в жирном формате ! .TRUE., если текст выводится
Функция возвращает значение типа INTEGER(2), равное нулю в случае успеха, и -1 при неудаче. После инициализации и установки шрифта можно вывести строку текста, применив подпрограмму CALL OUTGTEXT(text) text - строка выводимого текста. Строка выводится вместе с хвостовыми пробелами. Вывод текста начинается от текущей позиции графического вывода. Текст выводится с использованием текущего шрифта и цвета. После вывода строки текущей становится первая следующая за строкой позиция. Изменение текущей позиции вывода графического текста выполняется подпрограммой MOVETO. Также текущая позиция графического вывода меняется после работы функции LINETO. Текущая позиция графического вывода возвращается подпрограммой GETCURRENTPOSITION. ⎯ 84 ⎯
2. Вывод графических данных
При работе с подпрограммой OUTGTEXT текущий цвет текста устанавливается функциями SETCOLORRGB и SETCOLOR. Длина выводимой посредством OUTGTEXT строки текста в пикселях (включая и хвостовые пробелы) возвращается функцией result2 = GETGTEXTEXTENT(text) text - символьная переменная, содержащая строку текста, длина которой должна быть определена. Функция возвращает длину текста text в пикселях. В случае неудачи (если шрифты не были установлены посредством INITIALIZEFONTS) функция возвращает -1. Тип возвращаемого значения - INTEGER(2). Перед обращением к GETGTEXTEXTENT шрифты должны быть инициализированы. Длина текста вычисляется с учетом выбранного шрифта. Ориентация выводимого подпрограммой OUTGTEXT текста может быть изменена в результате вызова подпрограммы CALL SETGTEXTROTATION(degrees) degrees - параметр типа INTEGER(4), задающий угол поворота текста (его ориентацию) в десятках градусов (число градусов, умноженное на 10). Отсчет угла поворота текста выполняется против часовой стрелки. По умолчанию текст выводится в горизонтальной ориентации (degrees равен нулю). Если параметр degrees равен 900, то выводится текст, повернутый против часовой стрелки на 90°. Задание 1800 (180°) означает вывод справа налево перевернутого текста и т. д. При задании значения большего 3600 (360°) подпрограмма берет значение MODULO(Заданное_значение_degrees, 3600) Минимальное изменение ориентации текста - 1°. Текущее значение ориентации текста возвращается функцией result4 = GETGTEXTROTATION( ) Функция возвращает число типа INTEGER(4), равное установленной подпрограммой SETGTEXTROTATION ориентации текста в десятках градусов (число градусов, умноженное на 10). Пример. Вывести имена шрифтов в различных ориентациях. use dflib ! Режим 800*600, 16 цветов integer(4), parameter :: nfonts = 6 integer(2) :: status2, x, y integer(4) :: ifont character(11) :: face(nfonts), options(nfonts)
⎯ 85 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
character(20) :: list type(xycoord) :: xy type(fontinfo) :: fi data face /"courier", "helvetica", "times roman", "modern", "script", "roman"/ data options /"t'courier'", "t'helv'", "t'tms rmn'", "t'modern'", "t'script'", "t'roman'"/ ! Инициализация шрифтов if(initializefonts() < 0) stop 'Шрифты не найдены' status2 = setbkcolorrgb(#ffffff) ! Цвет фона - ярко-белый ! Вывод имени шрифта в различных ориентациях do ifont = 1, nfonts ! Строка опций для выбора шрифта list = trim(options(ifont)) // 'h30w24b' call clearscreen($gclearscreen) if(setfont(list) .ge. 0) then ! Если установлен текущий шрифт ! разместим текст по центру видеоокна. Найдем его длину и высоту x = (800 - getgtextextent(face(ifont)) / 2) / 2 if(getfontinfo(fi) .ne. 0) then call outtext( 'Нет данных о шрифте' ) call sleepqq(1500) ! Задержка cycle end if y = (600 + fi.ascent)/2 call moveto(x, y, xy) status2 = setcolor(int2(ifont)) call shotext(0) ! Горизонтальный текст call shotext(900) ! Вертикальный текст call shotext(1800) ! Повернутый на 180° текст call shotext(2700) ! Поворот на 270° else call outtext('Шрифт не найден') end if call sleepqq(1500) ! Задержка end do contains subroutine shotext(degrees) integer(4) :: degrees call setgtextrotation(degrees) call moveto(x, y, xy) call outgtext(face(ifont)) end subroutine end
! Позиция вывода текста ! в каждой ориентации одна и та же
⎯ 86 ⎯
2. Вывод графических данных
2.12. Управление типом линий По умолчанию графические примитивы изображаются сплошными линиями. Однако при необходимости тип линии можно изменить, применив подпрограмму CALL SETLINESTYLE(mask) mask - маска, задающая тип линии (параметр типа INTEGER(2)). Маска представляет собой 16-битовое число, в котором каждый бит отображает пиксель линии. Если бит содержит 1, то соответствующий пиксель линии закрашивается цветом, определяемым текущим цветом и установленным способом вывода линий. Последнее выполняется функцией SETWRITEMODE. Если бит содержит 0, то соответствующий пиксель линии не изменяется. Образец воспроизводится по всей длине линии. Устанавливаемая по умолчанию маска равна #FFFF, что соответствует сплошной линии. Подпрограмма SETLINESTYLE оказывает влияние на способ отображения отрезков прямых, прямоугольников и многоугольников (функции LINETO, LINETO_W, POLYGON, POLYGON_W, RECTANGLE, RECTANGLE_W) и не влияет на работу функций ARC, ARC_W, ELLIPSE, ELLIPSE_W и PIE, PIE_W. Пример. На белом фоне нарисовать параллельные линии разных типов: сплошную, из длинных и коротких штрихов, из точек. Используем для задания типа линии шестнадцатеричное представление констант. Так, для перечисленных типов значения констант таковы (в скобках даны двоичные представления констант): #FFFF -
сплошная линия
(2#1111111111111111);
#FF00 -
длинные штрихи
(2#1111111100000000);
#F0F0 -
короткие штрихи
(2#1111000011110000);
#CCCC -
точки
(2#1100110011001100).
use dflib ! Режим 800*600, 16 цветов integer(2) :: k, dy = 75, status2 integer(2) :: ltype(4) = (/ #FFFF, #FF00, #F0F0, #CCCC /) integer(4) :: status4 status4 = setbkcolorrgb(#ffffff) ! Белый фон call clearscreen($gclearscreen) do k = 1, 4 ! Вывод линий call drawline( )
⎯ 87 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
end do contains subroutine drawline( ) type(xycoord) xy status2 = setcolor(k) ! Текущий цвет call setlinestyle(ltype(k)) ! Текущий тип линии call moveto(100_2, int2(100 + dy*k), xy) ! Начало отрезка status2 = lineto(700_2, 100_2 + dy*k) ! Рисуем отрезок end subroutine end
Линии, как, впрочем, и иные графические примитивы, могут выводиться поверх существующего изображения. Однако для линий, прямоугольников и многоугольников можно регулировать способ их взаимодействия с существующим изображением. Это выполняется функцией result2 = SETWRITEMODE(wmode) wmode - параметр типа INTEGER(2), задающий способ взаимодействия с существующим изображением. Функция при успешном выполнении возвращает значение типа INTEGER(2), равное величине предыдущего способа, или -1 при неудаче. Функция setwritemode оказывает влияние на работу функций lineto, polygon и rectangle. Возможные значения параметра wmode: $GAND, $GOR, $GPRESET, $GPSET и $GXOR. Эти именованные константы определены в модуле DFLIB. Их смысл разъяснен в разд. 2.14. Здесь же ограничимся описанием наиболее часто используемых констант. $GPRESET - существующее изображение замещается выводимым; при этом цвет каждого обновляемого пикселя инвертируется. Так, при выводе фиолетового пикселя его цвет после инверсии станет зеленым. $GPSET - существующий цвет пикселей замещается цветом пикселей выводимой линии без каких-либо преобразований цвета. По умолчанию устанавливается последний способ вывода линий. Функция типа INTEGER(2) result2 = GETLINESTYLE( ) возвращает значение маски для текущего типа линии.
⎯ 88 ⎯
2. Вывод графических данных
2.13. Заполнение замкнутых областей Замкнутые области могут быть образованы одним графическим примитивом (прямоугольник, эллипс, сектор и многоугольник) или сочетанием нескольких, например дуга и отрезки прямых. Мы уже видели, что графические примитивы: прямоугольник, многоугольник, эллипс и сектор - могут быть заполнены текущим цветом в процессе их построения, если управляющий заполнением параметр control равен $GFILLINTERIOR. При этом цвет заливки и границы примитива совпадают. Помимо этой возможности, любую замкнутую область, а не только отдельный примитив, можно заполнить текущим, установленным функцией SETCOLOR цветом, применив RGB или не RGB-функции result4 = FLOODFILLRGB(x, y, color) или result4 = FLOODFILLRGB_W(wx, wy, color) или result2 = FLOODFILL(x, y, bcolor) или result2 = FLOODFILL_W(wx, wy, bcolor) x, y - параметры типа INTEGER(2), задающие видовые координаты стартовой точки заполнения. wx, wy - параметры типа REAL(8), задающие оконные координаты стартовой точки заполнения. color - параметр типа INTEGER(4), задающий RGB-цвет границы. bcolor - параметр типа INTEGER(2), задающий номер цвета границы. Функция возвращает отличное от нуля значение при успешном выполнении или 0 в противном случае. Последнее происходит, если заполнение не может быть завершено, например если стартовый пиксель (пиксель, в котором расположена стартовая точка) имеет цвет color (bcolor) или если стартовая точка лежит за пределами окна вывода. Возвращаемое функцией FLOODFILL значение имеет тип INTEGER(2), а функцией FLOODFILLRGB - INTEGER(4). При заполнении области используются текущий цвет и текущая маска заполнения (задается подпрограммой SETFILLMASK). Если стартовая точка находится внутри области, то заполняется сама область. Если стартовая точка находится вне области, то заполняется ⎯ 89 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
пространство вне области. При задании стартовой точки на границе области заполнение не производится. Заполнение осуществляется во всех направлениях, прекращаясь при достижении пикселей, окрашенных в цвет color (bcolor) - цвет границы области. Замечание. Заполнение области будет выполнено успешно, если область не имеет разрывов и для отображения ее границ использован сплошной тип линии. Пример. Выполнить заливку области, расположенной между прямоугольником и размещенным внутри его эллипсом. Такая задача может быть решена с применением функций FLOODFILL или FLOODFILLRGB, если границы эллипса и прямоугольника имеют одинаковый цвет и стартовая точка расположена между этими границами. Заливку области и границы фигур выполним разным цветом. use dflib ! Режим 800*600 integer(2) :: status2 integer(4) :: status4 status4 = setbkcolorrgb(#ff0000) ! Цвет фона - синий call clearscreen($gclearscreen) ! Окраска экрана в цвет фона status4 = setcolorrgb(#00ffff) ! Желтый цвет status2 = ellipse($gborder, 300, 250, 500, 350) status2 = rectangle($gborder, 200, 200, 600, 400) status4 = setcolorrgb(#008000) ! Светло-зеленый цвет status4 = floodfillrgb(201, 201, #00ffff) ! Граница желтого цвета end
По умолчанию в текущий цвет окрашиваются все пиксели области. Однако можно задать образец (шаблон) заливки, который в результате применения функций FLOODFILL или FLOODFILLRGB будет воспроизведен по всей области заполнения. Задание шаблона выполняется подпрограммой CALL SETFILLMASK(mask) mask - массив битов шаблона типа INTEGER(1). Массив содержит 8 целых чисел. Каждый бит числа представляет пиксель экрана. Фактически при задании шаблона мы имеем дело с массивов 8 * 8 бит, пример которого приведен в табл. 2.4.
⎯ 90 ⎯
2. Вывод графических данных
Таблица 2.4. Пример шаблона заполнения Номер элемента массива
Представление элемента массива двоичное
шестнадцатеричное
1
10010011
#93
2
11001001
#C9
3
01100100
#64
4
10110010
#B2
5
01011001
#59
6
00101100
#2C
7
10010110
#96
8
01001011
#4B
В программе при задании может быть использовано любое представление шаблона: двоичное, шестнадцатеричное, десятичное... Наличие в бите единицы приведет к заполнению соответствующего пикселя текущим цветом. Бит с нулем оставит пиксель без изменения. Пример. Выполнить штриховку области между эллипсом и прямоугольником. Для штриховки используем матрицу битов, имеющую единицы на главной и двух соседних побочных диагоналях. use dflib ! Режим 800*600, 16 цветов integer(1) :: hatch(8) = (/ 2#00000111, 2#00001110, 2#00011100, & 2#00111000, 2#01110000, 2#11100000,& 2#11000001, 2#10000011 /) integer(2) :: status2 integer(4) :: status4 status4 = setbkcolorrgb(#ffffff) ! Цвет фона - ярко-белый call clearscreen($gclearscreen) ! Окраска экрана в цвет фона status2 = setcolor(6_2) ! Коричневый цвет status2 = ellipse($gborder, 300, 250, 500, 350) status2 = rectangle($gborder, 200, 200, 600, 400) status2 = setcolor(7_2) ! Светло-серый цвет call setfillmask(hatch) ! Маска-штриховка status2 = floodfill(201_2, 201_2, 6_2) ! Штриховка области end
⎯ 91 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Замечание. Если совпадают цвет границы области и текущий цвет заполнения и если применяется шаблон, имеющий нулевые биты, то могут возникать ошибки заполнения.
2.14. Передача образов Под образом будем понимать ограниченное прямоугольной областью изображение. Образ может быть сохранен в оперативной или во внешней памяти, а также загружен из памяти и размещен в текущем окне вывода.
2.14.1. Обмен с оперативной памятью Запись образа в оперативную память выполняется подпрограммами CALL GETIMAGE(x1, y1, x2, y2, image) или CALL GETIMAGE_W(wx1, wy1, wx2, wy2, image) x1, y1 - координаты левого верхнего угла ограничивающего образ прямоугольника. x2, y2 - координаты правого нижнего угла ограничивающего образ прямоугольника. Тип параметров x1, y1, x2, y2 - INTEGER(2). wx1, wy1, wx2, wy2 - имеют тот же смысл. Тип параметров REAL(8). image - массив типа INTEGER(1), в котором сохраняется образ. Подпрограмма сохраняет ограниченный прямоугольником образ в массиве-буфере image. GETIMAGE используется в видовой системе координат, а GETIMAGE_W - в оконной. Буфер должен иметь достаточные для хранения образа размеры. Размеры образа возвращаются функцией IMAGESIZE или вычисляются по формуле, приведенной при описании этой функции. Замечание. Образ сохраняется в одномерном, типа INTEGER(1), массиве-буфере. Буфер может быть задан в программе как массив-ссылка или как размещаемый массив. Функции типа INTEGER(4) result4 = IMAGESIZE(x1, y1, x2, y2) или result4 = IMAGESIZE_W(wx1, wy1, wx2, wy2) возвращают число байт, необходимых для сохранения образа в памяти. ⎯ 92 ⎯
2. Вывод графических данных
x1, y1 - координаты левого верхнего угла образа. x2, y2 - координаты правого нижнего угла образа. Тип параметров x1, y1, x2, y2 - INTEGER(2). wx1, wy1, wx2, wy2 - имеют тот же смысл. Тип параметров - REAL(8). Функция IMAGESIZE используется в видовой системе координат, а IMAGESIZE_W - в оконной. В видовой системе координат размер size образа определяется по формулам: xwid = ABS(x1 - x2) + 1, ywid = ABS(y1 - y2) + 1, size = 4 + INT((xwid * bits_per_pixel + 7) / 8) * ywid. Значение bits_per_pixel возвращается функцией GETWINDOWCONFIG как bitsperpixel (разд. 2.5). Загрузка и размещение в окне ранее сохраненного в оперативной памяти образа выполняется подпрограммами CALL PUTIMAGE(x, y, image, action) или CALL PUTIMAGE_W(wx, wy, image, action) x, y - параметры типа INTEGER(2), задающие видовые координаты левого верхнего угла образа. wx, wy - параметры типа REAL(8), задающие оконные координаты левого верхнего угла образа. image - массив типа INTEGER(1), содержащий образ. action - параметр типа INTEGER(2), задающий вид взаимодействия с существующим на экране изображением. Параметр action может принимать значение одной из определенных в модуле DFLIB именованных констант: $GAND - результирующий образ является логическим И двух образов: совпадающие точки существующего и передаваемого образов, имеющие одинаковый цвет, этот цвет сохраняют; точки, имеющие разный цвет, объединяются посредством логического И. Например, если пиксель экрана имеет бирюзовый цвет, а соответствующий пиксель образа - фиолетовый, то результирующий пиксель будет синего цвета. Это становится очевидным при рассмотрении RGB-битов названных цветов: AND =
11111111 11111111 00000000 11111111 00000000 11111111 11111111 00000000 00000000
(Бирюзовый (Фиолетовый (Синий -
⎯ 93 ⎯
#FFFF00) #FF00FF) #FF0000)
О. В. Бартеньев. Visual Fortran: новые возможности
$GOR - загружаемый образ накладывается на существующий. Продолжая пример, получим при наложении бирюзового и фиолетового белый цвет: OR =
11111111 11111111 00000000 11111111 00000000 11111111 11111111 11111111 11111111
(Бирюзовый (Фиолетовый (Белый -
#FFFF00) #FF00FF) #FFFFFF)
$GPRESET - существующий образ замещается загружаемым; при этом цвет каждого загружаемого пикселя инвертируется. Так, при загрузке фиолетового пикселя его цвет после инверсии станет зеленым: ИНВЕРСИЯ
11111111 00000000 11111111 00000000 11111111 00000000
(Фиолетовый (Зеленый -
#FF00FF) #00FF00)
$GPSET - существующий образ замещается загружаемым без какихлибо преобразований цвета. $GXOR - обеспечивает следующее: когда образ дважды накладывается на изображение, то оно остается неизменным. Это позволяет перемещать образ, не изменяя изображения; режим $GXOR часто используется в программах анимации: XOR = XOR =
11111111 11111111 00000000 11111111 00000000 11111111 00000000 11111111 11111111 11111111 00000000 11111111 11111111 11111111 00000000
(Бирюзовый (Фиолетовый (Желтый (Фиолетовый (Бирюзовый -
#FFFF00) #FF00FF) #00FFFF) #FF00FF) #FFFF00)
Замечания: 1. Если вставляемый образ выходит за пределы окна вывода, то он не отображается и экран остается без изменений. 2. В рассмотренных режимах вставки образа взаимодействуют цвета, которые пиксели получили в результате изображения графических примитивов. Пиксели, окрашенные в цвет фона, во взаимодействии не участвуют. Пример. Проиллюстрировать все режимы загрузки фиолетового образа в видеоокно бирюзового цвета. use dflib ! Режим 800*600, 16 цветов integer(1), allocatable :: buffer(:) integer(2) :: k, status2 ! ix, iy - размер прямоугольника, ограничивающего образ integer(2) :: ix = 160, iy = 120 integer(2) :: alist(5) = (/ $gand, $gor, $gpreset, $gpset, $gxor /)
⎯ 94 ⎯
2. Вывод графических данных
integer(4) :: imsize imsize = imagesize(0, 0, ix, iy) ! Размер образа allocate(buffer(imsize)) ! Размещение массива status4 = setcolorrgb(#ff00ff) ! Цвет образа фиолетовый status2 = rectangle($gfillinterior, 0, 0, ix, iy)! Образ call getimage(0, 0, ix, iy, buffer) ! Сохраняем образ status4 = setcolorrgb(#ffff00) ! Цвет видеоокна - бирюзовый status2 = rectangle($gfillinterior, 0, 0, 799, 599) do k = 1, 5 ! Вставка образа в бирюзовый экран call putimage(ix*(k-1), iy*(k-1), buffer, alist(k)) call sleepqq(1500) ! Задержка if(k == 5) then ! Повторная вставка образа в режиме $GXOR call putimage(ix*(k-1), iy*(k-1), buffer, alist(k)) call sleepqq(1500) ! Задержка end if end do deallocate(buffer) end
Пример. использования режима $GXOR: Напишем программу, выполняющую перемещение желтого круга на отрезке [-1, 3] по траектории, которая задается функцией 4 9 y = ln( + x ) + − e − sin(1,3x − 0,7) . 3 7 Причем обеспечим сохранность изображения траектории в процессе перемещения круга. Порядок вычислений: 1°. Начало. 2°. Создать образ (впишем образ в квадрат размера 40 * 40). 3°. Задать окно вывода. 4°. Построить траекторию перемещения образа. 5°. Выполнить перемещение образа по построенной траектории. 6°. Конец.
Алгоритм перемещения образа таков: 1°. Начало. 2°. x = xa 3°. Пока x <= xb y = f (x)
! xa - начальная x-координата траектории ! xb - конечная x-координата траектории
⎯ 95 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Вставить образ в точку (x, y), используя режим вставки $GXOR. Выполнить задержку вычислений, например на 20 мс. Вставить повторно в точку (x, y) перемещаемый образ, используя режим вставки $GXOR. ! Повторное размещение образа в точке (x, y) в режиме $GXOR ! восстановит изображение, существовавшее до первой вставки ! образа в эту точку. x = x + dx конец цикла 3°. 4°. Конец. ! Модуль vport обеспечивает формирование окна вывода ! с границами xa, xb и ya, yb module vport use dflib integer(2) XE, YE ! XE,YE - размеры экрана в пикселях integer(2) xl, yl, xr, yr ! Задающие видеоокно координаты real(8) :: xa, xb ! xa, xb - границы окна вывода по x real(8) :: ya, yb ! ya, yb - границы окна вывода по y contains subroutine screen( ) logical(4) :: res type(windowconfig) :: wc ! wc содержит параметры видеоокна res = getwindowconfig(wc) ! Читаем параметры видеоокна XE = wc.numxpixels ! numxpixels - число пикселей по оси x YE = wc.numypixels ! numypixels - число пикселей по оси y end subroutine screen subroutine port(xl, yl, xr, yr) ! xl, yl - координаты верхнего левого угла видеоокна ! xr, yr - координаты нижнего правого угла видеоокна integer(2) :: xl, yl, xr, yr, status2 logical(2) :: finv = .true._2 ! Ось y направлена снизу вверх call setviewport(xl, yl, xr, yr) ! Задание видового порта ! Оконная система координат (ОСК) status2 = setwindow(finv, xa, ya, xb, yb) end subroutine port end module vport use dflib use vport integer(1), allocatable :: buffer(:) ! Массив для хранения образа ! ix, iy - размер прямоугольника, ограничивающего образ
⎯ 96 ⎯
2. Вывод графических данных
integer(2) :: status2, ix = 40, iy = 40, ri, xi, yi, xw integer(4) :: status4, imsize real(8), parameter :: pi = 3.14159265_8 real(8), external :: fx ! Функция, задающая траекторию real(8) :: x, y, dx = 0.01 ! dx - шаг изменения x type (xycoord) :: s ! Подготовка образа imsize = imagesize(0, 0, ix, iy) ! Размер образа allocate(buffer(imsize)) ! Размещение массива status2 = setcolorrgb(#008080) ! Цвет шара - светло-желтый status2 = ellipse($gfillinterior, 0, 0, ix, iy) ! Образ call getimage(0, 0, ix, iy, buffer) ! Сохраняем образ ri = ix/2 ! Радиус образа (круга) call clearscreen($gclearscreen) ! Очистка экрана status4 = setbkcolorrgb(#c0c0c0) ! Цвет фона - серый call screen( ) ! Определим размеры видеоокна ! Найдем минимальное (ya) и максимальное (yb) значения, которые ! принимает функция y = fx(x) на отрезке [xa, xb] xa = -1.0_8; xb = 3.0_8 ! xa, xb - границы окна вывода по x call y_size(fx, XE, xa, xb, ya, yb) ! x и y координаты верхнего левого и нижнего правого углов видеоокна xl = XE/4_2; yl = YE/4_2 xr = 3_2*XE/4_2; yr = 3_2*YE/4_2 call port(xl, yl, xr, yr) ! Формирование видеоокна xw = xr - xl ! Размер видеоокна по оси x call curve(fx, xw, xa, xb, 10_2) ! Рисуем fx светло-зеленым цветом x = xa ! Перемещаем образ по заданной кривой do while(x <= pi) ! Изменение x в ОСК y = fx(x) ! Значение y в ОСК call getviewcoord_w (x, y, s) ! Преобразование координат xi = s.xcoord - ri ! xi, yi - координаты вставки образа yi = s.ycoord - ri ! в видовой системе координат call putimage(xi, yi, buffer, $gxor) ! Вставка образа call sleepqq(20) ! Задержка 20 мс call putimage(xi, yi, buffer, $gxor) ! Восстановим изображение x = x + dx end do deallocate(buffer) end function fx(x) real(8) :: fx, x fx = sqrt(log(4/3. + x) + 9./7) - exp(-sin(1.3*x - 0.7)) end function fx
⎯ 97 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
subroutine y_size(fx, xw, xa, xb, ya, yb) integer(2) xw ! Возвращает ya и yb real(8) :: fx, xa, xb, ya, yb, x, y, dx ya = huge(ya); yb = tiny(yb) dx = (xb - xa)/dble(xw) ! Шаг изменения x x = xa ! Найдем минимальное (ya) и максимальное do while(x <= xb) ! (yb) значения функции на отрезке [xa, xb] y = fx(x); ya = min(y, ya); yb = max(y, yb) x = x + dx end do end subroutine y_size subroutine curve(fx, xw, a, b, color) use dflib real(8) fx, a, b, dx, x, y integer(2) xw, color, status2 status2 = setcolor(color) dx = (b - a)/dble(xw/2) x=a do while(x <= b) y = fx(x) status2 = setpixel_w(x, y) x = x + dx end do end subroutine curve
! График функции y = fx(x) ! fx - формальная функция ! График функции цветом color ! Шаг по оси x ! Изменение x в ОСК ! Значение y в ОСК ! Вывод точки графика
2.14.2. Обмен с внешней памятью Сохранение образа во внешней памяти выполняется функциями result4 = SAVEIMAGE(filename, ulxcoord, ulycoord, lrxcoord, lrycoord) или result4 = SAVEIMAGE_W(filename, ulwxcoord, ulwycoord, lrwxcoord, lrwycoord) filename - символьное выражение, содержащее полное имя файла, в который сохраняется образ. ulxcoord, ulycoord - параметры типа INTEGER(4), задающие видовые координаты левого верхнего угла сохраняемого образа. lrxcoord, lrycoord - параметры типа INTEGER(4), задающие видовые координаты правого нижнего угла сохраняемого образа. ulwxcoord, ulwycoord - параметры типа REAL(8), задающие оконные координаты левого верхнего угла сохраняемого образа. ⎯ 98 ⎯
2. Вывод графических данных
lrwxcoord, lrwycoord - параметры типа REAL(8), задающие оконные координаты правого нижнего угла сохраняемого образа. Функции возвращают значение типа INTEGER(4), равное нулю при успешном выполнении и меньшее нуля при неудаче. Функции сохраняют образ в Windows bitmap-формате (растровый формат Windows). Образ сохраняется с палитрой, содержащей выведенные на экран цвета. Используемое расширение - BMP. Загрузка образа выполняется функциями result4 = LOADIMAGE(filename, xcoord, ycoord) или result4 = LOADIMAGE_W(filename, wxcoord, wycoord) filename - символьное выражение, задающее имя bitmap-файла. xcoord, ycoord - параметры типа INTEGER(4), задающие видовые координаты левого верхнего угла загружаемого образа. wxcoord, wycoord - параметры типа REAL(8), задающие оконные координаты левого верхнего угла загружаемого образа. Функции возвращают значение типа INTEGER(4), равное нулю при успешном выполнении и меньшее нуля при неудаче. Загруженный образ отображается с использованием цветов, сохраненных в bitmap-файле. Следовательно, если сохраненная в bitmapфайле цветовая палитра отличается от текущей палитры, то новая цветовая палитра загружается вместо текущей. Если образ выходит за пределы окна вывода, то он не загружается и изображение не изменяется. Замечание. Образ сохраняется вместе с характеристиками используемого видеорежима. Загрузку BMP-файла образа следует выполнять в том же видеорежиме, в котором образ был создан. Windows bitmap-файлы могут быть использованы в качестве фона в создаваемых в среде Фортрана изображений. Пример. Создать последовательно фон из файлов pattern.bmp, stone.bmp и flax.bmp. Приведенные файлы поставляются с Windows и находятся, как правило, в директории c:\windows. Для создания фона необходимо знать ширину и высоту образа в пикселях. Зная эти параметры, легко определить, сколько раз нужно повторить загрузку образа и координаты каждой загрузки. Структура BMP-файла такова: сначала следует заголовок файла (14 байт), затем - заголовок изображения файла (40 байт), затем - карта цветов и, наконец, растровые данные. Параметры рисунка (ширина, ⎯ 99 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
высота...) записаны в заголовке изображения BMP-файла (второе и третье поле заголовка изображения: байты 19-26, начиная от начала файла). use dflib ! Режим 800*600 integer(2) :: XE = 800, YE = 600 character(50) :: path = 'c:\windows\', fname character(18) :: ch ! Все инициализирующие строки имеют равную длину character(12) :: fn(3) = (/ 'pattern.bmp', 'stone.bmp ', 'flax.bmp ' /) integer(4) :: ix, iy, x, y, status4, i, j, k, nx, ny do k = 1, 3 ! Демонстрация фонов fname = trim(path) // fn(k) open(1, file = fname, form = 'binary', iostat = ios, status = 'old') if(ios /= 0) then ! Проверяем, удалось ли открыть файл call outtext('File ' // trim(fname) // ' not found' // char(10)) cycle end if read(1) ch ! Пропускаем первые 18 байт файла read(1) ix ! Ширина рисунка в пикселях read(1) iy ! Высота рисунка в пикселях close(1) ! Закрываем файл для обеспечения его загрузки nx = XE/ix + 1 ! Число вставок рисунка по x и y ny = YE/iy + 1 y=0 do i = 1, ny x=0 do j = 1, nx status4 = loadimage(fname, x, y) x = min(x + ix, XE - ix) ! Образ не выходит end do ! за границу видеоокна по оси x y = min(y + iy, YE - iy) ! Образ не выходит end do ! за границу видеоокна по оси y call sleepqq(1500) ! Задержка end do end
2.15. Статус выполнения графических процедур Проконтролировать выполнение посредством функции result2 = GRSTATUS( )
графических
⎯ 100 ⎯
процедур
можно
2. Вывод графических данных
Функция возвращает значение типа INTEGER(2), которое меньше нуля при ошибочном завершении последней использованной графической процедуры, которое больше нуля при наличии предупреждения или которое равно нулю в случае успеха. Функция GRSTATUS может быть использована сразу после вызова графической процедуры, чтобы зафиксировать возможные ошибки или предупреждения. Для нулевого значения результата функции GRSTATUS в модуле DFLIB определена символьная константа $GROK. Также в этом модуле определены и именованные константы, которым присвоены коды ошибок и предупреждений. Обработка ошибок графического вызова может быть выполнена, например, так: if(grstatus( ) < $grok) then ! обработка ошибки выполнения графической процедуры ... end if
Перечисленные ниже процедуры не приводят к ошибкам, и всегда после их применения GRSTATUS возвратит $GROK (0): DISPLAYCURSOR,
GETTEXTCOLORRGB,
GETTEXTPOSITION,
GETBKCOLORRGB,
GETTEXTWINDOW,
GETCOLOR,
OUTTEXT,
GETCOLORRGB,
GETTEXTCOLOR,
WRAPON.
⎯ 101 ⎯
GETBKCOLOR,
3. Приложения QUICKWIN 3.1. Возможности QUICKWIN В Digital Visual Fortran (DVF) и Microsoft Fortran PowerStation (FPS) исполняемое приложение может быть создано как проект QuickWin многооконный проект с графикой. В среде MS Developer Visual Studio (VS) DVF 6.0 проект QuickWin создается после выполнения цепочки: File - New Projects - Fortran Standard Graphics or QuickWin Application - задать имя проекта (Project name) и папку его размещения (Location) - выбрать QuickWin (multiple windows) - Finish. В программных единицах, которые используют процедуры QuickWin, должна быть ссылка на модуль DFLIB.MOD в случае DVF или MSFLIB.MOD в случае FPS: USE DFLIB или USE MSFLIB Работая с QuickWin, можно: •
скомпилировать консоль-приложение как простое приложение для Windows;
•
минимизировать и максимизировать создаваемые в QuickWin окна, в которые выполняется передача данных;
•
вызывать любую из приведенных в гл. 2 графическую процедуру;
•
загружать и сохранять образы;
•
выбирать, копировать и вставлять текст и графику;
•
обнаруживать и обрабатывать удары мыши;
•
выводить на принтер графические данные;
•
программировать меню;
•
создавать собственные иконы;
•
открывать несколько дочерних окон. Используя диалоговые окна и возможности QuickWin, можно создавать приложения с полноценным текстовым и графическим интерфейсом.
⎯ 102 ⎯
3. Приложения QUICKWIN
3.2. Операции над окнами QUICKWIN 3.2.1. Виды окон QUICKWIN После запуска приложения QuickWin создается обрамляющее окно (frame window). С обрамляющим окном в модуле DFLIB (MSFLIB) связана целочисленная константа QWIN$FRAMEWINDOW, используемая в некоторых процедурах QuickWin, например в SETWSIZEQQ, для идентификации обрамляющего окна. В обрамляющем окне могут быть расположены одно или несколько дочерних окон (child windows). Любое дочернее окно QuickWin появляется либо после вызова функции SETWINDOWCONFIG, либо после выполнения первой процедуры ввода/вывода (В/В), например подпрограммы CLEARSCREEN, заполняющей окно цветом фона. В приложении QuickWin может быть до 40 дочерних окон. Причем в это число входят также дочерние окна, подсоединенные к устройствам, которые были закрыты оператором CLOSE. Такие окна недоступны для операций В/В данных (см. разд. 3.2.5).
3.2.2. Создание дочернего окна В общем случае дочернее окно создается оператором OPEN со спецификатором FILE = 'USER', например: open(unit = 10, file = 'user', title = 'Пробное дочернее окно') Спецификатор TITLE= является необязательным и используется для задания заголовка дочернего окна. Каждое новое дочернее окно подсоединяется к устройству В/В с уникальным номером, задаваемым спецификатором UNIT=. Однако оператор OPEN может в приложении отсутствовать. В этом случае в QuickWin создается одно дочернее окно, подсоединенное к всегда существующим устройствам 0, 5 и 6. Создаваемое таким образом окно имеет заголовок Graphic1. Пример. Задать 2 дочерних окна и вывести в первое сообщение Window 1, а во второе - Next window. program win2 use dflib integer(4) :: status4 open(unit = 10, file = 'user', title = 'Первое дочернее окно') open(unit = 20, file = 'user', title = 'Второе дочернее окно') write(10, *) 'Window 1' ! Вывод сообщения в окно 10
⎯ 103 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
write(20, *) 'Next window' call sleepqq(1500) status4 = focusqq(10) call sleepqq(1500) status4 = focusqq(20) end program win2
! Вывод текста в окно 20 ! Задержка на 1500 мс ! Помещаем в фокус окно 10 ! Помещаем в фокус окно 20
3.2.3. Активизация дочернего окна Дочернее окно считается активным, если в него выполняется графический В/В, например процедурами ARC, LINETO, OUTTEXT, OUTGTEXT. Окно становится активным после выполнения функции SETACTIVEQQ. Функция возвращает 1 при удачном выполнении и 0 в противном случае. Тип результата - INTEGER(4). Например: open(unit = 10, file = 'user', title = 'Первое дочернее окно') open(unit = 20, file = 'user', title = 'Второе дочернее окно') status4 = setactiveqq(10) ! Направляем вывод в окно 10 call outtext('Window 1') ! Вывод сообщения в окне 10 status4 = setactiveqq(20) ! Теперь активно окно 20 call outtext('Next window') ! Вывод текста в окне 20
Функция GETACTIVEQQ возвращает номер устройства, к которому подсоединено активное дочернее окно, например: unit = GETACTIVEQQ( ) ! unit - номер активного окна Тип результата - INTEGER(4). Функция, если нет активного окна, вернет QWIN$NOACTIVEWINDOW - значение, определенное в модуле DFLIB (MSFLIB). Функция GETHWINDQQ преобразовывает номер unit устройства, к которому подсоединено окно, в значение обработчика окна, например: handle = GETHWNDQQ(unit) ! handle - значение обработчика окна. Если unit имеет значение QWIN$FRAMEWINDOW, определенное в модуле DFLIB или MSFLIB, то функция GETHWINDQQ вернет значение обработчика обрамляющего окна. Возвращаемое функцией значение имеет тип INTEGER(4). Функция вернет 0, если окно не открыто. Значение обработчика может быть использовано, например, в процедурах многониточного программирования. Окно, в которое выполняется графический вывод, не обязательно располагается поверх других окон. Окно окажется сверху других окон, если его поместить в фокус. ⎯ 104 ⎯
3. Приложения QUICKWIN
3.2.4. Размещение дочернего окна в фокусе Дочернее окно QuickWin, создаваемое оператором OPEN, имеющим спецификатор IOFOCUS = .TRUE., например open(unit = 10, file = 'user', title = 'Пробное дочернее окно', iofocus = .true.)
всегда попадает в фокус, если на него направляется В/В, выполняемый операторами READ, WRITE и PRINT. Графический вывод или вывод неграфического текста посредством подпрограммы OUTTEXT не перемещает активного окна (окна, в которое выполняется вывод) в фокус. Спецификатор IOFOCUS, если он опущен, задается со значением .TRUE. за исключением окна, открытого как UNIT = *, для которого по умолчанию IOFOCUS имеет значение .FALSE.. Открытое окно перемещается в фокус и располагается поверх других окон после выполнения функции FOCUSQQ. Функция возвращает 1 при удачном выполнении и 0 - в противном случае. Тип результата INTEGER(4). Например: status4 = focusqq(20)
! Помещаем в фокус окно 20
3.2.5. Закрытие устройства дочернего окна Из программы устройство, к которому подсоединено дочернее окно, закрывается оператором CLOSE. Дочернее окно можно оставить открытым (видимым) и после выполнения этого оператора, если в нем задать спецификатор STATUS = 'KEEP'. По умолчанию оператор CLOSE выполняется со спецификатором STATUS = 'DELETE', который может быть задан и явно, например: close(10) close(unit = 20, status = 'delete') close(unit = 30, status = 'keep') открыто
! Закрываем и устройство и дочернее окно ! Закрываем и устройство и дочернее окно ! Закрываем только устройство; окно
Открытое окно, устройство которого закрыто оператором CLOSE, недоступно для выполнения операций В/В. В заголовок подобного окна добавляется сообщение Closed, и в меню окна становится активной команда, закрывающая окно, - кнопка ×.
3.2.6. Изменение свойств дочернего окна Параметры дочернего окна хранятся в структуре windowconfig, определенной в модуле DFLIB (MSFLIB) и приведенной в разд. 2.5.
⎯ 105 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Параметры дочернего окна (его конфигурация) могут быть заданы или изменены рассмотренной в разд. 2.5 функцией SETWINDOWCONFIG. Однако вызов SETWINDOWCONFIG необязателен. При его отсутствии окно выберет наилучшее из возможных разрешений и размер шрифта 8 * 16. Число цветов в цветовой палитре зависит от видеоадаптера. Значения параметров окна возвращаются функцией GETWINDOWCONFIG.
3.2.7. Изменение размеров и позиции обрабляющего и дочернего окна Помимо параметров в QuickWin можно задать размеры и положение окна. Это выполняется функцией SETWSIZEQQ, имеющей синтаксис: result4 = SETWSIZEQQ(unit, winfo) unit - выражение типа INTEGER(4), задающее устройство, к которому подсоединено окно. По умолчанию окно подсоединено к устройствам 0, 5 и 6. Для установки размеров обрамляющего окна в качестве номера устройства следует использовать определенную в модуле DFLIB (MSFLIB) именованную константу QWIN$FRAMEWINDOW. winfo - переменная производного типа qwinfo, задающая физические координаты левого верхнего угла окна, его высоту и ширину. Производный тип qwinfo определен в модуле DFLIB (MSFLIB): TYPE QWINFO INTEGER(2) type INTEGER(2) x INTEGER(2) y INTEGER(2) h INTEGER(2) w END TYPE QWINFO
! Тип окна ! x-координата верхнего левого угла окна ! y-координата верхнего левого угла окна ! Высота окна ! Ширина окна
Действие функции зависит от значения компонента type, которое может быть: •
QWIN$MIN - выполняется минимизация окна;
•
QWIN$MAX - выполняется максимизация окна;
•
QWIN$RESTORE - минимизированное окно возвращается к прежним размерам и позиции;
•
QWIN$SET - положение окна и его размеры устанавливаются по значениям другим компонентов qwinfo. Функция возвращает значение типа INTEGER(4), равное нулю в случае успеха и отличное от нуля при неудаче. ⎯ 106 ⎯
3. Приложения QUICKWIN
Замечание. Позиция и размеры обрамляющего окна задаются в пикселях экрана. Позиция и размеры дочернего окна задаются в единицах высоты и ширины неграфической литеры - знакоместа (символа, выводимого операторами PRINT, WRITE и подпрограммой OUTTEXT). Позиция и размеры окна возвращаются функцией result4 = GETWSIZEQQ(unit, ireq, winfo) Параметры unit и winfo имеют тот же смысл, что и одноименные параметры функции SETWSIZEQQ. ireq - выражение типа INTEGER(4), задающее тип запрашиваемой информации; может принимать значение одной из определенных в модуле DFLIB (MSFLIB) именованных констант: •
QWIN$SIZEMAX - запрос о максимально возможных размерах окна;
•
QWIN$SIZECURR - получение данных о текущих размерах окна. Функция возвращает значение типа INTEGER(4), равное нулю в случае успеха и отличное от нуля при неудаче. Пример 1. Максимизировать обрамляющее окно и задать размеры и позицию открываемого по умолчанию дочернего окна. program child_window use dflib integer(4) :: result integer(2) :: numfonts, fontnum type(qwinfo) :: qw type(xycoord) :: pos qw.type = qwin$max ! Максимизация обрамляющего окна result = setwsizeqq(qwin$framewindow, qw) numfonts = initializefonts( ) ! Инициализация шрифтов fontnum = setfont('t''arial''h50w34i') ! Установим шрифт ARIAL qw.x = 5_2; qw.y = 5_2 ! Позиция верхнего левого угла окна qw.h = 10_2; qw.w = 60_2 ! Размеры окна qw.type = qwin$set ! Задаваемое окно позволяет вывести ! на строке до 60 неграфических литер result = setwsizeqq(0, qw) ! Окно подсоединено к устройствам 0, 6 и 5 call moveto(int2(5), int2(30), pos) call outgtext('Child window') end program child_window
Пример 2. Вывести на двух расположенных рядом окнах проекции куба, используя 2 вида параллельного проецирования: изометрию и диметрию.
⎯ 107 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Вывод на экран трехмерного объекта выполняется при помощи проецирования. Напомним идеи проецирования на примере куба. Пусть грани куба параллельны осям координат (рис. 3.1, а). (По традиции, принятой в машинной графике, ось z направлена перпендикулярно плоскости проекций, которая, в свою очередь, ассоциируется с экраном монитора.) Выполним при помощи пучка параллельных лучей, которые, вдобавок, перпендикулярны плоскости x0y, проекцию куба на эту плоскость (рис. 3.1, б). Такое проецирование называется параллельным, а проекция ортографической. Очевидно, что она неинформативна, так как содержит только одну лицевую грань куба. y
8 5
z
3
1
2 а
6
5
6 4
7
y
7 x
x 2
1 б
8
6
5
2
4 1 в
Рис. 3.1. Пространственное расположение куба и его проекции на плоскость x0y: а - положение куба в пространстве; б - ортографическая проекция; в - изометрия
С целью повышения информативности проецирования применим такой прием: развернем в пространстве куб так, чтобы одновременно были видны левая, правая и верхняя грани, и вновь выполним его параллельное проецирование. Если для позиционирования куба в пространстве выполнить 2 поворота - первый относительно оси y на угол ψ (sin2ψ = 1/2), а второй относительно оси x на угол ϕ (sin2ϕ = 1/3) (оба поворота выполняются против часовой стрелки), а вслед параллельное проецирование, то мы получим изометрическую проекцию (рис. 3.1, в). В случае диметрии углы поворота связаны соотношением sin2ψ = tg2ϕ. Известны и уравнения для вычисления значений новых, после поворота, координат точки x*, y*, z* по ее старым координатам x, y, z и углам поворота ϕ и ψ вокруг осей x и y:
⎯ 108 ⎯
3. Приложения QUICKWIN
⎛x * ⎞ 0 0 ⎞ ⎜ ⎟ ⎛1 ⎜ y * ⎟ = ⎜ 0 cos ϕ sin ϕ ⎟ ⎟ ⎜ ⎟ ⎜ ⎜ z * ⎟ ⎜⎝ 0 − sin ϕ cos ϕ ⎟⎠ ⎝ ⎠
⎛ cos ψ ⎜ ⎜ 0 ⎜ sin ψ ⎝
0 − sin ψ ⎞ ⎟ 1 0 ⎟ 0 cos ψ ⎟⎠
⎛x ⎞ ⎜ ⎟ ⎜y⎟ ⎜z ⎟ ⎝ ⎠
При графическом выводе будут использованы только x* и y* координаты; z* координата на плоскости x0y не отображается. После умножения матриц получим уравнения, по которым вычисляются получаемые после преобразования проецирования x* и y* координаты вершин куба: x* = x * cosψ + z * sinψ, y* = x * sinϕ * sinψ + y * cosϕ - z * sinϕ * cosψ. Координаты вершин куба, которые они имели до преобразования проецирования, занесем в массивы cvex, cvey и cvez (координаты x, y и z i-й вершины куба до проецирования равны cvex(i), cvey(i) и cvez(i)). В массив inve(1:4, 1:4) поместим данные о номерах соединяемых вершин: например, первая вершина должна быть соединена с вершинами 2, 4 и 5 (нумерация вершин приведена на рис. 3.1, а). Ребра, выходящие из вершины 4, невидимы. Поэтому список вершин, инцидентных этой вершине, состоит из нулей. Осуществим вывод изометрической проекции в окно 1, а диметрии - в окно 2. Результаты работы программы приведем на рис. 3.2.
Рис. 3.2. Результаты вывода проекций куба
program isodim use dflib
⎯ 109 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
! Массивы x, y и z координат приведенного на рис. 3.1 куба real(8) :: cvex(8) = (/ 0, 100, 100, 0, 0, 100, 100, 0 /) real(8) :: cvey(8) = (/ 0, 0, 0, 0, 100, 100, 100, 100 /) real(8) :: cvez(8) = (/ 100, 100, 0, 0, 100, 100, 0, 0 /) ! Массивы координат вершин проекции куба real(8) :: cvex2(8), cvey2(8), fi, psi ! Массив соединяемых вершин integer(2) :: inve(4, 4) = reshape((/ 1, 2, 4, 5, & 3, 0, 0, 0, & 6, 5, 7, 2, & 8, 5, 7, 4 /), shape = (/ 4, 4 /)) integer(2) :: i2 ! win1 - окно для изометрии integer(4) :: i4, win1 = 1, win2 = 2 ! win2 - окно для диметрии call owi(win1, 'Cube1'c) ! Открываем окно win1 call owi(win2, 'Cube2'c) ! Открываем окно win2 fi = dasin(1.0_8 / dsqrt(3.0_8)) ! Углы ϕ и ψ для изометрии psi = dasin(1.0_8 / dsqrt(2.0_8)) ! Построение изометрии i4 = setactiveqq(win1) ! Направим вывод на окно 1 call cube(cvex, cvey, fi, psi) ! Вывод куба fi = dasin(1.0_8) / 4.0_8 ! Углы ϕ и ψ для диметрии psi = dasin(dtan(fi)) ! Пусть ϕ = π/4 i4 = setactiveqq(win2) ! Направим вывод на окно 2 call cube(cvex, cvey, fi, psi) i4 = focusqq(win1) ! Установим фокус на окно 1 contains subroutine owi(win, tit) ! Открываем окно win integer(4) :: win character(*):: tit type(qwinfo) :: qw ! Параметры окна data qw.x, qw.y, qw.h, qw.w / 0, 0, 60, 60 / qw.type = QWIN$SET open(win, file = 'user', title = tit) i4 = setwsizeqq(win, qw) i4 = clickmenuqq(QWIN$TILE) call setviewport(20_2, 20_2, 250_2, 250_2) ! Видовой порт для куба i2 = setwindow(.true._2, -100d0, -150d0, 250d0, 300d0) i2 = setcolor(10_2) ! Цвет ребер куба - зеленый end subroutine owi subroutine cube(cvex, cvey, fi, psi) ! Вывод куба real(8) :: cvex(:), cvey(:), fi, psi, vx, vy, dsf, dcf, dsp, dcp type(wxycoord) :: wxy integer(2) :: i, j, vi, vi2
⎯ 110 ⎯
3. Приложения QUICKWIN
dsf = dsin(fi) ; dcf = dcos(fi) dsp = dsin(psi); dcp = dcos(psi) ! Координаты вершин после cvex2 = cvex*dcp + cvez*dsp ! поворота cvey2 = cvex*dsf*dsp + cvey*dcf - cvez*dsf*dcp do j = 1, 4 ! Вывод проекции куба vi = inve(1, j) vx = cvex2(vi); vy = cvey2(vi) do i = 2, 4 vi2 = inve(i, j) if(vi2 == 0) cycle ! Если ребро невидимо - cycle call moveto_w(vx, vy, wxy) i2 = lineto_w(cvex2(vi2), cvey2(vi2)) end do end do end subroutine cube end program isodim
Замечание. Функция CLICKMENUQQ позволяет имитировать выбор команды QuickWin меню. Если параметр функции принимает значение QWIN$TILE, то дочерние окна размещаются рядом друг с другом. (Рассмотрена в разд. 3.6.)
3.3. Изменение системного меню По умолчанию приложение QuickWin сопровождается приведенным на рис. 3.3 меню.
Рис. 3.3. Меню QuickWin
Рассмотрим устройство меню. Оно состоит из пунктов меню; каждый пункт имеет подпункты, которые отображаются после воздействия (мышью или с клавиатуры) на пункт меню. Так, пункт меню File имеет подпункты Print, Save, Exit (Ctrl+C). Пункты меню и подпункты имеют номера. Так, пункт File имеет номер 1, пункт Edit - номер 2 и т. д. Так же изменяются и номера подпунктов: подпункт Print имеет номер 1, Save - номер 2, а Exit номер 3. С каждым подпунктом меню связана установленная по умолчанию процедура, например с подпунктом Print связана процедура WINPRINT, запускаемая при выборе этого подпункта и открывающая диалоговое окно работы с принтером. ⎯ 111 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
При инициализации можно создать свое собственное меню, которое в процессе работы программы может быть изменено. Также в процессе работы программы можно изменить и задаваемое по умолчанию меню. Функции, изменяющие меню и его свойства, приведены в табл. 3.1. Все функции имеют тип LOGICAL(4). Таблица 3.1. Функции, изменяющие меню и его свойства Функция
Назначение
APPENDMENUQQ
Добавляет пункт или подпункт меню
DELETEMENUQQ
Удаляет пункт или подпункт меню
INSERTMENUQQ
Вставляет пункт или подпункт меню
MODIFYMENUFLAGSQQ
Изменяет свойства пунктов или подпунктов меню, применяя одно из приведенных в табл. 3.2 действий
MODIFYMENUROUTINEQQ
Изменяет связанную с пунктом меню или его подпунктом подпрограмму
MODIFYMENUSTRINGQQ
Изменяет название пункта или подпункта меню
SETWINDOWMENUQQ
Создает список имеющихся дочерних окон
Таблица 3.2. Изменение свойства пунктов и подпунктов меню Действие
Результат
$MENUGRAYED
Пункт или подпункт меню неактивен и светлосерого цвета
$MENUDISABLED
Пункт или подпункт меню неактивен, но обычного серого цвета
$MENUENABLED
Пункт или подпункт меню становится активным (доступным)
$MENUSEPARATOR
Вывод разделительной линии между подпунктами меню
$MENUCHECKED
Простановка галочки рядом с пунктом или подпунктом меню
$MENUUNCHECKED
Удаление галочки, стоящей рядом с пунктом или подпунктом меню
⎯ 112 ⎯
3. Приложения QUICKWIN
При необходимости можно в одной функции задать несколько действий, связав их логической операцией .OR., например: $MENUCHECKED .OR. $MENUENABLED. С каждым пунктом пользовательского меню можно связать либо заданную в QuickWin (табл. 3.3), либо пользовательскую подпрограмму, которая будет запускаться при выборе подпункта. Таблица 3.3. Заданные процедуры QuickWin Имя процедуры
Действие
WINPRINT
Вывод содержимого окна на принтер
WINSAVE
Запись изображения в файл
WINEXIT
Выход из QuickWin
WINSELTEXT
Выбор текста из находящегося в фокусе дочернего окна
WINSELGRAPH
Выбор графического изображения из текущего окна
WINSELALL
Выбор всего содержимого находящегося в фокусе дочернего окна
WINCOPY
Копирование выбранных элементов в буфер
WINPASTE
Вставка из буфера текстовых данных в текущее текстовое окно активного дочернего окна во время выполнения оператора READ
WINCLEARPASTE
Очистка буфера
WINSIZETOFIT
Освобождение дочернего окна от передвижных шкал
WINFULLSCREEN
Переключение в режим полного экрана
WINSTATE
Переключение между режимами "пауза - исполнение"
WINCASCADE
Создание каскада неминимизированных дочерних окон
WINTILE
Полная видимость всех неминимизированных дочерних окон
WINARRANGE
Упорядочение икон, отображающих минимизированные дочерние окна
WINSTATUS
Включает и отключает полосу сообщений о статусе исполнения
WININDEX
Вывод содержания имеющейся справочной информации (Help)
WINUSING
Вывод информации о том, как использовать Help
⎯ 113 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
WINABOUT
Вывод информации о выполняемом приложении QuickWin
NUL
Отсутствие процедуры
3.4. Инициализация меню и обрамляющего окна Если программу QuickWin снабдить внешней функцией INITIALSETTINGS, то эта функция будет автоматически вызвана при запуске приложения. В этой функции можно задать начальные параметры обрамляющего окна (размеры и положение) и создать свое собственное меню взамен устанавливаемого по умолчанию меню QuickWin (см. рис. 3.3). Функция INITIALSETTINGS может включать приведенные в табл. 3.1 функции, которые изменяют меню QuickWin, и функцию SETWINSIZEQQ, задающую размеры и позицию обрамляющего окна QuickWin. Напомним, что параметры обрамляющего окна задаются в пикселях, а размеры и позиция дочернего окна - в единицах высоты и ширины знакоместа. Пользовательское меню будет создано, если первой среди всех изменяющих меню функций выполнить функцию APPENDMENUQQ, добавляющую первый пункт меню, например: flag = appendmenuqq(1, $menuenabled, '&Операции'c, nul)
Если INITIALSETTINGS не создает пользовательского меню, то будет загружено приведенное на рис. 3.3 меню. Причем выполняемые в INITIALSETTINGS попытки удалить или вставить пункты (подпункты) меню будут проигнорированы. Функция INITIALSETTINGS имеет тип LOGICAL(4) и должна вернуть .TRUE., если удалось выполнить начальные установки QuickWin, и .FALSE. - в противном случае. Если функция INITIALSETTINGS в программе отсутствует, то QuickWin запускает свою функцию инициализации, которая всегда возвращает .TRUE.. Пример. Задать начальные параметры обрамляющего окна и задать меню из двух пунктов: •
Просмотр с подпунктами Убрать полосы прокрутки и Весь экран;
•
Выполнить с подпунктом Надпись, после выбора которого должно выводиться сообщение Child window, подпунктом Стереть надпись и подпунктом Выход. ⎯ 114 ⎯
3. Приложения QUICKWIN
program initdemo use dflib call clearscreen($gclearscreen) call sleepqq(1000000) подпунктам end program initdemo
! Заполним окно цветом фона ! Задержка, которая обеспечит доступ к ! меню Надпись и Стереть надпись
subroutine sayword(fl) ! Подпрограмма выводит сообщение use dflib ! Child window и делает неактивным подпункт logical(4) :: fl ! Надпись и активным подпункт Стереть надпись integer(2) :: numfonts, fontnum integer(4) :: result type(xycoord) :: pos ! Теперь подпункт Надпись неактивен result = modifymenuflagsqq(2, 1, $menugrayed) ! Подпункт Стереть надпись активен result = modifymenuflagsqq(2, 2, $menuenabled) call moveto(int2(5), int2(30), pos) numfonts = initializefonts( ) ! Инициализация шрифтов fontnum = setfont('t''arial''h50w34i') ! Установим шрифт ARIAL call outgtext('Child window') ! Вывод графического текста return; call unusedqq(fl) end subroutine sayword subroutine delword(fl) ! Подпрограмма стирает сообщение use dflib ! Child window и делает активным подпункт logical(4) :: fl ! Надпись и неактивным подпункт Стереть надпись integer(4) :: result ! Теперь подпункт Надпись активен result = modifymenuflagsqq(2, 1, $menuenabled) ! Подпункт Стереть надпись неактивен result = modifymenuflagsqq(2, 2, $menugrayed) call clearscreen($gclearscreen) ! Заполним окно цветом фона return; call unusedqq(fl) end subroutine delword function initialsettings( ) позиции, use dflib формирование logical(4) :: initialsettings, flag integer(4) :: result
!
Инициализация
!
размеров
QuickWin:
обрамляющего
! пользовательского меню
⎯ 115 ⎯
задание окна
и
О. В. Бартеньев. Visual Fortran: новые возможности
type(qwinfo) :: qw external sayword, delword ! Связанные с подпунктами меню подпрограммы qw.x = 50_2; qw.y = 50_2 ! Позиция (в пикселях) верхнего левого угла окна qw.h = 400_2; qw.w = 650_2 ! Размеры окна в пикселях qw.type = qwin$set result = setwsizeqq(qwin$framewindow, qw) flag = appendmenuqq(1, $menuenabled, 'Просмотр'c, nul) flag = appendmenuqq(1, $menuenabled, 'Убрать полосы прокрутки'c, winsizetofit) flag = appendmenuqq(1, $menuenabled, 'Весь экран'c, winfullscreen) flag = appendmenuqq(2, $menuenabled, 'Выполнить'c, nul) flag = appendmenuqq(2, $menuenabled, 'Надпись'c, sayword) flag = appendmenuqq(2, $menugrayed, 'Стереть надпись'c, delword) flag = appendmenuqq(2, $menuseparator, ' 'c, nul) ! Разделительная полоса в меню flag = appendmenuqq(2, $menuenabled, 'Выход'c, winexit) initialsettings = .true. end function initialsettings
3.5. Создание списка имеющихся дочерних окон По умолчанию меню содержит список имеющихся в приложении дочерних окон. В пользовательском меню такой список создается функцией SETWINDOWMENUQQ и отображается в виде подпунктов меню. Пункт меню, в котором надо отобразить список окон, указывается в качестве параметра функции. Например, после выполнения приводимой ниже программы winlist меню будет иметь один пункт, содержащий, кроме подпункта Выход, подпункты с именами открытых окон. program winlist use dflib open(10, file = 'user', title = 'Окно 1'); open(20, file = 'user', title = 'Окно 2'); open(30, file = 'user', title = 'Окно 3'); open(40, file = 'user', title = 'Окно 4'); end program winlist
write(10, *) 'Window 1' write(20, *) 'Window 2' write(30, *) 'Window 3' write(40, *) 'Window 4'
function initialsettings( ) ! Инициализация QuickWin: use dflib ! формирование пользовательского меню logical(4) :: initialsettings, flag flag = appendmenuqq(1, $menuenabled, 'Список окон'c, nul) flag = appendmenuqq(1, $menuenabled, 'Выход'c, winexit) flag = setwindowmenuqq(1) ! Теперь в первом пункте будет отображаться
⎯ 116 ⎯
3. Приложения QUICKWIN
initialsettings = .true. end function initialsettings
! список дочерних окон
3.6. Имитация выбора команд меню В программу можно включить вызовы функции CLICKMENUQQ, которая в качестве параметра содержит одну из перечисленных ниже команд меню. Вызов этой функции эквивалентен воздействию (мышью или с клавиатуры) на пункт (подпункт) меню, содержащий команду. Возможные значения параметра функции приведены в табл. 3.4. Таблица 3.4. Возможные значения параметра функции CLICKMENUQQ Значение параметра
QWIN$STATUS
Действие
Выполняется процедура WINSTATUS (см. табл. 3.3)
QWIN$TILE
“
“
WINTILE
QWIN$CASCADE
“
“
WINCASCADE
QWIN$ARRANGE
“
“
WINARRANGE
Задача. Открыть 4 дочерних окна и разместить их без перекрытий в обрамляющем окне. program tilewindows ! Программа также содержит и примеры use dflib ! к следующему разделу integer(4) :: result open(10, file = 'user', title = 'Окно 1'); write(10, *) 'Window 1' open(20, file = 'user', title = 'Окно 2'); write(20, *) 'Window 2' open(30, file = 'user', title = 'Окно 3'); write(30, *) 'Window 3' open(40, file = 'user', title = 'Окно 4'); write(40, *) 'Window 4' result = clickmenuqq(qwin$tile) ! Имитация выбора подпункта меню Tile ! Примеры изменения сообщений QuickWin ! Меняем сообщение Finished на сообщение Это все ! Новое сообщение - обычная строка Фортрана, а не СИ-строка call setmessageqq('Это все', qwin$msg_finished) ! Меняем сообщение Program terminated with exit code ! на сообщение Программа завершена. Код завершения call setmessageqq('Программа завершена. Код завершения ', qwin$msg_term) end program tilewindows
⎯ 117 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
3.7. Изменение сообщений QUICKWIN В строках состояний обрамляющего окна QuickWin, в заголовке дочернего окна и в стандартном диалоговом окне QuickWin на разных этапах работы с приложением появляются различные сообщения, примеры которых приведены в табл. 3.5. Таблица 3.5. Примеры сообщений QuickWin Обозначение сообщения
QWIN$MSG_FINISHED QWIN$MSG_PAUSED QWIN$MSG_RUNNING
Где выводится
Строка состояний “
”
“
”
Сообщение
"Finished" "Paused" "Running"
QWIN$MSG_SELECTTEXT
Заголовок дочернего окна
"Select Text in"
QWIN$MSG_SELECTGRAPHICS
То же
"Select Graphics in"
QWIN$MSG_TERM
Диалоговое окно QuickWin
"Program terminated with exit code "
QWIN$MSG_EXITQ
То же
"Exit Window?"
Содержание каждого сообщения можно изменить, обратившись к подпрограмме SETMESSAGEQQ. Можно, например, перевести сообщение с английского на русский язык. Примеры изменения содержания сообщений приведены в программе tilewindows из предыдущего раздела. Заметим, что первым параметром подпрограммы SETMESSAGEQQ, задающим новый текст сообщения, должна быть обычная строка Фортрана, а не используемая с другими процедурами QuickWin, например с функцией APPENDMENUQQ, СИ-строка.
3.8. Вывод стандартного окна сообщений При необходимости, обратившись к функции MESSAGEBOXQQ, в приложении можно вывести стандартное окно сообщений. Функция имеет синтаксис response = MESSAGEBOXQQ(msg, caption, mtype) msg - выводимое сообщение; должно быть СИ-строкой. caption - текст, выводимый в заголовке окна сообщений; должен быть СИ-строкой. ⎯ 118 ⎯
3. Приложения QUICKWIN
mtype - именованная константа, определяющая объекты (вид кнопок или икон) или атрибуты окна сообщений. Можно, используя логическую операцию .OR., комбинировать несколько констант, например сочетание MB$YESNO .OR. MB$ICONQUESTION задает кнопки Да и Нет и икону с вопросительным знаком. Примеры констант mtype приведены в табл. 3.6. Таблица 3.6. Примеры констант mtype функции MESSAGEBOXQQ Именованная константа
Выводимые объекты
MB$ICONEXCLAMATION
Икона с восклицательным знаком
MB$ICONSTOP
Икона со знаком ⊗
MB$ICONQUESTION
Икона с вопросительным знаком
MB$YESNO
Кнопки Да и Нет
MB$YESNOCANCEL
Кнопки Да, и Нет, и Отмена
Отметим, что в случае русского Windows имена кнопок выводятся на русском языке (кроме названия кнопки OK). Функция возвращает значение типа INTEGER(4), равное нулю, если не хватает памяти для вывода окна сообщений, или равное одному из приведеных в табл. 3.7 значений. Таблица 3.7. Возвращаемые функцией MESSAGEBOXQQ значения Возвращаемое значение
Возвращается после следующего действия
MB$IDABORT
Нажата кнопка Стоп
MB$IDCANCEL
Нажата кнопка Отмена
MB$IDIGNORE
Нажата кнопка Пропустить
MB$IDNO
Нажата кнопка Нет
MB$IDOK
Нажата кнопка OK
MB$IDRETRY
Нажата кнопка Повтор
MB$IDYES
Нажата кнопка Да
Понятно, что возвращаемое функцией значение может быть обработано исполняемой программой. Пример: use dflib integer(4) :: response ... response = messageboxqq('Продолжить вычисления?'c,
⎯ 119 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
'Поиск корней уравнения'c, mb$iconquestion .or. mb$yesno) if(response == mb$idyes) then вычисления ... else stop end if
& !
Нажата
кнопка
Да.
Продолжаем
! Нажата кнопка Нет
3.9. Переопределение сообщения о программе При выборе в меню подпункта About в окне сообщений выводится информация о программе. Выводимый текст можно переопределить, вызвав функцию ABOUTBOXQQ, например: result = aboutboxqq('Система поиска оптимальных управлений. Версия 3.0'c)
Параметр функции - СИ-строка. Тип возвращаемого результата INTEGER(4).
3.10. Копирование текста и графики окна QUICKWIN В дочернем окне QuickWin можно выделить и скопировать в буфер присутствующие в нем текст и/или графическое изображение. Для этого используются подпункты Select Text, Select Graphics и Select All пункта меню Edit. После выбора подпункта Select Text следует, используя мышь или Shift и стрелки клавиатуры, выделить текст (выделенный текст подсвечивается). При выборе графики выбранная область отмечается прямоугольником. Далее выбранная часть окна может быть скопирована в буфер (подпункт Copy меню Edit или Ctrl+Ins). Затем содержимое буфера можно вставить, например, в создаваемый вами в среде Word документ. Вставка производится после выполнения Ctrl+V или Shift+Ins. Причем скопированный текст вставляется как последовательность символов, а скопированные графика и, в случае Select All, графика и текст вставляются как BMP-образ выделенной части дочернего окна. Заметим, что выбор текста не ограничивается текущим текстовым окном, созданным посредством подпрограммы SETTEXTWINDOW, но может быть выполнен в любой области находящегося в фокусе дочернего окна. Кроме того, скопированный текст можно вставить в дочернее окно, в котором оператор READ ожидает ввод данных. В общем случае это может ⎯ 120 ⎯
3. Приложения QUICKWIN
быть то же самое окно, в котором выбран и занесен в буфер (скопирован) текст. Задача. Скопировать текст из окна устройства 10 в окно устройства 20.
⎯ 121 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Для решения запишем программу textcopy. program textcopy use dflib open(10, file = 'user', title = 'Окно 1') write(10, *) 'Text to be copied to window 2' open(20, file = 'user', title = 'Окно 2') write(20, *) 'First line in window 2' k = clickmenuqq(qwin$tile) ! Имитация команды меню Tile read(20, *) ! В окно устройства 20 можно вставить текст как из end program textcopy ! окна устройства 10, так и из окна устройства 20
Затем после запуска программы перейдем в Окно 1, выделим и скопируем в нем текст. Далее перейдем в Окно 2 и вставим в него текст, использовав в пункте меню Edit подпункт Paste.
3.11. Применение пользовательских икон Обрамляющее и дочерние окна QuickWin снабжены задаваемой по умолчанию иконой, расположенной в верхнем левом углу верхней полосы окна. Однако она (икона) при необходимости может быть заменена на пользовательскую. Для замены выполните следующее: •
загрузите в среде VS редактор икон, выполнив цепочку: Insert - Resource - Icon;
•
нарисуйте свою или импортируйте в редактор уже готовую икону;
•
откройте диалог Icon Properties, дважды ударив мышью за пределами иконы либо нажав Alt+Enter, и дайте иконе ID-имя, заменив устанавливаемое по умолчанию имя на новое. Икона обрамляющего окна должна иметь имя "frameicon", икона дочерних окон должна иметь имя "childicon". Даваемое имя в обязательном порядке обрамляется кавычками. Икона будет сохранена в файле с расширением ICO;
•
создайте script-файл, выполнив File - Save As. Файл должен иметь расширение RC;
•
добавьте script-файл в проект с приложением QuickWin и заново постройте приложение. Теперь окно QuickWin (обрамляющее и/или дочернее) будет сопровождаться созданной для него иконой. ⎯ 122 ⎯
3. Приложения QUICKWIN
3.12. Использование мыши 3.12.1. Связанные с мышью события В QuickWin по умолчанию мышь используется для доступа к меню и управления обрамляющим и дочерними окнами (изменение их положения, размеров...). Все эти управляющие воздействия выполняются за пределами дочернего окна. Внутри дочернего окна мышью можно обеспечить выход из режима "Полный экран", выделение графики и текста с целью их последующего копирования в буфер и переключение с одного дочернего окна на другое. Однако в QuickWin можно создать подпрограммы, которые фиксируют и соответствующим образом реагируют на те или иные происходящие с мышью события. Перечень таких событий приведен в табл. 3.8. Таблица 3.8. События, которые могут происходить с мышью Событие
Описание
MOUSE$LBUTTONDOWN
Нажата левая кнопка мыши
MOUSE$LBUTTONUP
Отпущена после нажатия левая кнопка мыши
MOUSE$LBUTTONDBLCLK
Двойной удар левой кнопкой мыши
MOUSE$RBUTTONDOWN
Нажата правая кнопка мыши
MOUSE$RBUTTONUP
Отпущена после нажатия правая кнопка мыши
MOUSE$RBUTTONDBLCLK
Двойной удар правой кнопкой мыши
MOUSE$MOVE
Перемещение мыши
Замечания: 1. Программы могут фиксировать лишь те события, которые произошли, когда мышь находилась в пределах дочернего окна. 2. При двойном ударе кнопкой мыши происходят 4 события: BUTTONDOWN, затем BUTTONUP, далее BUTTONDBLCLK и вновь BUTTONUP. Причем событие BUTTONDBLCLK происходит лишь в том случае, если временная разница между первым и вторым ударами мыши не превышает порогового значения, установленного для мыши в панели управления Windows. В противном случае повторно происходит событие BUTTONDOWN. ⎯ 123 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
3. Перечисленные события задаются в виде именованных, определенных в модуле DFLIB (MSFLIB) констант. 4. Программы, обрабатывающие происходящие с мышью события, имеют приоритет над двойным ударом мышью при выходе из режима "Полный экран". Всегда можно покинуть этот режим, нажав Alt+Enter. В то же время при выборе текста и графики дочернего окна QuickWin эти подпрограммы имеют более низкий приоритет, чем процедуры выбора текста или графики.
3.12.2. Функции обработки событий Обработка любого приведенного в табл. 3.8 события выполняется по следующей схеме. Для заданного дочернего окна и события регистрируется некоторая подпрограмма, которая начинает выполняться, когда это событие произошло. Причем ни одно событие не может произойти на минимизированном дочернем окне. Регистрация подпрограммы выполняется функцией REGISTERMOUSEEVENT, имеющей синтаксис: result4 = REGISTERMOUSEEVENT(unit, mouseevents, callbackroutine) unit - номер устройства дочернего окна, для которого регистрируется подпрограмма, обрабатывающая события mouseevents. Тип unit INTEGER(4). mouseevents события, которые должны обрабатываться подпрограммой callbackroutine. События задаются в виде именованных констант, приведенных в табл. 3.8. Тип mouseevents - INTEGER(4). callbackroutine - подпрограмма, которая имеет атрибут EXTERNAL и которая вызывается, когда происходит событие mouseevents или, если mouseevents - OR-комбинация событий, одно из событий mouseevents. Подпрограмма должна иметь следующий интерфейс: INTERFACE SUBROUTINE CallBackRoutine(unit, mouseevent, keystate, MouseXpos, MouseYpos) INTEGER(4) :: unit, mouseevent INTEGER(4) :: keystate INTEGER(4) :: MouseXpos, MouseYpos END SUBROUTINE CallBackRoutine END INTERFACE
Параметры unit и mouseevent имеют тот же смысл, что и соответствующие параметры функции REGISTERMOUSEEVENT. Параметры MouseXpos и MouseYpos передают x и y координаты мыши во время события. Параметр keystate передает состояние кнопок мыши и ⎯ 124 ⎯
3. Приложения QUICKWIN
клавиш Shift и Ctrl клавиатуры во время события. Параметр является комбинацией приведенных в табл. 3.9 именованных констант. Комбинация составляется посредством логической операции .OR.. Таблица 3.9. Возможные константы, образующие параметр keystate Константа
Описание
MOUSE$KS_LBUTTON
Нажата левая кнопка мыши
MOUSE$KS_RBUTTON
Нажата правая кнопка мыши
MOUSE$KS_SHIFT
Нажата и удерживается во время события клавиша Shift клавиатуры
MOUSE$KS_CONTROL
Нажата и удерживается во время события клавиша Ctrl клавиатуры
Значение параметра, например, при нажатой клавише Shift и ударе правой кнопкой мыши равно MOUSE$KS_SHIFT .OR. MOUSE$KS_RBUTTON. Функция REGISTERMOUSEEVENT возвращает величину типа INTEGER(4), большую или равную нулю в случае успеха. В случае неудачи функция вернет отрицательное число, которое может быть равно: •
MOUSE$BADUNIT, если указанное устройство (unit) не открыто или не подсоединено к дочернему окну QuickWin;
•
MOUSE$BADEVENT, если заданное событие (mouseevent) не поддерживается. Функция UNREGISTERMOUSEEVENT типа INTEGER(4) выполняет отмену регистрации подпрограммы callbackroutine, например: ! 10 - номер устройства окна result = unregistermouseevent(10, mouse$lbuttondown)
Если REGISTERMOUSEEVENT вызывается повторно с новой подпрограммой callbackroutine без отмены предыдущей регистрации, то новая регистрация отменяет старую и для указанного события на указанном окне будет выполняться новая подпрограмма. Пример. Создать программу, которая: •
в окнах устройств 10 и 20 читает после нажатия на левую кнопку мыши ее координаты;
⎯ 125 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
•
в окне устройства 10 соединяет каждую пару последовательно введенных точек отрезком прямой (точки задаются считанными координатами);
•
в окне устройства 20 строит окружность заданного радиуса с центром во введенной точке;
•
очищает окна устройств 10 и 20 от выполненных построений либо после выбора соответствующего пункта меню, либо после двойного нажатия левой кнопки мыши.
module forpline use dflib integer(4) :: npoints ! Число введенных точек integer(4) :: result, radius = 20 ! radius - радиус окружности integer(4), parameter :: unit1 = 10, unit2 = 20 end module forpline program polyline_circles use forpline ! Подпрограммы, отображающие точки и построенные на них отрезки прямых и окружности external circs_and_lines, newentities ! Зарегистрированные подпрограммы имеют npoints = 0 ! атрибут EXTERNAL open(unit1, file ='user', title = 'Построение полилинии') ! Оператор включен для отображения дочернего окна call clearscreen($gclearscreen) open(unit2, file ='user', title = 'Построение окружностей') call clearscreen($gclearscreen) ! Отобразим окно устройства unit2 result = clickmenuqq(qwin$tile) ! Имитация выбора подпункта меню Tile result = registermouseevent(unit1, mouse$lbuttondown, circs_and_lines) result = registermouseevent(unit2, mouse$lbuttondown, circs_and_lines) result = registermouseevent(unit1, mouse$lbuttondblclk, newentities) result = registermouseevent(unit2, mouse$lbuttondblclk, newentities) call sleepqq(1000000) ! Предотвратим немедленное завершение программы end program polyline_circles function initialsettings( ) ! Инициализация QuickWin - формирование use dflib ! пользовательского меню из одного пункта logical(4) :: initialsettings, flag ! и трех подпунктов external newpline, newcirc ! Связанные с подпунктами меню подпрограммы flag = appendmenuqq(1, $menuenabled, 'Операции'c, nul) flag = appendmenuqq(1, $menuenabled, 'Новая полилиния'c, newpline) flag = appendmenuqq(1, $menuenabled, 'Новые окружности'c, newcirc)
⎯ 126 ⎯
3. Приложения QUICKWIN
flag = appendmenuqq(1, $menuenabled, 'Выход'c, winexit) initialsettings = .true. end function initialsettings subroutine newpline(fl) меню use forpline выбора logical(4) :: fl, ltemp npoints = 0 result = setactiveqq(unit1) result = focusqq(unit1) call clearscreen($gclearscreen) цветом) ltemp = fl end subroutine newpline
! fl - обязательный параметр для процедуры ! Подпрограмма очищает окно unit1 после ! подпункта меню Новая полилиния ! Направим вывод на окно unit1 ! Переместим окно unit1 в фокус ! Заполнение окна цветом фона (черным ! Предотвратим сообщения компилятора
subroutine newcirc(fl) ! fl - обязательный параметр для процедуры меню use forpline ! Подпрограмма очищает окно unit2 после выбора logical(4) :: fl, ltemp ! подпункта меню Новые окружности result = setactiveqq(unit2); result = focusqq(unit2) call clearscreen($gclearscreen) ltemp = fl ! Предотвратим сообщения компилятора end subroutine newcirc subroutine circs_and_lines(unit, mevent, keystate, mxpos,mypos) use forpline ! Вывод отрезка прямой или окружности integer(4) :: unit, mevent, keystate, mxpos, mypos integer(2) :: status2, mpixel= 2 ! Координаты для вывода круга, отображающего точку integer(2) :: lcx, lcy, rcx, rcy type(xycoord) pt ! Для выполнения подпрограммы moveto result = mevent; result = keystate ! Для подавления предупреждений компилятора result = setactiveqq(unit) ! Направляем вывод на окно unit lcx = max(mxpos - mpixel, 0); lcy = max(mypos - mpixel, 0) rcx = mxpos + mpixel; rcy = mypos + mpixel result = setcolorrgb(rgbtointeger(255, 0, 0)) ! Точку - красным цветом ! Вывод круга, отображающего точку status2 = ellipse($gfillinterior, lcx, lcy, rcx, rcy) result = setcolorrgb(rgbtointeger(255, 255, 255)) ! Примитив - ярко-белым цветом select case(unit) ! Вывод отрезка или окружности case(unit1) ! Вывод выполняется в окне unit1
⎯ 127 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
npoints = npoints + 1 ! Число введенных точек if(npoints > 1) then status2 = lineto(int2(mxpos), int2(mypos)) ! Вывод отрезка прямой else call moveto(int2(mxpos), int2(mypos), pt) ! Перемещение позиции вывода end if ! в первую введенную точку case(unit2) ! Вывод выполняется в окне unit2 lcx = max(mxpos - radius, 0); lcy = max(mypos - radius, 0) rcx = mxpos + radius; rcy = mypos + radius status2 = ellipse($gborder, lcx, lcy, rcx, rcy) ! Вывод очередной окружности end select end subroutine circs_and_lines ! Подпрограмма очищает окно unit после события MOUSE$LBUTTONDBLCLK subroutine newentities(unit, mevent, keystate, mxpos,mypos) use forpline integer(4) :: unit, mevent, keystate, mxpos, mypos result = mevent; result = keystate; result = mxpos; result = mypos call clearscreen($gclearscreen) if(unit == unit1) npoints = 0 ! В случае окна, предназначенного end subroutine newentities ! для вывода полилинии
Подпрограммы, обрабатывающие связанные с мышью события, должны завершаться достаточно быстро. Это нужно для того, чтобы каждый последующий вызов одной из подпрограмм выполнялся, когда предшествующий вызов другой подпрограммы завершен. Если же зарегистрированная подпрограмма выполняется продолжительное время и один вызов накладывается на другой, то для такой подпрограммы следует создать, применяя технику многониточного программирования, самостоятельную нить. В противном случае, когда самостоятельная нить не создана, наложение вызовов невозможно. Подпрограммы - обработчики событий следует использовать в приложениях, в которых характерны переходы. Например, в рассмотренном примере произвольным образом осуществляется переход от построения окружностей к построению отрезков и обратно. В последовательно работающих приложениях предпочтительнее использовать блокирующую функцию WAITONMOUSEEVENT.
3.12.3. Блокирующая функция WAITONMOUSEEVENT Блокирующая функция WAITONMOUSEEVENT приостанавливает выполнение программы до тех пор, пока не произойдет заданное связанное с мышью событие. Эта функция подобна функции INCHARQQ, читающей ⎯ 128 ⎯
3. Приложения QUICKWIN
единичный введенный с клавиатуры символ и возвращающей его 8-битовый код. Функция имеет синтаксис: result4 = WAITONMOUSEEVENT(mouseevents, keystate, x, y) Смысл параметров mouseevents и keystate и их значения приведены в предыдущем разделе. И mouseevents и keystate могут быть заданы как комбинация соответствующих именованных констант (см. табл. 3.8 и 3.9), составленная при помощи логической операции .OR.. Например: mouseevents = MOUSE$RBUTTONDOWN .OR. MOUSE$LBUTTONDOWN x, y - x и y координаты мыши во время события. Параметр mouseevents является входным (имеет вид связи IN), а параметры keystate, x и y - выходные, с видом связи OUT. Функция возвращает значение типа INTEGER(4), равное в случае успеха именованной константе, описывающей заданное событие, или MOUSE$BADEVENT - константе, обозначающей, что заданное событие не поддерживается. Функция не возвращает значения до тех пор, пока не произойдет заданное событие. На период ожидания события в строку сообщений обрамляющего окна помещается текст: "Mouse input pending in XXX", где XXX - имя дочернего окна. Заданное событие должно произойти в окне, которое было в фокусе, когда была вызвана функция WAITONMOUSEEVENT. Если же заданное событие произойдет в другом окне, то оно не приведет к возврату из функции WAITONMOUSEEVENT, но вызовет выполнение подпрограммы, зарегистрированной для этого события и окна (при наличии таковой). Пример. Создать программу, которая после нажатия на левую кнопку мыши строит окружность радиуса R1 красного цвета, а после нажатия на правую кнопку мыши строит окружность радиуса R2 желтого цвета. При нажатой клавише клавиатуры Ctrl цвет выводимой окружности меняется в первом случае на зеленый, а во втором на бирюзовый. После двойного удара по левой кнопке мыши программа завершается. program circles use dflib integer(4) :: result, mouseevents, keystate, mxpos, mypos integer(4) :: R1 = 20, R2 = 40 ! Радиусы окружностей integer(2) :: status2 integer(2) :: lcx, lcy, rcx, rcy ! Координаты для вывода окружности open(10, file ='user', title = 'Построение окружностей')
⎯ 129 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
! Оператор включен для отображения дочернего окна call clearscreen($gclearscreen) mouseevents = mouse$lbuttondown .or. mouse$rbuttondown .or. mouse$lbuttondblclk do result = waitonmouseevent(mouseevents, keystate, mxpos, mypos) select case(result) case(mouse$lbuttondown) if(keystate == (mouse$ks_control .or. mouse$ks_lbutton)) then result = setcolorrgb(rgbtointeger(0, 255, 0)) ! Зеленый цвет else result = setcolorrgb(rgbtointeger(255, 0, 0)) ! Красный цвет end if lcx = max(mxpos - R1, 0); lcy = max(mypos - R1, 0) rcx = mxpos + R1; rcy = mypos + R1 ! Вывод очередной окружности радиуса R1 status2 = ellipse($gborder, lcx, lcy, rcx, rcy) case(mouse$rbuttondown) if(keystate == (mouse$ks_control .or. mouse$ks_rbutton)) then result = setcolorrgb(rgbtointeger(0, 255, 255)) ! Бирюзовый цвет else result = setcolorrgb(rgbtointeger(255, 255, 0)) ! Желтый цвет end if lcx = max(mxpos - R2, 0); lcy = max(mypos - R2, 0) rcx = mxpos + R2; rcy = mypos + R2 ! Вывод очередной окружности радиуса R2 status2 = ellipse($gborder, lcx, lcy, rcx, rcy) case(mouse$lbuttondblclk) call exit( ) end select enddo end program circles function initialsettings( ) ! Инициализация QuickWin - формирование use dflib ! пользовательского меню из одного пункта logical(4) :: initialsettings, flag ! и двух подпунктов external newcirc ! Связанная с подпунктом меню подпрограмма flag = appendmenuqq(1, $menuenabled, 'Операции'c, nul) flag = appendmenuqq(1, $menuenabled, 'Новые окружности'c, newcirc) flag = appendmenuqq(1, $menuenabled, 'Выход'c, winexit) initialsettings = .true. end function initialsettings
⎯ 130 ⎯
3. Приложения QUICKWIN
subroutine newcirc(fl) меню use dflib logical(4) :: fl, ltemp integer(4) :: result result = setactiveqq(10) call clearscreen($gclearscreen) ltemp = fl end subroutine newcirc
! fl - обязательный параметр для процедуры ! Подпрограмма очищает окно после выбора ! подпункта меню Новые окружности ! Направим вывод на окно 10 ! Предотвратим сообщения компилятора
Замечание. При нажатой клавише Ctrl в момент, например, удара по левой кнопке мыши (событие MOUSE$LBUTTONDOWN) значение параметра keystate равно результату логической операции MOUSE$KS_CONTROL .OR. MOUSE$KS_LBUTTON. Поэтому для проверки, нажата ли клавиша Ctrl в момент события или нет, в операторе IF используется выражение отношения: if(keystate == (mouse$ks_control .or. mouse$ks_lbutton)) then
3.12.4. Особенности работы с блокирующими процедурами Блокирующие процедуры, такие, как WAITONMOUSEEVENT, READ или INCHARQQ, приостанавливают выполнение программы до тех пор, пока не произойдет ожидаемое событие. При наличии в приложении нескольких блокирующих процедур проблемы возникают, когда и процесс и одна или несколько подпрограмм обработки события внутри этого процесса содержат блокирующие процедуры. Суть проблемы: QuickWin выдает сообщение в строке состояний о том, какое ожидается событие. Однако в сложных случаях это сообщение может быть ложным, т. е. реально процесс ожидает не то событие, о каком сообщает QuickWin. Это может привести к ошибочным действиям пользователя и к неверным результатам. Чтобы избежать подобных ошибок, не включайте блокирующие процедуры в подпрограммы обработки событий. Кроме того, QuickWin вообще не поддерживает более одной блокирующей процедуры типа READ или INCHARQQ в подпрограммах обработки событий, происходящих в одно и то же время на одном и том же дочернем окне. Если процесс блокирован в результате выполнения READ или INCHARQQ, то блокировка в другой подпрограмме путем READ или INCHARQQ будет проигнорирована и число -1 будет возвращено в вызывающую программную единицу. Конечно же, этот результат, включив соответствующую проверку, можно выявить и принять надлежащие меры, ⎯ 131 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
но лучше вообще не использовать блокирующие процедуры как в подпрограммах обработки событий, так и в подпрограммах, связанных с пунктами меню.
3.12.5. Особенности подпрограмм обработки событий Подпрограммы обработки событий выполняются в нити, отличной от нити главной программы. Таким образом, в программе должны быть скоординированы процессы разделения данных, вывода в окна и в файлы. Эта проблема возникает особенно остро, если подпрограммы обработки событий выполняются достаточно долго и в процессе функционирования одной подпрограммы должна быть запущена другая. В этом случае следует применить технику многониточного программирования, создав для каждой из подпрограмм, одновременно требующих один и тот же ресурс, свою нить.
⎯ 132 ⎯
4. Многониточное программирование 4.1. Постановка задачи Напишем программу, отображающую полосу изменяющейся длины, высота которой равна h. При расширении полосы координату x правого конца полосы будем изменять от xs до xf. При достижении xf начнем процесс сжатия полосы, который завершим, когда длина полосы станет равной нулю. Процесс расширения-сжатия полосы выполним в бесконечном цикле DO - END DO. Изменение длины полосы отобразим на рис. 4.1. xs ys
x
xf
xs
x
h - высота полосы
y
y
а
б
Рис. 4.1. Бегущая полоса: а - полоса в момент времени ti; б - полоса в момент времени tj (tj > ti)
Вывод полосы выполним в физической системе координат. Используем алгоритм: 1°. Начало. 2°. Задать xs, ys, xf и h, где xs, ys - координаты начала полосы; xf - конец полосы; h высота полосы. 3°. Задать dx - шаг изменения длины полосы, color1 - цвет полосы в процессе увеличения ее длины и color2 - цвет полосы в процессе уменьшения ее длины. 4°. x = xs; color = color1. 5°. Выполнить цикл: Вывести прямоугольник цвета color с координатами верхнего левого угла x, ys и нижнего правого угла - x + dx, ys + h. Если x ≥ xf, то Изменить цвет полосы (color = color2); dx = -dx. иначе, если x ≤ xs, то Изменить цвет полосы (color = color1); dx = -dx.
⎯ 133 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
конец если. конец цикла 5°. 6°. Конец.
! Для выхода из цикла нажать Ctrl + C
Используем для реализации алгоритма проект типа QuickWin. Напомним, что такой проект задается в среде MS Developer Visual Studio (VS) Digital Visual Fortran (DVF) 6.0 после выполнения цепочки: File - New Projects - Fortran Standard Graphics or QuickWin Application - задать имя проекта (Project name) и папку его размещения (Location) - выбрать QuickWin (multiple windows) - Finish. Программа: module bar_data use dflib ! Модуль DFLIB содержит интерфейсы процедур integer(2) :: status2 ! и константы, необходимые для работы QuickWin integer(4) :: status4 ! В FPS4 следует задать USE MSFLIB logical(4) :: lret integer(2), parameter :: xs = 25, ys = 30, h = 50 integer(2) :: XW ! Размер видеоокна по x в пикселях contains subroutine set_window( ) ! Задает видеоокно и определяет его размеры type(qwinfo) :: qwin type(windowconfig) :: wincon wincon%numxpixels = -1_2; wincon%numypixels = -1_2 wincon%numtextcols = -1_2; wincon%numtextrows = -1_2 wincon%numcolors = -1_2; wincon%fontsize = -1 wincon%title = 'Бегущая полоса длины'c ! Зададим заголовок видеоокна lret = setwindowconfig(wincon) lret = getwindowconfig(wincon) XW = wincon%numxpixels ! Размер видеоокна по x (в пикселях) ! Раскроем обрамляющее окно до максимального размера qwin%type = qwin$max status4 = setwsizeqq(qwin$framewindow, qwin) ! Раскроем окно вывода до максимального размера; ! окно подсоединено к устройствам 0, 5 и 6 status4 = setwsizeqq(0, qwin) end subroutine set_window end module bar_data program bars
⎯ 134 ⎯
4. Многониточное программирование
use bar_data call set_window( ) размеры call run_bar( ) end program bars
! Зададим окно вывода и определим его ! Вывод бегущей полосы
subroutine run_bar( ) use bar_data integer(2) :: xf, x, y, dx integer(4) :: color, color1, color2 color1 = rgbtointeger(0, 0, 255) ! Синий цвет color2 = rgbtointeger(200, 200, 200) ! Серый цвет xf = XW - xs; x = xs; y = ys; dx = 5; color = color1 status4 = setcolorrgb(color) ! Устанавливаем текущий цвет вывода do ! Цикл вывода полосы status2 = rectangle($gfillinterior, x, y, x + dx, y + h) call sleepqq(5) ! Задержка на 5 мс x = x + dx if(x >= xf) then call beepqq(200, 5) ! Сигнал с частотой 200 Гц color = color2; dx = - dx ! и продолжительностью 5 мс status4 = setcolorrgb(color) else if(x <= xs) then call beepqq(400, 5) color = color1; dx = - dx status4 = setcolorrgb(color) end if end do end subroutine run_bar
Пусть теперь надо отобразить несколько параллельных полос, каждая из которых изменяет длину по приведенному выше алгоритму, причем изменение длин отображаемых полос выполняется одновременно.
4.2. Нити и процессы Для решения подобного рода задач в Фортране на однопроцессорных машинах, впрочем так же, как и в других языках программирования, например СИ, используется техника многониточного программирования. Суть ее в следующем. В программе создаются несколько нитей ее выполнения (в данной задаче следует создать число нитей, равное числу отображаемых бегущих полос). Далее при выполнении программы планировщик заданий распределяет ресурсы компьютера между ⎯ 135 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
существующими нитями в соответствии с установленными для них приоритетами. Однако при обращении нитей к некоторым ресурсам, например к видеоадаптеру, программист дополнительно должен позаботиться о синхронизации работы нитей, используя существующие для этих целей функции. Заметим, что всегда существует основная нить, автоматически создаваемая загрузчиком, в которой исполняется главная программа. Помимо нитей, которые являются отдельными путями исполнения программы, могут быть созданы самостоятельные процессы. Процесс состоит из одной или более нитей, кода, данных и других распределенных в памяти ресурсов, например таких, как открытые файлы, семафоры (объекты, синхронизирующие работу нитей), динамически выделенная память. Программа выполняется, когда системный планировщик передает управление одной из нитей. Планировщик определяет, когда и какая нить должна выполняться. Нити с более низким приоритетом могут ждать, когда нити, обладающие более высоким приоритетом, завершат свои задачи. На многопроцессорных машинах планировщик, чтобы сбалансировать загрузку процессоров, может распределить отдельные нити между различными процессорами. Нити, в отличие от процессов, требуют меньше ресурсов для их создания и функционирования, поэтому они предпочтительнее (по сравнению с процессами) в программах, интенсивно использующих внешние ресурсы, например монитор или принтер. Приводимые ниже сведения носят не исчерпывающий, но вполне достаточный характер для освоения техники многониточного программирования. Для расширения представления о нитях и процессах следует обратиться к документации или книгам по Win32 API.
4.3. Организация нитей 4.3.1. Модули для многониточного программирования Программа, если только она не является приложением Windows API, в которой созданы несколько нитей, должна содержать ссылку (или несколько ссылок) в случае FPS 4.0 на модуль MT.MOD, а в случае DVF 6.0 - на модуль DFMT.MOD: USE MT или USE DFMT
⎯ 136 ⎯
4. Многониточное программирование
Подобные ссылки должны быть в тех программных компонентах, в которых происходит обращение к процедурам многониточного программирования. Если создается Windows API-приложение, то все необходимые для многониточного программирования данные содержатся в случае FPS 4.0 в модуле MSFWIN.MOD, а в случае DVF - в модуле DFWIN.MOD, ссылка на который выполняется оператором USE MSFWIN или USE DFWIN Приводимые программы компилировались и выполнялись как в среде FPS 4.0, так и в среде DVF (5.0 и 6.0).
4.3.2. Построение проекта с несколькими нитями Компилируя и линкуя проект с несколькими нитями, необходимо обеспечить доступ к библиотекам, содержащим процедуры многониточного программирования. При работе в среде VS Фортрана DVF 6.0, прежде чем построить проект, выполните цепочку: Project - Settings - Settings for (Debug, или Release, или All configurations) - выбрать проект - Fortran - в поле Category выбрать Libraries - в поле Reentrancy Support выбрать Threaded OK. В случае FPS 4.0 используйте цепочку: Build - Settings - Settings for (Debug, или Release) - Fortran - в поле Category выбрать Fortran Libraries активизировать кнопку Multithreaded или Multithreaded DLL - OK.
4.3.3. Создание нити Нить создается функцией hThread = CreateThread(security, stack, thread_func, argument, & flags, thread_id) Все параметры, кроме thread_func, имеют тип INTEGER(4). Параметр security мы будем устанавливать равным нулю. Параметр stack задает размер стека для новой нити. Равный нулю, параметр stack задает для вновь создаваемой нити размер стека, который равен размеру стека первичной нити, т. е. нити, автоматически создаваемой загрузчиком. При необходимости система увеличивает размер требуемого для нити стека, максимальная величина которого может быть равна 1 М. thread_func - имя процедуры, которая реализует создаваемую нить. Процедура имеет лишь один формальный параметр, который ассоциируется с параметром argument функции CreateThread. Процедура thread_func должна быть либо внешней, обладающей атрибутом EXTERNAL, либо ⎯ 137 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
модульной процедурой. Атрибут EXTERNAL, которым, напомним, обладают и процедуры, имеющие явно заданный интерфейс, должен быть задан в программной единице, создающей нить. То же справедливо и для ссылки на модуль, содержащий thread_func. argument - имя фактического параметра процедуры thread_func. Задаваемое до обращения к функции CreateThread значение параметра argument используется внутри процедуры thread_func для идентификации нити. Параметр flags может принимать значение 0 или CREATE_SUSPENDED. В первом случае нить начинает функционировать сразу же после ее создания. Во втором - для запуска нити придется вызвать функцию result = ResumeThread(hThread) thread_id - уникальный идентификатор созданной нити. Функция CreateThread возвращает значение типа INTEGER(4), которое называется обработчиком нити и может быть употреблено в качестве аргумента используемых при работе с нитями процедур, например в функции CloseHandle, закрывающей нить: lresult = CloseHandle(handle) При создании нити, если параметр функции CreateThread flags равен нулю, начинает выполняться задающая ее процедура thread_func.
4.3.4. Реализующая нить процедура Каждая нить ассоциируется с реализующей ее процедурой. Такая процедура должна отвечать следующим требованиям: •
имя процедуры присутствует в списке CreateThread (третий параметр функции);
•
процедура обладает атрибутом EXTERNAL или является модульной процедурой;
•
процедура имеет один формальный параметр, который ассоциируется при создании нити с фактическим параметром argument функции CreateThread. Значение фактического параметра argument должно быть задано до создания нити;
•
значения существующих внутри процедуры нити переменных, если процедура используется для задания нескольких нитей, не должны
⎯ 138 ⎯
параметров
функции
4. Многониточное программирование
конфликтовать между собой. То есть значения переменных одной нити должны быть независимы от значений переменных другой нити; •
каждая нить должна своевременно получать доступ к одному и тому же ресурсу системы, например к устройству вывода или последовательному порту, без конфликта с другими нитями. Рассмотрим подробнее, как создаются процедуры для нити, удовлетворяющие приведенным требованиям.
4.3.5. Пример создания нити Вернемся к нашей задаче отображения бегущих полос. Пусть создающая полосу переменной длины подпрограмма имеет заголовок subroutine run_bar(barcount) use bar_data integer(4) :: barcount
⎯ 139 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Используем подпрограмму start_bar, позволяющую создать до maxbars нитей. subroutine start_bar( ) ! Задание бегущих полос use bar_data integer(4) :: barvalue = 0, j ! barvalue - номер полосы integer(4), allocatable :: hthread(:) ! Массив обработчиков нитей character(1) :: keyvalue ! Процедура нити обладает атрибутом EXTERNAL external run_bar allocate(hthread(maxbars)) ! maxbars - максимально возможное число полос ! Зададим красный цвет для текста status4 = settextcolorrgb(rgbtointeger(255, 0, 0)) print *, 'Press A to create a Running Bar or Q to quit' do keyvalue = getcharqq( ) if(keyvalue == 'A' .or. keyvalue == 'a') then barvalue = barvalue + 1 if(barvalue > maxbars) exit ! Создание очередной нити (в нашем случае - бегущей полосы) hthread(barvalue) = CreateThread(0, 0, run_bar, barvalue, 0, j) end if if(keyvalue == 'Q' .or. keyvalue == 'q') exit end do do j = 1, barvalue ! Закрываем нити lret = CloseHandle(hthread(j)) end do end subroutine start_bar
4.3.6. Использование значения параметра argument В примере одна и та же процедура (подпрограмма run_bar) использована для реализации нескольких нитей. Чтобы задать для каждой нити разные начальные значения переменных, мы должны воспользоваться в процедуре нити (подпрограмма run_bar) значением формального параметра barcount, который ассоциируется с параметром barvalue приведенной в предыдущем разделе функции CreateThread. (При описании функции CreateThread этот параметр имел имя argument.) Воспользуемся в нашей задаче значением параметра barcount только для задания начальной y-координаты полосы и ее цвета (переменная color1). gbval = 100 + 30 * (barcount - 1) if(gbval > 255) gbval = 100
! barcount - номер полосы
⎯ 140 ⎯
4. Многониточное программирование
color1 = rgbtointeger(0, gbval, gbval) y = ys + (h + dbar) * (barcount - 1)
! Оттенок бирюзового цвета ! dbar - расстояние между полосами
4.3.7. Обеспечение независимости переменных процедуры нити По умолчанию переменные процедуры нити являются статическими, т. е. они разделяются между использующими процедуру нитями. Чтобы избежать такого совместного использования значений переменных, следует применить одну их трех возможностей: •
объявить переменные автоматическими;
•
создать для каждой нити свой вектор значений переменных;
•
создать динамические локальные переменные для каждой нити, использовав процедуры TlsAlloc, TlsGetValue, TlsSetValue, TlsFree. Мы же воспользуемся первым способом. Напомним, что переменные процедуры с атрибутом AUTOMATIC помещаются в создаваемый при вызове процедуры стек, который в случае процедуры нити является частью нити. При завершении процедуры автоматические переменные уничтожаются. В нашей процедуре объявление автоматических переменных будет таким: subroutine run_bar(barcount) use bar_data integer(4) :: barcount integer(2), automatic :: xf, x, y, dx integer(4), automatic :: color, color1, color2, gbval
4.3.8. Способы синхронизации нитей при доступе к ресурсам Многие устройства, например экран, являются последовательными, и их в каждый заданный момент времени может использовать только одна нить. Кроме того, поведение нити при использовании того или иного ресурса должно быть предсказуемым, т. е. таким, как это предусмотрено алгоритмом. Для достижения этих целей в многониточном программировании используется следующий подход: в программе создается синхронизирующий объект, которым поочередно могут владеть разные нити. Очередность владения объектом определяется планировщиком. Объект идентифицируется целым числом типа INEGER(4), называемым ⎯ 141 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
обработчиком объекта. Далее, перед тем как нить получит доступ к ресурсу, определяется нить, владеющая этим объектом, и именно она получает возможность использовать ресурс. После его использования объект освобождается от своего владельца. Могут быть созданы синхронизирующие объекты четырех видов: •
критическая секция;
•
исключение;
•
семафор;
•
событие. Принцип использования объектов таков. Перед кодом, содержащим операторы доступа к разделяемому ресурсу, размещается специальная процедура (EnterCriticalSection в случае критических секций или waitфункции в случае иных объектов), которая выполняет опрос объекта, и если он свободен (в этом случае объект подает сигнал), то нить, ожидающая ресурса, получает право на его использование, а объект получает владельца и перестает подавать сигнал. После использования ресурса объект вновь должен быть переведен в состояние, в котором он подает сигнал. Это выполняется специальной процедурой, например release-функцией, которая размещается вслед за кодом, содержащим операторы доступа к разделяемому ресурсу.
4.4. Программирование объектов синхронизации нитей 4.4.1. Критические секции Критическая секция используется для синхронизации нитей в случаях, когда они разделяют последовательный ресурс, например монитор или принтер. Такой ресурс в каждый заданный момент времени может быть использован только одной нитью. Объект - критическая секция имеет тип rtl_critical_section и может быть объявлен, например, так: use mt rtl_critical_section type(rtl_critical_section) rts
!
Модуль,
содержащий
тип
Прежде чем воспользоваться этим объектом для синхронизации нитей, его следует инициализировать, например, так: ⎯ 142 ⎯
4. Многониточное программирование
call InitializeCriticalSection(loc(rts))
Обратите внимание, что подпрограмма InitializeCriticalSection, как и все иные программы работы с критическими секциями, содержит в качестве параметра адрес объекта, который в Фортране возвращается функцией LOC. Синхронизация нитей при доступе к разделяемому ресурсу при помощи критической секции выполняется так: •
перед кодом, осуществляющим доступ к разделяемому ресурсу, вызывается подпрограмма EnterCriticalSection. Подпрограмма определяет нить, которая будет на данном этапе существования процесса владеть объектом - критической секцией и, следовательно, получит право доступа к разделяемому ресурсу;
•
вслед за последним оператором кода, выполняющим доступ к разделяемому ресурсу, вызывается подпрограмма LeaveCriticalSection, которая освобождает критическую секцию от текущего владельца. В нашей задаче приведенные подпрограммы будут использованы в подпрограмме run_bar таким образом:
subroutine run_bar(barcount) use bar_data integer(4) :: barcount integer(2), automatic :: xf, x, y, dx integer(4), automatic :: color, color1, color2, gbval gbval = 100 + 30 * (barcount - 1) ! barcount - номер полосы if(gbval > 255) gbval = 100 color1 = rgbtointeger(0, gbval, gbval) ! Оттенок бирюзового цвета y = ys + (h + dbar) * (barcount - 1) ! dbar - расстояние между полосами color2 = rgbtointeger(100, 100, 100) ! Темно-серый цвет xf = XW - xs; x = xs; dx = 5; color = color1 do call EnterCriticalSection(loc(rts)) ! Определяем владельца объекта status4 = setcolorrgb(color) ! Устанавливаем текущий цвет вывода status2 = rectangle($gfillinterior, x, y, x + dx, y + h) call LeaveCriticalSection(loc(rts)) ! Освобождаем критическую секцию call sleepqq(5) ! Задержка процесса на 5 мс x = x + dx if(x >= xf) then call beepqq(200, 5) color = color2; dx = - dx else if(x <= xs) then call beepqq(400, 5)
⎯ 143 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
color = color1; dx = - dx end if end do end subroutine run_bar
Обратите внимание, что все функции графического вывода (в программе это SETCOLORRGB и RECTANGLE) расположены между вызовами подпрограмм EnterCriticalSection и LeaveCriticalSection. Параметром подпрограммы, работающей с объектом - критической секцией, является адрес этого объекта. Подпрограмма run_bar при наличии в процессе нескольких нитей работает так: на каждой итерации расположенные между вызовами подпрограмм EnterCriticalSection и LeaveCriticalSection операторы выполняются для каждой из полос. Далее выполняется задержка в 5 мс всего процесса. Последующие операторы выполняются для каждой из нитей (полос). Если же необходимо выполнить задержку после вывода прямоугольника каждой полосы, то вызов call sleepqq(5) следует разместить перед вызовом подпрограммы LeaveCriticalSection: call EnterCriticalSection(loc(rts)) ! Определяем владельца объекта status4 = setcolorrgb(color) ! Устанавливаем текущий цвет вывода status2 = rectangle($gfillinterior, x, y, x + dx, y + h) call sleepqq(5) ! Задержка для каждой полосы call LeaveCriticalSection(loc(rts)) ! Освобождаем критическую секцию
Заметим, что задержку всего процесса лучше выполнять, используя объект исключение. Способы работы с такими объектами будут рассмотрены чуть ниже.
4.4.2. Текст программы вывода бегущих полос с использованием критической секции Уточним в приводимом ниже тексте код модуля bar_data и главной программы. module bar_data use mt ! Обязательная ссылка на модуль MT.MOD use dflib ! Модуль DFLIB содержит интерфейсы процедур integer(2) :: status2 ! и константы, необходимые для работы QuickWin integer(2) :: XW, YW ! Размер видеоокна в пикселях integer(2), parameter :: xs = 25, ys = 20, h = 50, dbar = 30 integer(4) :: maxbars ! Максимально возможное число полос
⎯ 144 ⎯
4. Многониточное программирование
integer(4) :: status4 определения logical(4) :: lret type(rtl_critical_section) rts
!
Значение
maxbars
найдем
после
! размера видеоокна YW ! Задаем объект - критическую секцию
contains subroutine set_window( ) ! Задает видеоокно и определяет его размеры type(qwinfo) :: qwin ! Вычисляет максимальное число выводимых type(windowconfig) :: wincon ! полос ! Начальные параметры видеоокна wincon%numxpixels = -1_2; wincon%numypixels = -1_2 wincon%numtextcols = -1_2; wincon%numtextrows = -1_2 wincon%numcolors = -1_2; wincon%fontsize = -1 wincon%title = 'Бегущие полосы'c ! Зададим заголовок видеоокна lret = setwindowconfig(wincon) lret = getwindowconfig(wincon XW = wincon%numxpixels ! Размер видеоокна по x (в пикселях) YW = wincon%numypixels ! Размер видеоокна по y (в пикселях) ! maxbars - максимально возможное число полос maxbars = (YW - ys - h) / (h + dbar) ! Раскроем обрамляющее окно до максимального размера qwin%type = qwin$max status4 = setwsizeqq(qwin$framewindow, qwin) ! Раскроем окно вывода до максимального размера; ! окно подсоединено к устройствам 0, 5 и 6 status4 = setwsizeqq(0, qwin) status4 = setbkcolorrgb(rgbtointeger(220, 220, 220)) call clearscreen($glearscreen) end subroutine set_window end module bar_data program bars2 use bar_data call set_window( ) размеры call InitializeCriticalSection(loc(rts)) call start_bar( ) полос) end program bars2 subroutine start_bar( ) ... end subroutine start_bar
! Зададим окно вывода и определим его ! Инициализация критической секции ! Вывод полос переменной длины (бегущих
! Задание бегущих полос ! Текст подпрограммы приведен в разд. 4.3.5
⎯ 145 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
subroutine run_bar(barcount) ... end subroutine run_bar
! Подпрограмма нити. Ее текст приведен ! в разд. 4.4.1
4.4.3. Устранение недостатка в работе программы BARS2 Программа имеет явный недостаток: после завершения DO-цикла в подпрограмме start_bar (например, в результате нажатия на клавишу Q) и закрытия всех нитей (выполняется функцией CloseHandle) не проиcходит выхода из DO-цикла подпрограммы run_bar и процесс отображения бегущих полос на экране продолжается. Для устранения этого недостатка создадим объект-исключение, владельцем которого будет основная нить, т. е. нить, создаваемая загрузчиком автоматически. Выполним эту операцию в главной программе: program bars3 use bar_data call set_window( ) размеры call InitializeCriticalSection(loc(rts)) obj2 = CreateMutex(0, .true., 0) call start_bar( ) lret = CloseHandle(obj2) end program bars3
! Зададим окно вывода и определим его ! Инициализация критической секции ! Создаем объект-исключение ! Вывод бегущих полос ! Закрываем объект obj 2
Добавим также в модуль bar_data объявление переменной obj2: integer(4) :: obj2
Объект-исключение создается функцией CreateMutex, второй параметр которой может принимать значение .TRUE. или .FALSE.. Если второй параметр равен .TRUE., то объект имеет владельца сразу после создания, и им владеет нить, в которой этот объект создан. В противном случае - второй параметр равен .FALSE. - объект не имеет владельца. В нашей программе объект obj2 имеет владельца, и им является основная нить. Используем в подпрограмме run_bar объект obj2 так: заменим вызов call sleepqq(5)
на оператор IF, содержащий функцию WaitForSingleObject if(WaitForSingleObject(obj2, 35) == wait_failed) exit
Функция ожидания WaitForSingleObject, поскольку она вызывается в процедуре нити, возвратит значение:
⎯ 146 ⎯
4. Многониточное программирование
•
WAIT_OBJECT_0, если объектом-исключением obj2 завладеет одна из нитей;
•
WAIT_TIMEOUT, если завершился интервал времени, указанный в миллисекундах в качестве второго параметра функции;
•
WAIT_FAILED в случае неудачи, которая может возникнуть, например, если объект obj2 закрыт (посредством функции CloseHandle) нитью, в которой он был создан. Константы WAIT_OBJECT_0, WAIT_TIMEOUT и WAIT_FAILED определены в модуле MT.MOD. Однако поскольку в нашей программе объектом obj2 владеет основная нить, то им не сможет завладеть другая нить и возвращаемое функцией значение будет равно WAIT_TIMEOUT. Такая величина будет возвращаться до тех пор, пока существует объект obj2. В программе после нажатия на клавишу Q или после задания всех возможных нитей произойдет возврат из подпрограммы start_bar в главную программу и затем будет выполнена функция lret = CloseHandle(obj2)
! Закрываем объект obj 2
После этого WaitForSingleObject(obj2, 35) вернет значение WAIT_ FAILED, что приведет к завершению DO-цикла подпрограммы run_bar. Таким образом, в приведенной программе функция WaitForSingleObject выполняет, во-первых, задержку процесса и, во-вторых, обеспечивает выход из DO-цикла процедуры нити. Замечание. Вместо условия if(WaitForSingleObject(obj2, 35) == wait_failed) exit
можно использовать условие if(WaitForSingleObject(obj2, 35) /= wait_timeout) exit
4.4.4. Исключения Объекты-исключения создаются для корректного обеспечения доступа нитей к ресурсам, которые могут одновременно работать с одной нитью или процессом. Примером такого ресурса является принтер. В этом плане объекты-исключения эквивалентны критическим секциям. Но в отличие от критических секций исключения могут быть использованы для синхронизации разных процессов. Объект-исключение создается функцией handle = CreateMutex(security, owner, string) ⎯ 147 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Параметр security, как и для функции CreateThread, мы будем устанавливать равным нулю. Если параметр owner равен .TRUE., то создаваемый объект имеет владельца сразу после создания и им владеет нить, в которой этот объект создан. Если второй параметр равен .FALSE., то объект не имеет владельца. string - задает имя создаваемого объекта. Объект создается неименованным, если string равен нулю. Параметры security и string имеют тип INTEGER(4). Тип параметра owner - LOGICAL(4). В случае успеха функция возвращает значение обработчика объекта. Тип результата - INTEGER(4). При неудаче функция возвращает нуль. Если создается именованный объект и объект (например, событие, семафор или объект-исключение) с таким же именем уже существует, функция GetLastError, вызванная вслед за функцией CreateMutex, вернет значение ERROR_ALREADY_EXISTS. В других случаях GetLastError возвращает нуль. Объект-исключение подает сигнал, если им не владеет ни одна из нитей. Чтобы нить завладела объектом, должна быть использована одна из waitфункций, например WaitForSingleObject или WaitForMultipleObjects. Когда wait-функция успешно завершается, нить, находящаяся в состоянии ожидания, получает возможность продолжить работу (получает право на использование разделяемого ресурса). В то же время объект-исключение, получивший владельца, перестает сигнализировать. Синтаксис wait-функции WaitForSingleObject result = WaitForSingleObject(handle, Mseconds) handle - значение обработчика объекта-исключения; тип параметра INTEGER(4). Mseconds - максимальное время, в течение которого объект ожидает владельца; тип параметра - INTEGER(4). Значение Mseconds может быть указано как WAIT_INFINITE или как INFINITE. В первом случае задается бесконечный интервал ожидания, а во втором - интервал ожидания равен #FFFFFFFF миллисекундам. Функция возвращает результат типа INTEGER(4). Описание возвращаемых значений приведено в предыдущем разделе. После того как нить выполнила необходимые при работе с разделяемым ресурсом операции, объект-исключение следует освободить от нитивладельца. Это выполняется функцией lresult = ReleaseMutex(handle) ⎯ 148 ⎯
4. Многониточное программирование
Параметром handle функции ReleaseMutex является обработчик освобождаемого объекта, значение которого возвращается функциями CreateMutex и OpenMutex. Функция возвращает значение типа LOGICAL(4), равное .TRUE. в случае удачи, и .FALSE. - в противном случае, например если выполняется попытка освобождения объекта от нити, которая этим объектом не владеет.
4.4.5. Применение исключений в рассматриваемой задаче Внесем в программу ряд изменений. В модуле bar_data, приведенном в разд. 4.4.2, вместо оператора type(rtl_critical_section) rts
! Задаем объект - критическую секцию
разместим оператор, объявляющий обработчики создаваемых объектовисключений: integer(4) :: obj1, obj2
! Обработчики объектов-исключений
В главной программе создадим объекты-исключения и разместим операторы вызова функций, закрывающих эти объекты после завершения вычислений: program bars4 use bar_data call set_window( ) размеры obj1 = CreateMutex(0, .false., 0) obj2 = CreateMutex(0, .true., 0) call start_bar( ) lret = CloseHandle(obj1) lret = CloseHandle(obj2) end program bars4
! Зададим окно вывода и определим его ! Создадим два объекта-исключения ! Вывод бегущих полос ! Закрываем объекты-исключения
Второй объект-исключение (обработчик obj2) создается, как и в модифицированной программе с критической секцией, для выполнения задержки в DO-цикле подпрограммы run_bar и для выхода из этого цикла после завершения вычислений. Этот объект, поскольку при его создании второй параметр равен .TRUE., имеет владельца сразу после создания, и им является основная нить. Первый объект-исключение (обработчик obj1) при создании не имеет владельца и используется для синхронизации нитей в подпрограмме run_bar при доступе к видеоадаптеру. Код, обеспечивающий синхронизацию, задержку и выход из цикла, выглядит так: do
⎯ 149 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
! Определяем нить, которая будет владеть объектом obj1 ! Эта нить получит доступ к видеоадаптеру status4 = WaitForSingleObject(obj1, wait_infinite) status4 = setcolorrgb(color) ! Задаем цвет и выводим очередной прямоугольник status2 = rectangle($gfillinterior, x, y, x + dx, y + h) lret = ReleaseMutex(obj1) ! Освобождаем объект от нити-владельца x = x + dx if(x >= xf) then call beepqq(200, 5); color = color2; dx = - dx else if(x <= xs) then call beepqq(400, 5); color = color1; dx = - dx end if ! Задержка на 35 мс и выход из цикла после завершения вычислений if(WaitForSingleObject(obj2, 35) /= wait_timeout) exit end do
Все остальные фрагменты программы остаются без изменений.
4.4.6. Семафоры Семафоры регулируют число нитей, которые могут использовать ресурс. Семафор может быть также использован в режиме объектаисключения или критической секции, которые позволяют только одной нити быть владельцем ресурса. Семафор создается функцией handle = CreateSemaphore(security, InitialCount, MaxCount, string) Параметр security, как и для функции CreateThread, мы будем устанавливать равным нулю. InitialCount - начальная установка счетчика семафора. Семафор подает сигнал, если InitialCount больше нуля, и не подает сигнала, если InitialCount равен нулю. Последнее означает, что ресурс используется максимально возможным числом нитей. Значение InitialCount должно быть меньше MaxCount. MaxCount - максимально возможное значение счетчика семафора (максимально возможное число нитей, которые могут владеть объектомсемафором). string - задает имя создаваемого объекта. Объект создается неименованным, если string равен нулю. Все параметры функции имеют тип INTEGER(4).
⎯ 150 ⎯
4. Многониточное программирование
В случае успеха функция CreateSemaphore возвращает значение обработчика объекта. Тип результата - INTEGER(4). При неудаче функция возвращает нуль. Если создается именованный объект и объект с таким же именем уже существует, функция GetLastError вернет ERROR_INVALID_HANDLE. В других случаях GetLastError возвращает нуль. Функции ожидания, например WaitForSingleObject или WaitForMultipleObjects, используются, как и в случае объекта-исключения, когда нити необходимо получить доступ к ресурсу. При успешном завершении функции ожидания значение параметра InitialCount уменьшается и нить получает доступ к ресурсу. Если успешно выполнена функция WaitForSingleObject, то InitialCount уменьшается на единицу. Величина, на которую уменьшается InitialCount в случае применения WaitForMultipleObjects, зависит от значения параметров функции. Значение InitialCount увеличивается на заданную величину в результате вызова функции ReleaseSemaphore, которая имеет синтаксис: lresult = ReleaseSemaphore(handle, ReleaseCount, lpPreviousCount) Параметр handle идентифицирует семафор. Значение handle возвращается функциями CreateSemaphore и OpenSemaphore. ReleaseCount - величина, на которую должно увеличиться значение счетчика семафора (число нитей, от владения которых должен освободиться семафор). ReleaseCount должен быть больше нуля и не должен увеличивать счетчик семафора до значения, превышающего максимально возможную для него величину, задаваемую при вызове функции CreateSemaphore. При наличии такой ошибки значение счетчика семафора не изменяется и функция возвращает .FALSE.. lpPreviousCount - указатель на переменную, содержащую предыдущее значение счетчика семафора. Если параметр не нужен, то его значение задается равным нулю. Функция ReleaseSemaphore возвращает значение типа LOGICAL(4), равное .TRUE. в случае удачи, и .FALSE. - в противном случае.
4.4.7. Применение семафоров в рассматриваемой задаче Модуль bar_data оставим без изменений. В главной программе создадим 2 семафора: program bars5 use bar_data
⎯ 151 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
call set_window( ) ! Зададим окно вывода и определим его размеры obj1 = CreateSemaphore(0, 1, 1, 0) ! Семафоры могут иметь по одному владельцу obj2 = CreateSemaphore(0, 1, 1, 0) ! Семафором obj2 владеет основная нить status4 = WaitForSingleObject(obj2, wait_infinite) ! Последние 2 оператора можно заменить одним: ! obj2 = CreateSemaphore(0, 0, 1, 0) call start_bar( ) ! Вывод бегущих полос lret = CloseHandle(obj1) ! Закрываем семафоры lret = CloseHandle(obj2) end program bars5
Назначение семафоров obj1 и obj2 такое же, как назначение созданных в программе bars4 объектов-исключений. Код, обеспечивающий синхронизацию, задержку и выход из цикла, выглядит так: do ! Определяем нить, которая будет владеть объектом obj1 ! Эта нить получит доступ к видеоадаптеру status4 = WaitForSingleObject(obj1, wait_infinite) ! Задаем цвет и выводим очередной прямоугольник status4 = setcolorrgb(color) status2 = rectangle($gfillinterior, x, y, x + dx, y + h) ! Увеличиваем счетчик семафора на единицу lret = ReleaseSemaphore(obj1, 1, 0) ... ! Задержка на 35 мс и выход из цикла после завершения вычислений if(WaitForSingleObject(obj2, 35) /= wait_timeout) exit end do
Все остальные фрагменты программы остаются без изменений.
4.5. Организация нитей при многооконном выводе Напишем и разберем программу, в которой каждая бегущая полоса выводится в своем собственном окне. При этом, так же как и в разобранном выше примере, вывод каждой полосы будет обеспечиваться самостоятельной нитью программы. Для синхронизации вывода используем критическую секцию. Число полос и, следовательно, число окон вывода ограничим константой maxbars, приняв maxbars = 7. ⎯ 152 ⎯
4. Многониточное программирование
Обратим внимание на некоторые особенности приводимой ниже программы. Окна вывода управляются созданным пользовательским меню, содержащим 3 пункта: Выход, Создать полосу, Удалить полосу (рис. 4.2). Создание меню обеспечивается функцией InitialSettings, которая автоматически вызывается при загрузке программы.
Рис. 4.2. Управляющее задачей меню
Заметим, что вызываемые из меню подпрограммы (exitbar, createbar и deletebar) должны в качестве параметра иметь логическую переменную (в программе - fl). Поскольку обработки переменной в названных подпрограммах не выполняется, то для компиляции без предупреждений в них предусмотрен оператор call unusedqq(fl), который, правда, не выполняется, поскольку следует после оператора RETURN. Изначально создаются 2 окна вывода (2 бегущие полосы). Далее пользователь имеет возможность либо добавить, либо удалить полосу. Вывод полосы обеспечивается самостоятельной нитью. Важно обратить внимание на то, что для обеспечения работы нити при многооконном выводе необходимо передать ее номер (формальный параметр barcount) в подпрограмму нити run_bar по значению. Это обеспечивается, во-первых, тем, что вместо ранее использованного вызова result = CreateThread(0, 0, run_bar, barvalue, create_suspended, j)
используется вызов, применяющий функцию %VAL: result = CreateThread(0, 0, run_bar, %val(barvalue), create_suspended, j)
Кроме того, в подпрограмме нити run_bar формальный параметр barcount снабжен атрибутом VALUE: integer(4) :: barcount !dec$attributes value :: barcount
! Параметр barcount передается по значению
⎯ 153 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Все это обеспечивает передачу параметра barcount по значению, т. е. так, как это выполняется для скалярных переменных в СИ. Напомним, что при передаче по значению в процедуру передается копия параметра, а при передаче по ссылке - его адрес. В программе продемонстрированы возможности использования функции многониточного программирования - SetThreadPriority. Она позволяет задать приоритет нити. Синтаксис функции: lresult = SetThreadPriority(hThread, nPriority) Чтобы иметь возможность задать приоритет, нить создается с параметром CREATE_SUSPENDED, т. е. первоначально нефункционирующая. Возможные значения приоритета приведены в табл. 4.1. Задающие приоритет константы находятся в модуле MT.MOD. Таблица 4.1. Значения приоритетов нитей Константа
Значение
THREAD_PRIORITY_ABOVE_NORMAL
1
THREAD_PRIORITY_BELOW_NORMAL
-1
THREAD_PRIORITY_HIGHEST
2
THREAD_PRIORITY_IDLE
-15
THREAD_PRIORITY_LOWEST
-2
THREAD_PRIORITY_NORMAL
0
THREAD_PRIORITY_TIME_CRITICAL
15
Возобновление ResumeThread.
функционирования
нити
выполнено
функцией
module bar_data2 use mt use dflib ! Модуль DFLIB содержит интерфейсы процедур integer(2) :: status2 ! и константы, необходимые для работы QuickWin integer(4) :: status4 ! В DVF следует задать USE DFLIB logical(4) :: lret ! blen - длина полосы integer(2), parameter :: xs = 25, ys = 20, h = 50, blen = 225 ! maxbars - максимально возможное число полос (окон вывода) integer(4), parameter :: maxbars = 7 ! barvalue - номер полосы, n_of_bars - число полос integer(4) :: barvalue, n_of_bars
⎯ 154 ⎯
4. Многониточное программирование
integer(4) :: hthread(2, maxbars) = 0 integer(4) :: obj2 type(rtl_critical_section) rts
! Массив обработчиков нитей ! hthread(1, barvalue) равен единице, если ! существует полоса с номером barvalue
contains subroutine createbar(fl) logical(4) :: fl integer(4) :: j type(qwinfo) :: winfo character(2) :: stbar if(n_of_bars == maxbars) return do barvalue = 1, maxbars ! Найдем barvalue - номер несуществующей полосы if(hthread(2, barvalue) == 0) exit end do if(n_of_bars == 0) then winfo%type = qwin$max ! Максимизируем обрамляющее окно status4 = setwsizeqq(qwin$framewindow, winfo) end if n_of_bars = n_of_bars + 1 write(stbar, '(i2)') barvalue ! Преобразование “число - строка” open(unit = barvalue, file = 'user', title = 'Полоса № ' // stbar) status4 = setbkcolorrgb(rgbtointeger(220, 220, 220)) call clearscreen($glearscreen) ! Расположим окна вывода рядом друг с другом status4 = clickmenuqq(qwin$tile) ! Процедура нити - модульная процедура run_bar hthread(1, barvalue) = CreateThread(0, 0, run_bar, %val(barvalue), create_suspended, j) ! hthread(2, barvalue) равен нулю, если окно закрыто hthread(2, barvalue) = 1 lret = SetThreadPriority(hthread(1, barvalue), thread_priority_below_normal) status4 = ResumeThread(hthread(1, barvalue)) return; call unusedqq(fl) end subroutine createbar subroutine run_bar(barcount) integer(4) :: barcount ! Параметр barcount передается по значению !dec$attributes value :: barcount integer(2), automatic :: xf, x, y, dx integer(4), automatic :: color, color1, color2, blueval blueval = 100 + 30 * (barcount - 1) ! barcount - номер полосы if(blueval > 255) blueval = 100 color1 = rgbtointeger(0, blueval, blueval) color2 = rgbtointeger(100, 100, 100) ! Темно-серый цвет
⎯ 155 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
y = ys; xf = xs + blen; x = xs; dx = 5; color = color1 do call EnterCriticalSection(loc(rts)) if(hthread(2, barcount) == 0) then ! Если полоса barcount закрыта call LeaveCriticalSection(loc(rts)) exit end if status4 = setactiveqq(barcount) ! Направим вывод на окно с номером barcount status4 = setcolorrgb(color) status2 = rectangle($gfillinterior, x, y, x + dx, y + h) call LeaveCriticalSection(loc(rts)) x = x + dx if(x >= xf) then call beepqq(200, 5); color = color2; dx = - dx else if(x <= xs) then call beepqq(400, 5); color = color1; dx = - dx end if if(WaitForSingleObject(obj2, 35) == wait_failed) exit end do end subroutine run_bar subroutine deletebar(fl) ! Удаление полосы. После удаления всех полос logical(4) :: fl ! выполним выход их программы integer(4) :: winnumber call EnterCriticalSection(loc(rts)) ! winnumber - номер находящегося в фокусе окна if(inqfocusqq(winnumber) == 0) then hthread(2, winnumber) = 0 close(winnumber) ! Закрываем окно winnumber n_of_bars = n_of_bars - 1 ! Уменьшаем число открытых окон if(n_of_bars > 0) status4 = clickmenuqq(qwin$tile) end if call LeaveCriticalSection(loc(rts)) if(n_of_bars == 0) call exit(0) ! Останов программы, если закрыты все окна return; call unusedqq(fl) end subroutine deletebar subroutine exitbar(fl) logical(4) :: fl lret = CloseHandle(obj2) call exit(0)
⎯ 156 ⎯
4. Многониточное программирование
return; call unusedqq(fl) end subroutine exitbar end module bar_data2 program bars7 use bar_data2 call InitializeCriticalSection(loc(rts)) obj2 = CreateMutex(0, .true., 0) n_of_bars = 0 call createbar(.false.) call createbar(.false.) call sleepqq(1000000) end program bars7 программы
! Число созданных полос ! Создадим две нити ! Задержка, необходимая для того, чтобы ! не произошла мгновенная остановка
function initialsettings( ) ! Формируем меню, управляющее окнами вывода use bar_data2 logical(4) :: initialsettings ! Создаем пункты меню lret = appendmenuqq(1, $menuenabled, '&Выход'c, exitbar) lret = appendmenuqq(2, $menuenabled, '&Создать полосу'c, createbar) lret = appendmenuqq(3, $menuenabled, '&Удалить полосу'c, deletebar) lret = appendmenuqq(4, $menuenabled, '&Окна'c, nul) lret = appendmenuqq(4, $menuenabled, '&Каскад'c, wincascade) lret = appendmenuqq(4, $menuenabled, '&Расположить все'c, wintile) lret = setwindowmenuqq(4) ! Теперь 4-й пункт меню будет содержать initialsettings = .true. ! список всех дочерних окон end function initialsettings
Пояснение. В программе bars7 использованы функции QuickWin: •
SETWSIZEQQ - задает позицию обрамляющего или дочернего окна;
•
CLICKMENUQQ - имитирует выполнение команды меню;
•
SETACTIVEQQ - делает активным дочернее окно (без размещения его в фокусе);
•
INQFOCUSQQ- определяет, какое окно находится в фокусе;
•
APPENDMENUQQ - добавляет пункт меню в конец меню и фиксирует процедуру, выполняемую при выборе данного пункта меню;
•
SETWINDOWMENUQQ - задает пункт меню, в котором отображается список открытых окон;
•
INITIALSETTINGS - инициализация QuickWin.
⎯ 157 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
4.6. Перечень многониточных процедур Список используемых при многониточном программировании процедур приведен в табл. 4.2. Часть процедур описана в настоящем разделе. Синтаксис не рассмотренных в настоящем разделе процедур можно найти в поставляемом с Фортраном руководстве программиста и документации по Win32 API. Таблица 4.2. Процедуры многониточного программирования Процедура
Тип
Назначение
CloseHandle
LOGICAL(4)
Закрывает обработчик объекта или нити
CreateEvent
INTEGER(4)
Создает объект-событие
CreateMutex
”
Создает объект-исключение
CreateProcess
LOGICAL(4)
Создает процесс
CreateSemaphore
INTEGER(4)
Создает объект-семафор
CreateThread
”
Создает нить
DeleteCriticalSection
Подпрограмма
Удаление критической секции
DuplicateHandle
LOGICAL(4)
Дублирует обработчик объекта
EnterCriticalSection
Подпрограмма
Находит владельца критической секции
ExitProcess
Подпрограмма
Выход из процесса
ExitThread GetCurrentProcess
”
Выход из процедуры-нити
INTEGER(4)
Возвращает значение обработчика текущего процесса
GetCurrentProcessId
”
Возвращает значение идентификатора текущего процесса
GetCurrentThread
”
Возвращает значение обработчика исполняемой нити
GetCurrentThreadId
”
Возвращает значение идентификатора исполняемой нити
GetExitCodeProcess
LOGICAL(4)
Получает значение статуса завершения указанного процесса
⎯ 158 ⎯
4. Многониточное программирование
GetExitCodeThread GetLastError
”
Получает значение статуса завершения указанной нити
INTEGER(4)
Возвращает код ошибки вызываемой нити
GetPriorityClass
”
Возвращает класс приоритета заданного процесса
GetThreadPriority
”
Возвращает приоритет заданной нити
InitializeCriticalSection
LeaveCriticalSection OpenEvent
Подпрограмма ”
Инициализация критической секции Освобождение критической секции от нити-владельца
INTEGER(4)
Возвращает значение обработчика существующего именованного объекта-события
OpenMutex
”
Возвращает значение обработчика существующего именованного объекта-исключения
OpenProcess
”
Возвращает значение обработчика существующего именованного объекта-процесса
OpenSemaphore
INTEGER(4)
Возвращает значение обработчика существующего именованного объекта-семафора
PulseEvent
LOGICAL(4)
Устанавливает объект-событие в состояние “подать сигнал” и после запуска находящихся в состоянии ожидания нитей переводит объектсобытие в состояние “не подавать сигнал”
ReleaseMutex
”
Освобождает объект-исключение от нити, владеющей этим объектом
ReleaseSemaphore
”
Освобождает объект-семафор от нити, владеющей этим объектом
ResetEvent
”
Устанавливает объект-событие в состояние “не подавать сигнал”
⎯ 159 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
ResumeThread
INTEGER(4)
Возобновляет функционирование нити
SetEvent
LOGICAL(4)
Устанавливает объект-событие в состояние “подать сигнал”
SetLastError
Подпрограмма
Устанавливает код последней ошибки для вызываемой нити
SetPriorityClass
LOGICAL(4)
Устанавливает класс приоритета заданного процесса
SetThreadPriority
”
Устанавливает значение приоритета заданной нити
SuspendThread
INTEGER(4)
Приостанавливает функционирование нити
TerminateProcess
LOGICAL(4)
Завершает заданный процесс и все его нити
TerminateThread
Подпрограмма
Завершает заданную нить
WaitForMultipleObjects
INTEGER(4)
Возвращает значение, когда один или все заданные объекты находятся в состоянии "подать сигнал" или когда закончился заданный промежуток времени
WaitForSingleObject
”
Возвращает значение, когда заданный объект находятся в состоянии "подать сигнал" или когда закончился заданный промежуток времени
⎯ 160 ⎯
5. Компиляция и построение программ 5.1. Назначение команды DF Программа может быть построена либо в среде Microsoft Visual Studio (VS), либо в результате использования команды DF или команд DF и LINK, которые вызываются из командной строки. Предметом дальнейшего рассмотрения будет команда DF. По существу эта команда запускает программу-драйвер, которая: • принимает имена файлов и опции, управляющие компиляцией и построением программы; • вызывает компилятор DVF и передает ему предназначенные для компиляции опции и имена компилируемых файлов; • передает построителю предназначенные для него опции и имена объектных, созданных компилятором, файлов; • передает построителю имена библиотек объектных файлов. Каждая из запускаемых командой DF программ (компилятор или построитель) либо генерируют результат, выдавая при необходимости те или иные предупреждения, либо генерируют сообщения о найденных ошибках, без исправления которых нельзя выполнить компиляцию и/или построение программы. Построитель исполняемых файлов может быть также запущен из командной строки и командой LINK. Перечень опций команды DF и их синтаксис можно просмотреть, задав в командной строке DF /? или DF /help. Аналогично осуществляется просмотр опций команды LINK.
5.2. Переменные окружения Если DVF установлен стандартным образом, например в директорию DVF5, то для запуска команды DF необходимо задать переменным окружения TMP или TEMP, PATH, INCLUDE и LIB следующие значения: set temp=c:\temp set tmp=c:\temp set path=d:\dvf5\shared~1\bin;d:\dvf5\vc\bin;d:\dvf5\df\bin;"%path%" set include=d:\dvf5\vc\include;d:\dvf5\df\include;"%include%" set lib=d:\dvf5\vc\lib;d:\dvf5\df\lib;"%lib%"
⎯ 161 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Переменные окружения PATH, INCLUDE и LIB можно занести в файл autoexec.bat при установке DVF. При необходимости окружение можно установить, выполнив поставляемый с DVF файл dfvars.bat. Кроме этих переменных окружения для хранения часто применяемых опций можно использовать переменную окружения, имеющую имя DF. Описание переменных окружения приведено в табл. 5.1. Таблица 5.1. Переменные окружения, используемые командой DF Переменная
Описание
PATH
Задает пути поиска EXE и других файлов
INCLUDE
Переменная используется командой NMAKE, компилятором исходных файлов и компилятором ресурсов. NMAKE и компилятор исходных файлов используют переменную INCLUDE для поиска INCLUDE-файлов, включаемых оператором INCLUDE, и модулей, на которые ссылается оператор USE. Компилятор ресурсов, например диалоговых окон, использует переменную INCLUDE для обнаружения #include и RCINCLUDE файлов
LIB
Построитель использует эту переменную для поиска LIBфайлов, содержащих объектные библиотеки. Их поиск также выполняется и в текущей директории, т. е. директории, из которой выполнен запуск команды DF или LINK. Если эта переменная не задана, то поиск LIB-файлов выполняется только в текущей директории
DF
Используется для хранения часто применяемых опций. Опции и файлы, заданные переменной окружения DF, добавляются в команду DF и обрабатываются командой до первой явно заданной в ней опции. Однако действие опции, заданной в переменной DF, можно подавить путем задания в командной строке одноименной опции с другими значениями параметров
TMP
Задает директорию, в которую записываются создаваемые компилятором и построителем временные файлы
TEMP
Компилятор и построитель заносят временные файлы в директорию, заданную переменной TEMP, если не определена переменная окружения TMP
⎯ 162 ⎯
5. Компиляция и построение программ
5.3. Формат команды DF Команда DF принимает опции компилятора и построителя и имена как входных, так и выходных файлов. Порядок следования опций в команде DF определяется правилами: • опции компилятора могут следовать в произвольном порядке; • опциям построителя, если они задаются, должно предшествовать ключевое слово /link. Сами же опции построителя располагаются в конце командной строки вслед за другими опциями. Общий вид команды DF: DF options [/link options] Параметр options задает опции компилятора или построителя; имеет в общем случает следующий формат: [/option[:arg]] [filenamme.ext] /option[:arg] задает либо действия, которые должны быть выполнены компилятором или построителем, либо специальные свойства входных или выходных файлов. Некоторые опции могут принимать в качестве arg несколько ключевых слов. Синтаксис, используемый при наличии одного ключевого слова: DF /warn:unused test.f90 В случае нескольких ключевых слов они помещаются в скобки и разделяются запятыми: DF /warn:(argument_checking, unused) test.f90 Пробелы между двоеточием и опцией и двоеточием и ключевым словом или списком ключевых слов недопустимы. Возможен альтернативный синтаксис команды, в котором вместо двоеточия используется знак равенства: DF /warn=(argument_checking, unused) test.f90 filenamme.ext - одно или несколько имен входных или выходных файлов. Если в текущей директории обработке подлежат все файлы, например исходные, то в команде DF достаточно задать *.f90. Перед именем файла, если он не может быть найден командой DF, должен быть указан ведущий к нему путь. Расширение файла задает тип файла и определяет, какой программе, компилятору или построителю будет передан тот или иной файл.
⎯ 163 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
5.4. Правила задания опций Опции компилятора и построителя задаются вслед за слешем (/). Написание опций компилятора может быть сокращенным, например вместо /alignment:commons допустима запись /align:commons. Опции построителя сокращать нельзя. Пробелы или знаки табуляции недопустимы при написании опции и ее параметров. Имена опций и их параметров нечувствительны к регистру. Построитель прежде использует опции, заданные переменной окружения LINK. Затем он использует опции, заданные в командной строке, в порядке их следования. Если опция повторяется с различными параметрами, то приоритет имеет последняя опция. Опции построителя применяются ко всему приложению и не могут быть заданы для отдельных входных файлов.
5.5. Входные и выходные файлы В качестве входных файлов команда DF обрабатывает исходные и объектные файлы; последние могут размещаться в библиотеках - LIBфайлах. Входные файлы передаются компилятору, если они имеют одно из следующих расширений: .f90, .for, .f, .fpp, .i, .i90, .inc, .h, .c или .cpp. Типичными расширениями исходных файлов Фортрана являются .f90, .for и .f. Входные файлы поступают построителю, если они снабжены расширением .lib, .obj, .o, .exe, .res, .rbj или .def. Обычно объектные файлы имеют расширение .obj, а библиотеки объектных файлов - .lib. Команда DF вырабатывает следующие выходные файлы: •
файл с исходным текстом (.fpp), если задана опция компилятора /keep;
•
объектный файл (.obj), если заданы опции /compile_only, или /keep, или /object;
•
файл динамически подключаемой библиотеки (.dll), если задана опция /dll и не опущена опция /compile_only;
•
файл модуля (.mod), если компилируется модуль, заданный оператором MODULE;
•
база данных программы (.pdb), если заданы опции /pdbfile или /debug:full;
•
листинг (.lst), если задана опция /list; ⎯ 164 ⎯
5. Компиляция и построение программ
•
файл просмотра (.sbr), если задана опция /browser. Команда DF, если не заданы опции /compile_only или /keep, генерирует один временный объектный файл из одного или более исходных файлов. Затем построитель создает из этого объектного файла один исполняемый EXE-файл.
5.6. Формирование имен выходных файлов •
Имя исполняемого EXE-файла: определяется именем исходного файла, если использована команда DF test.f90 После выполнения этой команды, если нет ошибок, сформируется исполняемый файл test.exe; объектный файл test.obj не создается. Команда DF test1.f90 test2.f90 test3.for создаст исполняемый test1.exe, определив его имя по имени первого входного файла;
•
задается опцией /exe команды DF: DF test.f90 /exe:result.exe
•
задается опцией /out построителя: DF test.f90 /link /out:result.exe Имена объектных файлов:
•
определяются именами исходных файлов, если задана опция /compile_only или /keep: DF /compile_only find_ab.f90 find_cd.f90 После выполнения этой команды, если нет ошибок, сформируются объектные файлы find_ab.obj и find_cd.obj;
•
задаются опцией /object команды DF: DF /compile_only find_ab.f90 find_cd.f90 /object:result.obj Объектные файлы не будут созданы, если в командной строке отсутствуют опции /compile_only и /keep.
5.7. Временные файлы Временные файлы создаются и компилятором и построителем. Они записываются в директорию, заданную переменной окружения TMP. Если ⎯ 165 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
такая переменная не определена, то временные файлы запишутся в директорию, которая задана переменной окружения TEMP. Если же не определена и эта переменная, то для временных файлов будет использована текущая директория. Обычно DVF уничтожает временные файлы. Однако этого не произойдет, если задана опция /keep. Чтобы узнать, куда записываются временные файлы, используйте опцию /verbose, например: DF test.f90 /verbose
5.8. Управление библиотекой объектных файлов Статической библиотекой объектных файлов можно управлять как из среды VS, так и командой LIB, запускаемой из командной строки. В среде VS для доступа к библиотеке необходимо, определяя тип нового проекта, выбрать Static Library. Библиотека объектных файлов называется статической потому, что содержащиеся в ней программные компоненты могут присоединяться к исполняемому файлу только при его построении. Программные единицы динамических библиотек (DLL), в отличие от статических, вызываются EXE-файлом в момент его исполнения. Вспомним некоторые приемы работы с пользовательской библиотекой объектных файлов в командной строке. Объектные файлы b.obj и c.obj будут добавлены в находящуюся в текущей директории библиотеку stalib.lib после выполнения команды LIB stalib.lib b.obj c.obj Если библиотеки stalib.lib не существует, то ее следует создать, применив команду LIB /out:stalib.lib b.obj c.obj после выполнения которой библиотека не только будет создана, но в нее попадут файлы b.obj и c.obj. Теперь в библиотеку stalib.lib можно добавлять другие объектные файлы. После включения в библиотеку объектные файлы следует с диска удалить. Содержимое библиотеки будет выведено на экран монитора после выполнения команды LIB /list stalib.lib После запуска команды ⎯ 166 ⎯
5. Компиляция и построение программ
LIB /list:info.txt stalib.lib состав библиотеки отобразится в файле info.txt. Объектный файл, например b.obj, извлекается из библиотеки в текущую директорию командой LIB /extract:b.obj stalib.lib Извлекаемый файл в библиотеке, разумеется, сохраняется. Команда LIB /remove:b.obj stalib.lib удалит объектный файл b.obj из библиотеки stalib.lib.
5.9. Варианты использования команды DF 5.9.1. Компиляция и построение с одним исходным файлом Пусть исходные тексты модулей, главной программы и процедуры размещены в файле example.f90. Различные варианты использования команды DF для этого случая опишем в табл. 5.2. Таблица 5.2. Результаты применения команды DF к одному файлу с исходным текстом Команда
Выходные файлы
DF example.f90 /compile_only
example.obj
DF example.f90 /compile_only /object:result1
result1.obj
DF example.f90
example.exe, example.pdb
DF example.f90 /object:example.obj
example.obj, example.exe, example.pdb
DF example.f90 /object
То же
DF example.f90 /object:result1
result1.obj, example.exe, example.pdb
DF example.f90 /exe:result1.exe
result1.exe, result1.pdb
DF example.f90 /link /out:result1.exe DF example.f90 /object:example /exe:result1.exe
"
“
example.obj, result1.exe, result1.pdb
DF example.f90 /object: /exe:result1
"
⎯ 167 ⎯
“
“
О. В. Бартеньев. Visual Fortran: новые возможности
DF example.f90 /object:result1 /exe:result1
result1.obj, result1.exe, result1.pdb
5.9.2. Применение переменной окружения DF Составим файл, например e.bat, в котором определим переменную окружения DF и зададим одноименную команду set DF=/debug:minimal /list DF example.f90 Тогда после запуска файла e.bat команда DF воспримет все заданные в переменной DF опции и, в частности, выдаст листинг в файле example.lst. Запустим тот же файл, прежде добавив в команду DF опцию /show:code, включающую в листинг машинный код программы. set DF=/debug:minimal /list DF example.f90 /show:code В третьем варианте файла e.bat показано, как действие опции /list:out2 команды DF перекрывает действие опции /list, заданной переменной окружения DF: set DF=/debug:minimal /list DF example.f90 /show:code /list:out2 После запуска файла e.bat файл листинга будет иметь имя out2.lst.
5.9.3. Компиляция и построение с несколькими исходными файлами Пусть в текущей директории находится 3 файла с исходным текстом a.f90, b.f90 и c.f90. Выходные файлы, создаваемые командой DF, когда она принимает несколько исходных файлов, опишем в табл. 5.3. Таблица 5.3. Результаты применения команды DF к исходным файлам Команда
Выходные файлы
DF a.f90 b.f90 c.f90 /compile_only
a.obj, b.obj, c.obj
DF *.f90 /compile_only
"
“
DF a.f90 b.f90 c.f90 /compile_only /object:abc
abc.obj
DF a.f90 b.f90 c.f90
a.exe, a.pdb
DF *.f90
"
DF modfile.f90 a.f90 b.f90 c.f90
“
“
mod2.mod, modfile.exe, modfile.pdb
⎯ 168 ⎯
5. Компиляция и построение программ
DF modfile.f90 a.f90 b.f90 c.f90 /exe:result.exe DF modfile.f90 a.f90 b.f90 c.f90 /link /out:result.exe
mod2.mod, result.exe, result.pdb "
“
“
Если компилируется несколько файлов и применяется опция /compile_only, то обязательно задавайте опцию /object с тем, чтобы компилятор в результате совместного анализа обрабатываемых файлов смог создать более оптимальный по производительности код. Если программа содержит тексты определенных оператором MODULE модулей и вы используете несколько команд DF, то прежде компилируются файлы с модулями, а затем программы, эти модули использующие. Когда же в подобной ситуации используется одна команда DF, то, задавая в ней файлы с исходным текстом, прежде размещайте имена файлов с модулями, а вслед - имена файлов с другими программными компонентами, например: DF modfile.f90 a.f90 b.f90 c.f90 Результат обработки набора команд приведен в третьей снизу строке табл. 5.3. Файл mod2.mod появится в текущей директории, если в файле modfile.f90 определен модуль mod2. Имя генерируемого исполняемого файла можно контролировать, что отражено в двух последних строках табл. 5.3.
5.9.4. Использование последовательности команд Пусть вновь в текущей директории размещено 3 файла с исходным текстом a.f90, b.f90 и c.f90. Получим объектный и исполняемый файлы, выполнив последовательность DF a.f90 b.f90 c.f90 /list:result.lst /compile_only /object:result.obj DF result.obj Первая команда выполняет также и генерацию листинга result.lst исходных файлов. Это обеспечивается опцией /list:result.lst. Вторая команда вызывает построитель, который, получая файл result.obj, создает файл result.exe. Исполняемый файл можно получить и выполнив несколько иные команды: DF a.f90 b.f90 c.f90 /list: /compile_only DF a.obj b.obj c.obj
⎯ 169 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Теперь первая команда создаст 3 файла с листингами a.lst b.lst c.lst и 3 файла с объектным кодом a.obj b.obj c.obj. Вторая команда, получив в качестве входных объектные файлы, сгенерирует файлы a.exe и a.pdb.
5.9.5. Подключение библиотек объектных файлов Построение исполняемого файла a.exe из одного исходного файла и файлов, содержащихся в статической объектной библиотеке stalib.lib, обеспечит команда DF a.f90 stalib.lib При необходимости можно подключить и несколько пользовательских библиотек, задавая привычным образом их имена в команде DF: DF a.f90 stalib.lib mylib.lib /exe:result.exe
5.9.6. Использование динамических библиотек Динамическую библиотеку можно создать как в среде VS, так и командой DF, запуская ее из командной строки. В среде VS для доступа к библиотеке необходимо, определяя тип нового проекта, выбрать DynamicLink Library. Код, включенный в динамическую библиотеку, становится доступным приложению во время его исполнения. Однако для этого в приложение компилятор должен занести соответствующие ссылки на DLL. Рассмотрим код главной программы simple, находящейся в файле a.f90: program simple real(4) a, b, c, d integer(4) :: k a = 10; b = 20; c = 30; d = 40; k = 5 call find_ab(a, b) call find_cd(c, d) print '(4f7.2, i4)', a, b, c, d, k read * end program simple
! Код подпрограмм find_ab и find_cd ! размещен в DLL
! 1.00 2.00 3.00 4.00 5 ! Ожидаем нажатия Enter
Разместим вызываемые из программы simple подпрограммы find_ab и find_cd соответственно в файлах b.f90 и c.f90. Запишем затем их коды в динамическую библиотеку test.dll. Для этого снабдим эти подпрограммы атрибутом DLLEXPORT, указывающим на то, что код экспортируется в DLL, и задающим имя экспортируемого символа. subroutine find_ab(a, b) !dec$attributes dllexport :: find_ab real(4) :: a, b
⎯ 170 ⎯
5. Компиляция и построение программ
a = 1.0; b = 2.0 end subroutine find_ab subroutine find_cd(c, d) !dec$attributes dllexport :: find_cd real(4) :: c, d c = 3.0; d = 4.0 end subroutine find_cd
Создание динамической библиотеки test.dll. выполним командой DF b.f90 c.f90 /dll /exe:test.dll в которой опции /libs:dll и /dll задают режим формирования однониточной динамической библиотеки, а опция /exe определяет ее имя. При отсутствии последней опции библиотека будет иметь имя b.dll, т. е. ее имя определится по имени первого компилируемого файла. Помимо файла test.dll, компилятор сформирует файлы test.pdb, test.exp и test.lib. Последний файл test.lib, используем в команде DF, формирующей исполняемый файл a.exe: DF a.f90 /libs:dll test.lib Опция /libs:dll указывает на то, что не найденные построителем процедуры должны быть взяты из динамических библиотек, а файл test.lib предоставляет компилятору код, необходимый для организации ссылок на DLL. Теперь все готово для запуска a.exe. Однако следует помнить, что если файл a.exe переносится в другую директорию, то вместе с ним должен быть перенесен и файл test.dll. В противном случае динамическая библиотека найдена не будет, возникнет ошибка выполнения и программа прекратит работу. Заметим, что использование DLL приводит к снижению размера кода приложения.
5.9.7. Компиляция и построение приложений с текстами программ на Фортране и СИ Рассмотрим 3 файла: a.f90, d.f90 и cfun.c. Первый и второй файлы содержат Фортран-программы, а третий - СИ-функцию. Состав файла a.f90: program simple interface subroutine find_ab(a, b) ! Интерфейс СИ-функции find_ab !dec$attributes stdcall, alias : '_find_ab@8' :: find_ab
⎯ 171 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
integer(4) :: a, b программе end subroutine find_ab end interface real(4) :: a, b, c, d integer(4) :: k a = 10; b = 20; c = 30; d = 40; k = 5 call find_ab(loc(a), loc(b)) call find_cd(c, d) print '(4f7.2, i4)', a, b, c, d, k end program simple
! a, b - адреса объявленных в главной ! вещественных переменных a, b
! Вызов СИ-функции find_ab ! Вызов подпрограммы Фортрана find_cd
Состав файла d.f90: subroutine find_cd(c, d) real(4) :: c, d c = 3.0; d = 4.0 end subroutine find_cd
Состав файла cfun.c: void __stdcall find_ab(float *a, float *b) { *a = 1.0; *b = 2.0; }
При совместном использовании разноязычных текстов необходимо определиться с соглашениями о вызовах. Используем соглашение STDCALL. Информация об используемом соглашении сообщается СИ-функции в ее заголовке: void __stdcall find_ab(float *a, float *b)
В главной программе simple, из которой вызывается СИ-функция find_ab, определим ее интерфейс, указав в нем как тип используемого соглашения, так и имя _find_ab@8, которое СИ-функция find_ab получит при компиляции. Используем для этих целей атрибуты STDCALL и ALIAS: interface ! Интерфейс СИ-функции find_ab subroutine find_ab(a, b) ! Определим псевдоним функции и тип соглашения !dec$attributes stdcall, alias : '_find_ab@8' :: find_ab integer(4) :: a, b ! a, b - адреса объявленных в главной программе end subroutine find_ab ! вещественных переменных a, b end interface
⎯ 172 ⎯
5. Компиляция и построение программ
Имя _find_ab@8 является вторым именем (псевдонимом) СИ-функции find_ab. Число 8, завершающее имя, говорит о том, что формальные параметры функции find_ab занимают 8-байтовый отрезок памяти. Поскольку формальными параметрами функции являются указатели, то при ее вызове из Фортран-программы в качестве фактических параметров следует использовать адреса соответствующих переменных, которые возвращаются встроенной функцией LOC и имеют тип INTEGER(4). Это обстоятельство учтено и при написании интерфейса функции find_ab, и при ее вызове: call find_ab(loc(a), loc(b))
! Вызов СИ-функции find_ab
Компиляцию СИ-функции выполним командой CL /c cfun.c Ее результатом будет файл cfun.obj. Компиляцию и построение исполняемого файла, обеспечивающего выполнение программы simple, осуществим командой DF a.f90 d.f90 cfun.obj /debug:none Результатом ее выполнения станет исполняемый файл a.exe. Чтобы выполнить все приведенные команды, переменные окружения должны обеспечить доступ как к вызываемым командам (CL и DF), так и ко всем необходимым для их выполнения библиотекам. В системе, использующей различные версии компиляторов, возможен такой вариант задания переменных окружения: set path=c:\msdev\bin;d:\dvf5\df\bin;d:\progs\vs\msdev98\bin;d:\progs\vc98\bin;"%path%" set include=d:\dvf5\df\include;d:\progs\vc98\include;"%include%" set lib=d:\dvf5\vc\lib;d:\dvf5\df\lib;d:\progs\vc98\lib;"%lib%"
Рассмотрим теперь случай, когда главная программа написана на СИ. Пусть по-прежнему из главной программы вызываются две процедуры: find_ab и find_cd (в СИ есть только один вид процедур - функции). Первую реализуем в виде функции СИ и запишем в файл cfun.c, а вторую составим на Фортране и разместим в файле d.f90. Главную программу, функцию main, поместим в файл cmain.c: #include <stdio.h> void main( ) { float a, b, c, d; int k; void find_ab(float *a, float *b); find_cd
// Опишем прототипы функций find_ab и
⎯ 173 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
extern void find_cd(float *c, float *d); a = 10; b = 20; c = 30; d = 40; k = 5; find_ab(&a, &b); // Передаем параметры a и b по ссылке find_cd(&c, &d); // Вызов подпрограммы Фортрана find_cd printf("\n%7.2f%7.2f%7.2f%7.2f%4d", a, b, c, d, k); }
Состав файла cfun.c: void find_ab(float *a, float *b) { *a = 1.0; *b = 2.0; }
Состав файла d.f90: subroutine find_cd(c, d) !dec$attributes c :: find_cd !dec$attributes reference :: c, d real(4) :: c, d c = 3.0; d = 4.0 end subroutine find_cd
! Подпрограмма вызывается из СИ-функции ! Определяем тип соглашения о вызове ! и указываем, что параметры передаются ! по ссылке
Интерес представляет написанная на Фортране подпрограмма find_cd, в которой дополнительно к стандартным средствам Фортрана определен, вопервых, тип соглашения о вызове - С и, во-вторых, указано, что фактические параметры передаются по ссылке. Эти действия обеспечиваются атрибутами C и REFERENCE: !dec$attributes c :: find_cd !dec$attributes reference :: c, d по ссылке
! Определяем тип соглашения о вызове ! и указываем, что параметры передаются
Заметим, что тот же эффект получится и при использовании атрибутов C и REFERENCE в одном объявлении: ! Определим тип соглашения о вызове и способ передачи параметров в подпрограмму find_cd !dec$attributes c, reference:: find_cd
Необходимость использования этих атрибутов обусловлена тем, что подпрограмма find_cd вызывается из СИ-функции, причем при вызове фактическими параметрами являются адреса переменных: find_cd(&c, &d); переменных c и d
//
Фактические
параметры
-
адреса
Можно, впрочем, как и ранее, воспользоваться соглашением STDCALL. Тогда прототип процедуры find_cd в функции main опишется так: extern void __stdcall find_cd(float *c, float *d);
⎯ 174 ⎯
5. Компиляция и построение программ
Изменится и раздел объявлений подпрограммы find_cd: subroutine find_cd(c, d) ! Подпрограмма вызывается из СИ-функции !dec$attributes stdcall, reference, alias : '_find_cd@8' :: find_cd real(4) :: c, d
При любом соглашении о вызовах получение файлов cmain.obj и cfun.obj обеспечит команда CL /c cmain.c cfun.c Построим и исполняемый файл d.exe: DF d.f90 /nodformain cmain.obj cfun.obj /debug:none Опция /nodformain означает, что главная программа написана на отличном от Фортрана языке программирования.
5.9.8. Оптимизация при компиляции и построении Явно максимальный уровень оптимизации генерируемого кода устанавливается опцией /optimize:4. Этот уровень задан в команде DF по умолчанию (если, правда, отсутствует опция /debug без ключевых слов). Причем наилучшие условия для межпроцедурной оптимизации существуют, когда все файла компилируются одной командой и не задана опция /compile_only или /keep. Столь же хорошие условия для генерации быстрого приложения можно создать и при наличии команд /compile_only и /keep, указав опцией /object для всех присутствующих в команде DF файлов единый файл с объектным кодом, например: DF /compile_only /object:result.obj /optimize:4 a.f90 b.f90 c.f90 Если же в последней команде опцию /object:result.obj не задать, то будут созданы 3 объектных файла: a.obj, b.obj и c.obj - и возможности для межпроцедурной оптимизации будут существенно снижены. Меньшие уровни оптимизации, задаваемые опцией /optimize, допустимы на этапе отладки. При их задании снижаются затраты на компиляцию файлов (по сравнении с уровнем /optimize:4), но также может упасть быстродействие приложения. Выпуская рабочую версию продукта, устанавливайте максимально возможный уровень оптимизации.
5.9.9. Команда DF, параметры которой хранятся в текстовом файле Разместим в текстовом файле sam.txt параметры - опции и имена файлов - команды DF: ⎯ 175 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
/compile_only /object:result.obj /optimize:4 a.f90 b.f90 c.f90 Для запуска команды DF с этими параметрами следует набрать DF @sam.txt Если параметры не умещаются на одной строке текстового файла, то их запись продолжается на следующей строке. Символы продолжения не используются.
5.9.10. Примеры ошибочного использования команды DF Команда DF a.f90 b.f90 /link c.f90 /out:result.exe ошибочна, поскольку после опции /link можно указывать только имена тех файлов, которые передаются построителю, а файл c.f90, так же как и файлы a.f90 и b.f90, должен поступить для обработки компилятору. Ошибочна и эта команда: DF a.f90 b.f90 c.f90 /out: result.exe поскольку в ней используется опция построителя /out, которая может следовать только после опции /link. Следующая команда ошибок не содержит: DF a.f90 b.f90 c.f90 /link /out: result.exe
5.10. Ограничения компилятора и построителя Составляя программу, учитывайте приведенные в табл. 5.4 предельные значения. Таблица 5.4. Предельные значения DVF Элемент языка
Ограничение
Число параметров в одном вызове подпрограммы или функции
255
Число измерений массива
7
Протяженность по одному измерению
2,147,483,647 или возможности компьютера
Символьные константы
2000 символов
Число символов, вводимых под управлением списка ввода/вывода
2048 символов
⎯ 176 ⎯
5. Компиляция и построение программ
Число строк продолжения
99
Общая вложенность DO и IF операторов
128
Размер переменной цикла
2,147,483,647 или возможности компьютера
Вложенность групп в спецификации формата
8
Длина строки исходного текста Фортран-программы
132 символа
Вложенность INCLUDE-файлов
10
Число меток в списке вычисляемого или назначаемого оператора GOTO
500
Число знаков в операторе
3000
Число именованных common-блоков
250
Число вложенных скобок в выражении
40
Число вложений для структур
20
Длина имени
31 символ
Общий объем данных, размер массивов и программ ограничен только возможностями компьютера, а точнее - имеющимся объемом адресуемого виртуального пространства, на котором создаются и/или запускаются приложения.
5.11. Перечень опций компилятора и построителя Опции компилятора задаются либо в командой строке, и тогда они должны предшествовать опции /link (если таковая имеется), либо в диалоговом окне среды VS, открываемом при выполнении цепочки Build Settings... Команда DF может содержать только имена файлов и запускаться без опций. В этом случае действуют установленные по умолчанию значения опций. Опции построителя, если они не опущены, должны следовать после опции /link: DF a.f90 mylib.lib /link /nodefaultlib Также их можно задать в команде LINK, используемой, наряду с командой DF, для построения приложений, например: ⎯ 177 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
LINK a.obj mylib.lib /nodefaultlib Опции компилятора с разбивкой по категориям приведены в табл. 5.5. В столбце Возможные значения курсивом выделены принимаемые по умолчанию значения. В приводимой далее табл. 5.6 даны описания опций построителя. Таблица 5.5. Опции компилятора Назначение опции
Опция
Возможные значения
Опции общего назначения Уровень отладки
/[no]debug:[kyeword]
/debug:minimal | partial | full; /debug:none или /nodebug
Уровень оптимизации
/[no]optimize:level
/optimize:1 | 2 | 3 | 4; /nooptimize
Формирование файла просмотра
/[no]browser[:filename]
/browser[:filename]; /nobrowser
Задание символа для условной компиляции
/define:symbol[:integer]
/define:symbol[:integer]
Управление объемом и типом предупреждений
/[no]warn:[kyeword]
/warn:alignments | noalignments | argument_checking | noargument | declarations | nodeclarations | errors | noerrors | general | nogeneral | uncalled | nouncalled | uninitialized | nouninitialized | all | requests; /nowarn или /warn:none
Опции, обеспечивающие совместимость Совместимость с Microsoft Fortran PowerStation 4.0 (FPS4)
/[no]fpscomp:[kyeword]
/fpscomp:[no]filesfromcmd | [no]general | [no]ioformat | [no]libs | [no]logicals | [no]symbols | all; /nofpscomp или /fpscomp:none
Использовать компилятор FPS4
/oldfps
/oldfps
⎯ 178 ⎯
5. Компиляция и построение программ
Неформатные файлы других систем
/convert:kyeword и /assume:[no]byterecl
/convert:big_endian | cray | ibm | little_endian | native | vaxd | vaxg; /assume:[no]byterecl
VMS
/[no]vms
/vms; /novms
Выбрать подключаемую библиотеку Фортрана 77
/[no]f77rtl
/f77rtl; /nof77rtl
Опции, используемые при отладке Уровень отладки
[no]debug
/debug:minimal | partial | full; /debug:none или /nodebug
Файл с отладочной информацией
/[no]pdbfile[:filename]
/pdbfile[:filename]; /nopdbfile
Компиляция файлов с буквой D в первой позиции строки
/[no]d_lines
/d_lines; /nod_lines
Внешние процедуры и пересылка параметров Установленные по /[no]iface:kyeword умолчанию соглашения о вызовах
/iface:cref | default | stdref | [no]mixed_str_len_arg; /iface:(default, mixed_str_len_arg)
Длина строки параметра
/[no]iface:mixed_str_len_arg
/[no]iface:mixed_str_len_arg
Интерпретация имен
/names:kyeword
/names:as_is | lowercase | uppercase
Добавлять подчеркивание перед внешними именами
/assume:[no]underscore
/assume:underscore | nounderscore
Опции операций с плавающей точкой Обработка арифметических исключений
/fpe:level
/fpe:0 | 1 | 2 | 3 | 4
Последовательность операций с плавающей точкой
/[no]fltconsistency
/fltconsistency; /nofltconsistency
Разновидность типа констант /[no]fpconstant с плавающей точкой
⎯ 179 ⎯
/fpconstant; /nofpconstant
О. В. Бартеньев. Visual Fortran: новые возможности
Работа с данными Выравнивание данных в общем блоке
/alignment:kyeword
/align:commons | nocommons | dcommons
Выравнивание данных производного типа
/alignment:[no]records
/align:records | norecords;
Разновидность типа целых и логических данных
/integer_size:size
/integer_size 16 | 32
Разновидность типа целых констант
/[no]intconstant
/intconstant | nointconstant
Разновидность типа вещественных и комплексных констант
/real_size:size
/real_size:32 | 64
Автоматические или статические данные
/[no]automatic или /[no]static
/automatic | noautomatic или /static | /nostatic
Единицы измерения спецификатора RECL неформатных файлов
/assume:[no]byterecl
/assume:byterecl | nobyterecl
Совместное использование /assume: одной области памяти [no]dummy_aliases формальными параметрами процедур
/assume:dummy_aliases | nodummy_aliases
Опции языка программирования Проверка следованию правилам стандарта
/std[:strict]
/std | /std:strict
Использование семантики Фортрана 66
/[no]f66
/f66 | nof66
Использование альтернативного синтаксиса оператора PARAMETER
/[no]altparam
/altparam | noaltparam
Задание свободного или фиксированного формата исходного текста
/[no]free или /[no]fixed
/free / nofree или /fixed / nofixed
⎯ 180 ⎯
5. Компиляция и построение программ
Длина строки в исходном файле с фиксированным форматом
/[no]extend_source[:size]
/extend_source; /extend_source:72 | 80 | 132; /noextend_source
Интерпретация имен
/names:kyeword
/names:as_is | lowercase | uppercase
Опции библиотек и объектных файлов Компиляция без построения
/compile_only или /c
/compile_only или /c
Главная программа написана не на Фортране
/nodfmain
/nodfmain
Рекурсивные процедуры
/[no]recursive
/recursive | norecursive
Включение имен библиотек /[no]libdir в объектный файл
/libdir | nolibdir
Типы библиотек, используемые для поиска ненайденных ссылок (вызовов процедур)
/libs:keyword
/libs:dll | dllmulti | multi | static | qwin | qwins
Начало опций построителя
/[no]link
/link | nolink
Создание динамической библиотеки
/dll
/dll
Создание приложения Windows
/winapp
/winapp
Создание карты построителя
/[no]map[:file]
/map[:file]; nomap
Опции листинга Создание листинга на Ассемблере
/[no]asmfile[:file] или /[no]asmattributes:keyword
/asmfile[:file] | noasmfile; /asmattributes:source | machine | all | none; /noasmattributes
Листинг исходного файла
/[no]list[:file]
/list[:file]; /nolist
Содержание листинга исходного файла
/show:keyword или /[no]machine_code
/show:code | include | map | nomap; /machine_code | /nomachine_code
⎯ 181 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Разные опции компилятора Предельное значение на число ошибок компиляции
/[no]error_limit[:count]
/error_limit[:count]; /noerror_limit; /error_limit:30;
Директория для размещения модулей
/module[:path]
/module[:path]
Путь для поиска модулей и /[no]include[:path] и /assume:[no]source_include INCLUDE-файлов
/include[:path] | noinclude; /assume:source_include | nosource_include
Имя исполняемого файла или библиотеки DLL
/exe[:filename]
/exe[:filename]
Имя объектного файла
/[no]object[:filename]
/object[:filename]; /noobject
Генерация файла просмотра
/[no]browser[:filename]
/browser[:filename]; /nobrowser
Выполнять только проверку синтаксиса
/[no]syntax_only
/syntax_only; /nosyntax_only
Отбивка строк исходного файла завершающими пробелами
/[no]pad_source
/pad_source; /nopad_source
Показать справочные данные
/help или /?
/help или /?
Вставить строку string в объектный файл
/V"string"
/V"string"
Задание пользовательского /[no]extfor:ext расширения файлов для компилятора
/extfor:ext; /noextfor
Задание пользовательского /[no]extfpp:ext расширения файлов для предпроцессора
/extfpp:ext; /noextfpp
Задание пользовательского /[no]extlink:ext расширения файлов для построителя
/extlink:ext; /noextlink
⎯ 182 ⎯
5. Компиляция и построение программ
Показывать или не отображать данные об авторских правах и о версии компилятора
/nologo; /what
Показывать информацию о /verbose ходе выполнения компиляции
/nologo; /what
/verbose
Оптимизация и генерация кода Уровень оптимизации
/[no]optimize:level
/optimize:1 | 2 | 3 | 4; /nooptimize
Встраивание процедур
/[no]inline:kyeword
/inline:none | manual | size | speed | all; /noinline
Раскрутка циклов
/unroll:count
/unroll:count (0 ≤ count ≤ 16)
Изменение порядка выполнения операций с плавающей точкой
/assume:[no]accuracy_sensitive
/assume:noaccuracy_sensiti ve | noaccuracy_sensitive
Проверка параметров встроенных процедур
/math_library:fast
/math_library:fast
Проверка вызовов процедур математической библиотеки
/math_library:check
/math_library:check
Опции предпроцессора Задание символа для условной компиляции
/define:symbol[:integer]
/define:symbol[:integer]
Проверки в процессе выполнения приложения Проверка границ массивов и строк
/check:[no]bounds
/check:bounds | nobounds
Проверка согласованности формата и данных
/check:[no]format
/check:format | noformat
Проверка согласованности длины формата
/check:[no]output_conversion
/check:output_conversion | nooutput_conversion
Проверка переполнения в операциях с целыми числами
/check:[no]overflow
/check:overflow | nooverflow
⎯ 183 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Проверка степенных выражений
/check:[no]power
/check:power | nopower
Проверка потери значимости в операциях с числами с плавающей точкой
/check:[no]underflow
/check:underflow | nounderflow
Замечание. Процессом компиляции можно также управлять и из программы, включая в нее директивы - специальные инструкции, которые сообщают компилятору, какие надо предпринять действия при компиляции программы. Перечень директив DVF приведен в прил. 1. Таблица 5.6. Опции построителя Опция
Назначение
/align:number
Задает выравнивание каждой секции в пределах линейного адресного пространства программы
/base:address | @filename,key
Задает базовый адрес программы
/comment:["]comment["]
Вставляет строку-комментарий в заголовок исполняемого файла или DLL
/debug
Создает отладочную информацию для исполняемого файла или DLL
/debugtype:cv | coff | both
Задает формат генерации отладочной информации
/def:filename
Передает файл определений построителю
/defaultlib:libraries
Добавляет одну или более библиотек в список библиотек, в которых построитель ищет внешние ссылки
/dll
Создает в качестве выходного файла библиотеку DLL
/entry:function
Устанавливает стартовый адрес исполняемого файла или DLL
/export
Экспортирует во внешний файл функцию из программы; опция имеет синтаксис: /export:entryname[=internalname][,@ordinal [,noname]] [,data]
/fixed
Сообщает операционной системе, что программа должна загружаться по своему основному адресу
⎯ 184 ⎯
5. Компиляция и построение программ
/force:[multiple | unresolved]
Сообщает построителю о необходимости создания исполняемого файла или DLL, даже если символ, на который выполняется ссылка, не определен или определен многократно
/heap:reserve,[commit]
Размер произвольно распределяемой памяти в байтах
/implib:filename
Задает имя импортируемой библиотеки, которую создает построитель, когда программа имеет экспортируемые процедуры или данные
/include:symbol
Сообщает построителю, что нужно добавить символ в таблицу символов
/incremental:yes | no
Определяет, как выполняется нарастающая генерация файла
/map[:filename]
Передает построителю директиву о генерации файла с картой построения
/nodefaultlib[:library]
Сообщает построителю о необходимости удалить заданные для поиска внешних ссылок библиотеки из списка таких библиотек
/noentry
Не дает построителю выполнять ссылку на _main в DLL
/nologo
Предотвращает отображение информации об авторских правах и версии построителя
/opt:{ref | noref}
Управляет уровнем оптимизации, которую осуществляет построитель
/order:@filename
Управляет порядком размещения данных в создаваемом файле
/out:filename
Меняет задаваемое по умолчанию имя генерируемого построителем файла
/pdb[:filename]
Задает имя файла (взамен имени, известного по умолчанию), содержащего отладочную информацию
/profile
Создает файл, который может быть использован программой profiler, анализирующей созданное приложение
/release
Вставляет контрольную сумму в заголовок исполняемого файла
/stack:reserve[, commit]
Задает размер стека в байтах
⎯ 185 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
/stub:filename
Подсоединяет DOS-программу к WIN32-программе
/subsystem
Сообщает операционной системе, как исполнять EXE-файл. Имеет синтаксис: /subsystem:{console | windows | native}[,major [.minor]]
/verbose[:lib]
Посылает информацию о ходе построения в выходное окно
/version:major[.minor]
Сообщает построителю о необходимости разместить информацию о версии программы в заголовке создаваемого им EXE или DLL файла
/warn:level
Задает объем выводимых построителем предупреждений (0 ≤ level ≤ 2)
5.12. Распределение опций построителя по категориям VS В среде VS доступ к опциям компилятора и построителя становится возможным после выполнения цепочки Build - Settings... Опции компилятора и построителя распределены по категориям. В табл. 5.7. перечислены все опции построителя, однако для опций, которые нельзя задать ни в одной из LINK-категорий, в столбце Категория указано: “Командная строка”. Для прочих опций в этом столбце приводится категория, выбрав которую можно задать или изменить значение опции. Опции, не попадающие в LINK-категории, могут быть заданы в закладке Custom Build, доступ к которой выполняется по той же цепочке: Build Settings... Таблица 5.7. Категории VS для опций построителя Опция
Категория VS
/align:number
Командная строка
/base:address | @filename,key
Output
/comment:["]comment["]
Командная строка
/debug
Debug
/debugtype:cv | coff | both /def:filename
"
Название опции в VS
– Base Address – General Debug Info Microsoft Format | COFF Format | Both Formats
Командная строка
⎯ 186 ⎯
–
5. Компиляция и построение программ
/defaultlib:libraries
"
“
–
/dll
"
“
–
/entry:function
Output
/export
Командная строка
/fixed
"
Entry-Point Symbol “
/force:[multiple | unresolved]
Customize
/heap:reserve,[commit]
Командная строка
/implib:filename
"
– – Force File Output
“
– –
/include:symbol
Input
Force Symbol Reference
/incremental:yes | no
Customize
Link Incrementally
/map[:filename]
Debug
Mapfile Name
/map /nodefaultlib[:library] /nodefaultlib
"
Generate Mapfile
Input
Ignore Libraries
"
Ignore All Default Libraries
/noentry
Командная строка
/nologo
Customize
/opt:{ref | noref}
Командная строка
/order:@filename /out:filename /pdb[:filename]
"
– Suppress Start up Banner
“
Customize "
– – Output File Name Program Database Name
/profile
General
Enable Profiling
/release
Командная строка
/stack:reserve[, commit]
Output
Stack Allocations
/stub:filename
Input
MS-DOS Stub File Name
/subsystem
Командная строка
–
–
/verbose[:lib]
Customize
Print Progress Messages
/version:major[.minor]
Output
Version Information
/warn:level
Командная строка
⎯ 187 ⎯
–
О. В. Бартеньев. Visual Fortran: новые возможности
5.13. Использование опций FPS в команде DF Пользователи FPS, привыкшие к синтаксису Фортрана Microsoft, могут продолжать применять опции FPS вместо опций или вместе с опциями DVF в команде DF. Соответствие между опциями FPS и DVF приведено в табл. 5.8. Отметим, что опции FPS чувствительны к регистру и, следовательно, должны записываться в команде в полном соответствии с табл. 5.8. Таблица 5.8. Соответствие между опциями командной строки FPS и DVF Опции FPS
Назначение
Опции DVF
Управление листингом /FA
Листинг на Ассемблере
/asmfile[:file]
/FAc
Листинг на Ассемблере c машинным кодом
/asmattributes:machine и /asmfile[:file]
/FAs
Листинг на Ассемблере c исходным кодом
/asmattributes:source и /asmfile[:file]
/FAcs
Листинг на Ассемблере c машинным и исходным кодом
/asmattributes:all и /asmfile[:file]
/Fa[file]
Вывод листинга на Ассемблере в файл
/noasmattributes и /asmfile[:file]
/Fc[file]
Вывод листинга на Ассемблере c машинным и исходным кодом в файл
/asmattributes и /asmfile[:file]
/Fl[file]
Вывод листинга на Ассемблере c машинным кодом в файл
/asmattributes(nosource, machine) и /asmfile[:file]
/Fs[file]
Листинг исходных файлов с откомпилированным кодом
/list[:file] и /show:map
Генерация кода /FR[file]
Генерация файла просмотра
/browser[:file]
/Ob2
Автоматическое встраивание кода; используется совместно с /Ox
/inline:speed
/Od
Отказ от оптимизации кода
/optimize:0 и /math_library:check
⎯ 188 ⎯
5. Компиляция и построение программ
/Op
Улучшение последовательности выполнения операций с плавающей точкой
/fltconsistency
/Ox
Полная оптимизация без проверки ошибок
/optimize:4 и /math_library:nocheck
/Oxp
Оптимизация скорости, встраивание процедур, проверка ошибок
/optimize:4 и /fltconsistency
/Zp[n]
Упаковка структур по n-байтовой границе (n - это 1, 2 или 3)
/alignment[:keyword]
Расширения языка /4Lnn
Длина строки в файле с исходным кодом (nn - это 72, 80 или 132)
/extended_source[:nn]
/4Yb или /4Nb
Задать или отключить расширенную проверку ошибок
/check[:keyword]
/4Yb или /4Nb
Предупреждения о необъявленных переменных
/warn:[no]declarations
/W0
Отказ от предупреждений
/nowarn
/W1
Выводить предупреждения
/warn:general
/WX
Интерпретировать все предупреждения как ошибки
/warn:(general,errors)
Стандарты языка, исходный код и данные /4Ya или /4Na
Все переменные будут автоматическими или статическими
/[no]automatic или /[no]static
/4Yaltparam /4Naltparam
Использование альтернативного синтаксиса оператора PARAMETER
/[no]altparam
/4Yf или /4Nf
Задание свободного формата исходного кода
/[no]free; /[no]fixed
/4fps1
В DVF игнорируется
/4I2
Установить задаваемую по умолчанию разновидность типа целых и логических величин равной двум (KIND = 2)
⎯ 189 ⎯
– /integer_size::16
О. В. Бартеньев. Visual Fortran: новые возможности
/4R8
Установить задаваемую по умолчанию разновидность типа вещественных величин равной восьми (KIND = 8)
/real_size:64
/4Ys или /4Ns
Строгое следование последнему стандарту Фортрана
/std:strict
Директивы условной компиляции /Dsymbol[=int]
Задание символа, используемого при условной компиляции
/define:symbol[=int]
/4ccd
Обрабатывать строки с буквой d или D в первой позиции как комментарий
/d_lines
/4Yportlib или /4Nportlib
Автоматическое подсоединение к обеспечивающей переносимость программ библиотеке
/4Yportlib или /4Nportlib
/Fd[file]
Создание или отказ от создания PDB-файла
/[no]pdb[:filename]
/Fe[file]
Задание имени EXE или DLL файла
/exe[:filename]
/Fm[file]
Создание файла с картой работы построителя
/map[:filename]
/Fo[file]
Задание имени объектного файла
/object[:filename]
/Gna
Оставлять внешние имена без изменений и обрабатывать имена, содержащиеся в исходном коде, как чувствительные к регистру
/names:as_is
/GNl
Записать внешние имена строчными буквами и игнорировать регистр в именах, содержащихся в исходном коде
/names:lowercase
/GNu
Записать внешние имена прописными буквами и игнорировать регистр в именах, содержащихся в исходном коде
/names:uppercase
/Ipath
Задает путь поиска модулей и включаемых файлов
/[no]include[:path]
⎯ 190 ⎯
5. Компиляция и построение программ
/LD
Создание динамической библиотеки
/dll
/MD
Построитель использует многониточные динамические библиотеки
/libs:dllmulti
/MDs
Построитель использует однониточные динамические библиотеки
/libs:dll
/MG
Построитель использует библиотеки для Windows-приложений
/winapp
/ML
Построитель использует однониточные статические библиотеки
/libs:static
/MT
Построитель использует многониточные статические библиотеки
/libs:multi
/MW
Создание приложения QuickWin
/libs:qwin
/MWs
Создание приложения со стандартной графикой
/libs:qwins
/Tffile
Указывает, что файлы являются файлами Фортрана с исходным текстом
/source:filename
/V"string"
Помещает строку string в объектный файл
/V"string"
/Z7
Задает режим выдачи всей отладочной информации и размещает ее в объектном файле
/dubug:full и /nopdbfile
/Zd
Задает режим выдачи минимальной отладочной информации
/dubug:minimal и /pdbfile
/Zi
Задает режим выдачи всей отладочной информации и размещает ее в PDB-файле
/dubug:full и /pdbfile
/Zl
Отменяет запись имен библиотек в объектный файл
/nolibdir
/Zs
Выполняется только проверка синтаксиса
/syntax_only
⎯ 191 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
/link [option]
Все следующие за этой опцией параметры передаются построителю
/link [option]
Специальные опции /?, /help
Выдает справочную информацию об опциях компилятора и построителя
/?, /help
/nologo
Не отображаются данные об авторских правах
/nologo
⎯ 192 ⎯
6. Повышение быстродействия программ 6.1. Введение Быстродействие программы определяется следующими факторами: •
разработанным алгоритмом и выбранными структурами данных;
•
качеством программирования (кодирования) алгоритма;
•
способом и параметрами компиляции (compile) исходного кода и построения (link) исполняемого файла. Ниже будут рассмотрены приемы, позволяющие оптимизировать быстродействие программы по второму и третьему факторам. Анализируя программу на предмет оценки ее быстродействия, следует прежде выявить фрагменты, в которых могут происходить существенные потери времени. К ним относятся прежде всего циклы, анализу которых далее уделяется наибольшее внимание. Впрочем, не следует пренебрегать и иными фрагментами программы. Логичным будет такой подход к записи исходного кода: программист вырабатывает стиль программирования, учитывающий все факторы, влияющие на быстродействие программы, и придерживается этого стиля в процессе работы над каждым ее фрагментом. Разумеется, стиль программирования должен постоянно совершенствоваться.
6.2. Время выполнения программы Время выполнения программы или ее фрагмента определяется приводимой ниже функцией timer, которая использует встроенную подпрограмму DATE_AND_TIME. Задача. Сравнить время выполнения вычислений суммы и произведения элементов векторов, имеющих один размер. Тип векторов REAL(4) и REAL(8). Выполнить вычисления, первоначально используя циклы, а затем встроенные функции SUM и PRODUCT. program find_time integer(4), parameter :: n = 100000 ! Размер массивов array4 и array8 integer(4), parameter :: m = 100 ! Число вычислений сумм и произведений integer(4) :: i, j real(4), dimension(n) :: array4 = 1.000001 real(8), dimension(n) :: array8 = 1.00000123456789_8
⎯ 193 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
real(4) :: s4, p4 real(8) :: s8, p8 real(8) :: start_time4, finish_time4 ! Время начала и завершения вычислений real(8) :: start_time8, finish_time8 ! соответственно для типов REAL(4) и REAL(8) real(8) :: timer ! Функция, возвращающая процессорное время ! в миллисекундах, имеет тип REAL(8) start_time4 = timer( ) ! Начало вычислений s4 и p4 для типа REAL(4) do j = 1, m ! Сумму и произведение вычисляем m раз s4 = 0.0; p4 = 1.0 do i = 1, n ! Используем цикл s4 = s4 + array4(i) p4 = p4 * array4(i) end do end do finish_time4 = timer( ) ! Конец вычислений s4 и p4 для типа REAL(4) start_time8 = timer( ) ! Начало вычислений s8 и p8 для типа REAL(8) do j = 1, m ! Сумму и произведение вычисляем m раз s8 = 0.0_8; p8 = 1.0_8 do i = 1, n s8 = s8 + array8(i) p8 = p8 * array8(i) end do end do finish_time8 = timer( ) ! Конец вычислений s8 и p8 для типа REAL(8) print 123, '(4)', finish_time4 - start_time4 print 123, '(8)', finish_time8 - start_time8 print *, 'Run time with functions SUM and PRODUCT' start_time4 = timer( ) ! Начало вычислений s4 и p4 для типа REAL(4) do j = 1, m ! Сумму и произведение вычисляем m раз s4 = sum(array4) ! Используем встроенную функцию SUM p4 = product(array4) ! Используем встроенную функцию PRODUCT end do finish_time4 = timer( ) ! Конец вычислений s4 и p4 для типа REAL(4)
⎯ 194 ⎯
6. Повышение быстродействия программ
start_time8 = timer( ) ! Начало вычислений s8 и p8 для типа REAL(8) do j = 1, m ! Сумму и произведение вычисляем m раз s8 = sum(array8) p8 = product(array8) end do finish_time8 = timer( ) ! Конец вычислений s8 и p8 для типа REAL(8) print 123, '(4)', finish_time4 - start_time4 print 123, '(8)', finish_time8 - start_time8 123 format(1x, 'Run time for REAL', a3, f10.3) end program find_time function timer( ) ! Определение процессорного времени timer real(8) :: timer ! в миллисекундах integer(4) :: ival(8) call date_and_time(values = ival) timer = dble(ival(8))*0.001_8+dble(ival(7))+dble(ival(6))*60.0_8+dble(ival(5))*3600.0_8 end function timer
Замечание. В Фортране 95 процессорное время возвращает встроенная подпрограмма CPU_TIME (см. прил. 4), которой также можно воспользоваться для оценки времени вычислений.
6.3. Выравнивание данных 6.3.1. Размещение данных в памяти Размещение данных в оперативной памяти влияет на время доступа к ним и, следовательно, на быстродействие программы. С позиции быстродействия размещение подразделяется на выравненное и невыравненное. При выравненном размещении начальный адрес каждого скаляра кратен параметру разновидности его типа. (Напомним, что единицей измерения адреса памяти является байт.) Например, скаляр типа REAL(8) выравнен по своей естественной границе, если его адрес кратен восьми, в противном случае размещение скаляра будет невыравненным. Массив будет выравненным, если выравнены все его элементы. Понятно, что всегда выравнены данные стандартного символьного типа, имеющие параметр разновидности KIND, равный единице. Для увеличения быстродействия размещение данных должно быть выравненным. ⎯ 195 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
6.3.2. Невыравненные данные Digital Visual Fortran (DVF) по возможности выравнивает данные. Однако в операторах EQUIVALENCE, COMMON, TYPE - END TYPE и STRUCTURE - END STRUCTURE могут появляться невыравненные данные. Рассмотрим common-блок a1: logical(2) :: flag integer(4), dimension(3) :: iarray character(len = 7) string1 common/a1/ flag, iarray, string1
Расположение данных этого блока отобразим на рис. 6.1. flag 0
iarray(1) 2
iarray(2) 6
iarray(3) 10
s
t
r
i
n g 1
14
19
Рис. 6.1. Размещение элементов common-блока а1
Из рис. 6.1 видно, что адрес элементов массива iarray не кратен четырем, т. е. массив невыравнен. Чтобы избавиться от этого недостатка, нужно выполнить компиляцию программы с опцией /align:commons или /align:dcommons. В случае /align:commons выравнивание данных выполняется по 4-байтовой границе и компилятор добавит после байтов, занятых переменной flag, 2 пустых байта. Результат отображен на рис. 6.2. Компиляция с опцией /align:dcommons обеспечит выравнивание данных по 8-байтовой границе. flag 2
iarray(1) iarray(2) iarray(3) 4
6
10
s
t
r
i
n
g
14
1 21
Рис. 6.2. Выравненные данные common-блока а1
Однако правильнее добиться эффекта расположение данных в общем блоке:
выравнивания,
изменив
common/a2/ iarray, flag, string1
В этом случае прибегать при компиляции к опции /align:commons не придется: данные будут выравнены естественным образом (рис. 6.3). iarray(1)
iarray(2) 4
iarray(3) 8
12
flag
s
t
r
i
n
g
14
Рис. 6.3. Естественное выравнивание элементов common-блока а2
⎯ 196 ⎯
1 19
6. Повышение быстродействия программ
Скаляры производного типа DVF выравнивает по 8-байтовой границе. Рассмотрим объявления: type thing integer(4) :: thing_id real(4) :: price character(10) :: descr end type thing type(thing) :: item = thing(55, 27.98, 'furcoat')
Каждый элемент массива item типа thing займет приведенный на рис. 6.4 отрезок памяти, в котором последние 6 байт будут добавлены DVF для выравнивания данных. Thing_id
price 4
f
u r
c o a
t
8
18
24
Рис. 6.4. Отрезок памяти под скаляр производного типа item
Однако если при объявлении производного типа указан оператор SEQUENCE, то DVF не сможет добавить байты, необходимые для выравнивания данных, что приведет к снижению быстродействия. Следовательно, в этом случае программист сам при формировании производного типа должен позаботиться о выравнивании данных.
6.3.3. Сообщения о невыравненных данных Невыравненные данные обнаруживаются компилятором, который, если не задана опция /warn:noalignments, предупреждает об обнаруженных дефектах. Так, при компиляции программы, содержащей приведенный выше common-блок /a1/, компилятор выдаст размещенное на двух строчках сообщение: Warning: Alignment of variable or array is inconsistent with its type [IARRAY] integer(4), dimension(3) :: iarray
6.3.4. Как выравнивать данные Придерживайтесь следующих рекомендаций: •
не используйте оператор EQUIVALENCE, заменяя его автоматическими или размещаемыми массивами или ссылками, когда стоит задача экономии памяти, ссылками для создания псевдонимов и встроенной функцией TRANSFER для отображения одного типа данных в другой; ⎯ 197 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
•
•
•
•
минимизируйте использование common-блоков, заменяя обеспечиваемое ими ассоциирование памяти на use-ассоциирование, т. е. размещайте предназначенные для общего использования данные в модулях; не передавайте объекты производного типа в процедуры через параметры, а размещайте их в модулях и заменяйте ассоциирование параметров use-ассоциированием. Это позволит избежать использования оператора SEQUENCE, который нужно применять при объявлении производных типов, если объекты этих типов передаются в процедуры через их параметры; не используйте для объявления производных типов операторы STRUCTURE - END STRUCTURE, которые являются расширением DVF над стандартами Фортрана, заменяя их на операторы TYPE - END TYPE; при работе с common-блоками (если вы не отказались от их использования), с производными типами данных, чтобы добиться выравнивания данных, в первую очередь размещайте объекты с наибольшей разновидностью типа, например:
complex(8) :: z real(8) :: aval, array(20) integer(4) :: iarray(30) character(15) :: string common/a3/ z, aval, array, iarray, string
! Параметр разновидности типа KIND = 8 ! Параметр разновидности типа KIND = 4 ! Параметр разновидности типа KIND = 1
Следствие. При использовании в операторах COMMON, TYPE - END TYPE и STRUCTURE - END STRUCTURE смеси числовых и символьных данных прежде размещаются числовые данные.
6.3.5. Опции компилятора, управляющие выравниванием В common-блоках и производных типах данных выравнивание управляется приведенными в табл. 6.1 опциями. DVF выравнивает данные, добавляя при необходимости после размещения очередного элемента пустые байты подобно тому, как это представлено на рис. 6.1.
⎯ 198 ⎯
6. Повышение быстродействия программ
Таблица 6.1. Опции компилятора, отвечающие за выравнивание данных Опция
Действие
/align:commons
Выравнивает данные в common-блоках по 4-байтовой границе, если на задана опция /fast. По умолчанию используется опция /align:nocommons
/align:dcommons
Выравнивает данные в common-блоках по 8-байтовой границе, если на задана опция /fast. По умолчанию используется опция /align:nocommons
/align:norecords
Отменяет естественное выравнивание данных производных типов, размещая их по 1-байтовой границе. По умолчанию используется опция /align:records, если опущена опция /vms. Если задана опция /vms и в явном виде не указана опция /align:records, то применяется /align:norecords
/align:records
Обеспечивает естественное выравнивание данных. Необходимость в ее применении может возникнуть, если применяется опция /vms
/vms
Обеспечивает генерацию кода, пригодного для использования в OpenVMS VAX-системе, ранее называемой VAX FORTRAN. В части выравнивания данных устанавливает опцию /align:norecords, т. е. для выравнивания потребуется применения опции /align:records. По умолчанию действует опция /novms
6.4. Оптимизация исходного кода 6.4.1. Эффективное использование массивов При работе с массивами следует по возможности избегать циклов. Рассмотрим массив array: real(4), dimension(100, 100) :: array = 0.0
Придерживайтесь таких рекомендаций: 1) используйте, заменяя циклы, присваивание массивов: array = array + 1.0 array(1:50, :) = -4.0
! И так далее
2) применяйте вывод всего массива, и желательно в двоичный или неформатный файл: open(10, file = 'a.dat', form = 'binary')
⎯ 199 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
write(10) a
! Вывод всего массива
Замечание. Аналогичным образом работайте с массивом-компонентом производного типа, например: type points integer(4) :: npoints real(4), dimension(2, 100) :: coords end type points type(points) :: curve = points(85, 20.0) curve%coords = curve%coords * 0.5 write(10) curve%coords
3) если двумерный массив используется во вложенном цикле, во внешнем цикле меняйте индекс столбца, а во внутреннем - индекс строки. Это обеспечит естественный и, следовательно, более быстрый доступ к элементам массива, поскольку в Фортране они размещаются в памяти ЭВМ по столбцам, а не по строкам, как это предусмотрено, например, в СИ. Замерим время вычислений при правильной и неоптимальной организации вложенных циклов. program toappr integer(4), parameter :: n = 1000 real(4), dimension(n, n) :: array1, array2 real(8) :: start_time1, finish_time1 ! Время начала и завершения вычислений real(8) :: start_time2, finish_time2 ! соответственно при правильной и неоптимальной ! организации вложенных циклов real(8) :: timer ! Функция, возвращающая процессорное время ! в миллисекундах, имеет тип REAL(8) array1 = 1.1; array2 = 1.5 ! Инициализация массивов ! Правильная организация вложенного цикла обеспечивает естественный доступ ! к элементам массива по столбцам start_time1 = timer( ) ! Начало вычислений do j = 1, n ! Правильно: сначала задается столбец, do i = 1, n ! а затем меняется индекс строки array1(i, j) = array1(i, j) + array2(i, j) * 3.3 end do end do finish_time1 = timer( ) ! Конец вычислений array1 = 1.1; array2 = 1.5 ! Повторная инициализация массивов ! Неоптимальная организация вложенного цикла - неестественный доступ
⎯ 200 ⎯
6. Повышение быстродействия программ
! к элементам массива по строкам start_time2 = timer( ) ! Начало вычислений do i = 1, n ! Так программировать не надо. Обеспечьте do j = 1, n ! доступ к элементам массива по столбцам array1(i, j) = array1(i, j) + array2(i, j) * 3.3 end do end do finish_time2 = timer( ) ! Конец вычислений print *, (finish_time1 - start_time1) / (finish_time2 - start_time2) ! Результат: 0.352941176460518 end program toappr function timer( ) ! Определение процессорного времени timer real(8) :: timer ! в миллисекундах integer(4) :: ival(8) call date_and_time(values = ival) timer = dble(ival(8))*0.001_8+dble(ival(7))+dble(ival(6))*60.0_8+dble(ival(5))*3600.0_8 end function timer
Замечание. При построении используемого для замеров исполняемого файла была установлена конфигурация Release, в которой полностью отсутствует отладочный код, т. е. задана опция /debug:none. Аналогично следует организовывать вложенные циклы с массивами, число измерений которых более двух: во внутреннем цикле должен изменяться самый левый индекс, а во внешнем - самый правый; 4) используйте вместо циклов выражения с массивами. Так, в последнем примере циклы заменяются выражением array1 = array1 + array2 * 3.3
5) используйте вместо циклов встроенные функции для массивов (см. задачу в разд. 6.2); 6) используйте как встроенные, так и созданные вами элементные функции, например: array = abs(array) |x|
! ABS - элементная функция; abs(x) вернет
7) заменяйте DO-циклы с массивами операторами и конструкциями WHERE и FORALL, например цикл do j = 1, n do i = 1, n if(array1(i, j) > 0) array1(i, j) = array1(i, j) + array2(i, j) * 3.3 end do
⎯ 201 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
end do
заменяется на оператор where(array1 > 0.0) array1 = array1 + array2 * 3.3
Цикл do j = 1, n do i = 1, n array1(i, j) = array1(i, j) + (i + j) * array2(i, j) end do end do
заменяется оператором forall(i = 1:n, j = 1:n) array1(i, j) = array1(i, j) + (i + j) * array2(i, j)
8) с целью повышения эффективности использования быстрой памяти (кеш-памяти) не делайте границу многомерного массива равной целой степени числа 2, например равной 1024. Так, массив real(4), dimension(1024, 50) :: array
замените на массив real(4), dimension(1030, 50) :: array
Внося некоторую избыточность, вы повышаете быстродействие.
6.4.2. Организация быстрого ввода/вывода Затраты времени на В/В снизятся, если придерживаться следующих правил: 1) по возможности используйте неформатные файлы вместо форматных. Так, вывод в файл, подсоединенный к устройству 10, выполнится значительно быстрее вывода в файл, который подсоединен к устройству 20: real(4), dimension(100, 20) :: array open(10, file = 'a.dat', form = 'unformatted') ! или form = 'binary' open(20, file = 'a.txt', form = 'formatted') write(10) array ! Доступ к файлу a.dat происходит быстрее, write(20, '(20f8.3)') array ! чем к файлу a.txt
2) выполняйте В/В всего массива или всей строки не используя циклов; 3) если все же при передаче многомерных массивов необходимо организовать вложенные циклы, то изменяйте индексы по рассмотренному выше правилу: во внутреннем цикле должен изменяться самый левый индекс, а во внешнем - самый правый. Это ⎯ 202 ⎯
6. Повышение быстродействия программ
обеспечит доступ к элементам массива в порядке их размещения в оперативной памяти, что, естественно, ускорит передачу данных; 4) используйте, если позволяют ресурсы, для хранения промежуточных результатов оперативную память, а не внешние файлы; 5) применяйте в случае форматного В/В при программировании формата целочисленное выражение вместо символьной строки, поскольку в первом случае формат определяется единожды - при компиляции, а во втором - спецификация формата, строка form, формируется в процессе исполнения программы: real(4), dimension(1000) :: array integer(4) :: i character(15) :: form ... ! Вычисляется значение переменной n ! Этот способ задания формата лучше, чем формирование строки form, ! содержащей спецификацию формата ! - выражение в дескрипторе преобразований print '(1x, f8.3)', (array(i), i = 1, n) write(form, '(a, i5, a)') '(1x', n, 'f8.3)' ! Формируем строку формата form print form, (array(i), i = 1, n) ! Вывод по формату form
6) создавайте условия для декомпозиции используемых в операторах В/В циклических списков. Для этого переменная цикла должна быть целочисленной, не должна быть формальным параметром, принадлежать оператору EQUIVALENCE и обладать атрибутом VOLATILE, а спецификация формата в случае форматной передачи данных не должна иметь целочисленных выражений в дескрипторе преобразований. Пример циклического списка, имеющего и иное название - встроенный DO-цикл с параметром: write(10, '(20f8.3)') (array(i), i = 1, n)
! Циклический список из n элементов
Пояснение. Обычно каждый элемент списка В/В обращается к процедурам В/В библиотеки DVF. Временные затраты на эти обращения значительны. С целью их уменьшения встроенный DO-цикл замещается компилятором на несколько (до семи) вложенных DO-циклов, использующих для вывода оптимизированную процедуру В/В DVF, которая может осуществлять передачу порциями, содержащими несколько элементов В/В; 7) для увеличения объема передаваемых данных при одном обращении к диску попытайтесь увеличить значение спецификатора BUFFERCOUNT оператора OPEN, но не меняйте значение спецификатора BLOCKSIZE ⎯ 203 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
того же оператора, поскольку оно подбирается оптимальным для используемого устройства. Влияние BUFFERCOUNT на скорость передачи данных должно быть установлено экспериментально; 8) не задавайте значение спецификатора RECL оператора OPEN большим, чем размер буфера В/В (этот размер определяется спецификатором BLOCKSIZE), так как передача избыточных данных, незначительно заполняющих буфер, малопроизводительна; 9) значение спецификатора RECL выбирайте таким образом, чтобы буфер В/В заполнялся наилучшим образом. Буфер будет заполнен полностью, если его размер кратен RECL или, наоборот значение RECL кратно размеру буфера, например: размер буфера равен 8192, а RECL = 1024 или RECL = 16384; 10) используйте оптимальный с позиции быстродействия тип записей, задаваемый в операторе OPEN спецификатором RECORDTYPE: • для последовательных файлов наибольшую производительность обеспечат файлы с фиксированной длиной записи (RECORDTYPE = 'FIXED'); • в случае последовательных неформатных файлов используйте записи переменной длины (RECORDTYPE = 'VARIABLE'); • в последовательных файлах с записями переменной длины задавайте RECORDTYPE = 'STREAM_LF'.
6.4.3. Дополнительные приемы оптимизации кода Они таковы: •
используйте наиболее эффективные с позиции быстродействия типы данных. Перечислим типы данных в порядке снижения их эффективности: INTEGER, LOGICAL; REAL, REAL(4); DOUBLE PRECISION, REAL(8); COMPLEX, COMPLEX(4); DOUBLE COMPLEX, COMPLEX(8);
•
избегайте использования медленных арифметических операций. Перечислим операции в порядке снижения их эффективности: • сложение (+), вычитание (-), умножение чисел с плавающей точкой (*); • целочисленное умножение (*); ⎯ 204 ⎯
6. Повышение быстродействия программ
деление (/); возведение в степень (**). Так, вместо y = x ** 2 лучше записать y = x * x. Вместо y = x / 2.0 записать y = 0.5 * x; • •
•
заменяйте оператор y = x ** 0.5 на оператор, использующий встроенную функцию извлечения квадратного корня: y = SQRT(x);
•
не используйте выражения с разными типами данных, например:
real(4) :: x, y y=2*x константу 2.0 y = 2.0 * x
•
!
Вместо
константы
2
используйте
! Это лучше, чем 2 * x
выносите за пределы цикла не зависящие от его параметров выражения, например вместо фрагмента
do i = 1, n array(i) = 2.0 * float(i) * sqrt(array2(i)) * x end do
используйте фрагмент do i = 1, n array(i) = float(i) * sqrt(array2(i)) end do array = 2.0 * x * array
•
! Выносим 2.0 * x за пределы цикла
заменяйте внешние процедуры на внутренние, поскольку такие процедуры легче сделать встроенными, т. е. заменить вызовы процедур на их код.
6.5. Влияние опций команды DF на производительность Рассмотрим варианты создания объектных и исполняемого файлов на базе главной программы, размещенной в файле example.f90 и использующей процедуры, размещенные в файлах find_ab.f90 и find_cd.f90. Для построения исполняемого файла используем команду DF. На производительность применяемой программы влияет опция команды DF /optimize:уровень. Причем на ранних стадиях работы над программой с целью ускорения компиляции и построения EXE-файла применяется низкий уровень оптимизации либо оптимизация не используется вовсе. Например: ⎯ 205 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
DF /compile_only /debug /optimize:1 find_ab.f90 DF /compile_only /debug /optimize:1 find_cd.f90 DF /exe:example /debug /optimize:0 example.f90 find_ab.obj find_cd.obj Первые две команды создают объектные файлы find_ab.obj и find_cd.obj. Вторая компилирует файл с главной программой example.f90 и строит исполняемый файл example.exe. Замечание. Задание опции /debug без параметров меняет устанавливаемый по умолчанию уровень оптимизации с /optimize:4 на /optimize:0, т. е. отключает какую-либо оптимизацию. После завершения отладки результирующий EXE-файл следует построить, используя четвертый (задается по умолчанию) уровень оптимизации, удалив при этом опцию /debug или заменив ее на опцию /nodebug (/debug:none), например: DF /compile_only /nodebug /optimize:4 find_ab.f90 DF /compile_only /nodebug /optimize:4 find_cd.f90 DF /exe:example /debug:none example.f90 find_ab.obj find_cd.obj Замечание. Отсутствие опции /debug равнозначно заданию минимального уровня отладочной информации - /debug:minimal или /Zd. Приведенная последовательность создания исполняемого файла не является наилучшей, поскольку исходные файлы компилируются раздельно. Совместное компилирование исходных файлов и одновременное построение EXE-файла улучшает качество оптимизации, поскольку: •
существует больше возможностей для встраивания процедур. При встраивании процедуры вместо ее вызова в теле вызывающей программной единицы размещается код вызываемой процедуры (если такая замена возможна). Поскольку встраивание ведет к росту размера исполняемого файла, то, реализуя этот процесс, компилятор решает задачу оптимизации кода как с позиции быстродействия, так и по критерию его объема;
•
более полно анализируются потоки данных;
•
может сократиться число внешних процедур, поиск которых должен выполнить построитель. Пример более оптимального варианта команды DF: DF /exe:example /debug:none example.f90 find_ab.f90 find_cd.f90 Замечание. По умолчанию в случае опции /debug:none уровень оптимизации /optimize:4. ⎯ 206 ⎯
6. Повышение быстродействия программ
При использовании опции /compile_only, когда команда DF создает только объектные файлы и не выполняет построение EXE-файла, используйте также и опцию /object[:имя файла], обеспечивающую создание единого объектного файла для всех компилируемых файлов, например: DF /compile_only /debug:none example.f90 find_ab.f90 find_cd.f90 /object:example.obj DF /exe:example /debug:none example.obj Создавая единый объектный файл, компилятор имеет возможность выполнить совместный анализ обрабатываемых файлов и, следовательно, создать более оптимальный по производительности код. И напротив, не следует компилировать несколько исходных файлов одной командой DF, в которой присутствует опция /compile_only и опущена опция /object, например: DF example.f90 find_ab.f90 find_cd.f90 /compile_only Это не рекомендуется, поскольку, во-первых, будет создано несколько объектных файлов (для данного примера - 3), а во-вторых, серьезно сокращаются возможности для оптимизации кода. Об этом, кстати, компилятор проинформирует составляющего программу пользователя: df: info: Some interprocedural optimizations may be disabled when compiling in this mode Если все же по причине большого числа файлов необходимо использовать несколько DF-команд, то в каждой из них следует объединить связанные между собой исходные файлы. В табл. 6.2 приведены опции команды DF, влияющие на производительность получаемого в результате компиляции и построения исполняемого файла. Таблица 6.2. Опции компилятора, влияющие на производительность программ Опция
/align:ключевое слово
Описание
Управляет добавлением пустых ячеек памяти между данными в common-блоках и производных типах. Ячейки добавляются для того, чтобы обеспечить естественное выравнивание данных в оперативной памяти
⎯ 207 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
/fast
С целью повышения производительности устанавливает следующие опции компилятора: /align:dcommons, /assume:noaccuracy_sensitive и /math_library:fast
/assume:noaccuracy_sensitive
Позволяет компилятору изменять порядок выполнения арифметических операций с целью повышения производительности программы. Может, однако, привести к некоторой потери точности вычислений
/inline:all
Встраивает, если это возможно, каждый вызов процедуры. Чтобы избежать бесконечных рекурсий, не встраиваются некоторые рекурсивные процедуры
/inline:speed
Встраивает, если это возможно, большинство вызовов, не обращая внимания на размер получаемого исполняемого файла
/inline:size
Встраивает, если встраивание возможно, только те вызовы, которые не приводят к существенному росту размера исполняемого файла
/math_library:fast
Ускоряет вызовы встроенных процедур библиотеки за счет того, что не выполняется проверка передаваемых в процедуры параметров. Может привести к снижению точности и уменьшению надежности контроля за арифметическими исключениями
/optimize:уровень
Управляет уровнем оптимизации. По умолчанию, если не задана опция /debug, компиляция выполняется с опцией /optimize:4, в противном случае - с /optimize:0
/unroll:count (0 ≤ count ≤ 16)
Задает, когда действует опция /optimize:3 или 4, число раскруток цикла. При раскрутке (если циклы вложенные, то раскручивается внутренний цикл) создаются копии тела цикла таким образом, чтобы могла быть выполнена эффективная конвейерная обработка этих копий. Предельное число создаваемых копий определяется параметром count опции /unroll. Если опция /unroll:count опущена, то оптимизирующий компилятор раскручивает большинство циклов 4 раза и некоторые циклы 2 раза
⎯ 208 ⎯
6. Повышение быстродействия программ
В табл. 6.3 приведены опции компилятора, использование которых может снизить производительность генерируемых приложений. Таблица 6.3. Опции компилятора, снижающие производительность программ Опция
Описание
/assume:dummy_aliases
Заставляет компилятор создавать объектный код, в котором формальные параметры процедур разделяют память с другими формальными параметрами либо с переменными, передаваемыми посредством useассоциирования или common-блоков. Устаревший прием, не используемый в современном Фортране
/compile_only или /c
Производительность может понизиться, если при использовании этой опции создавать не один, а несколько объектных файлов. Поэтому эту опцию используйте совместно с опцией /object
/check:bounds
Генерирует дополнительный код, выполняющий в процессе работы программы проверку границ массивов и строк. При нарушении границ возникает ошибка исполнения
/check:overflow
Генерирует дополнительный код, выполняющий в процессе применения программы проверку целочисленной арифметики на предмет превышения допустимых для используемых разновидностей типов значений. Об обнаруженных нарушениях выдаются сообщения. После отладки удалите эту опцию (если она применялась). Это приведет и к снижению размера исполняемого файла, и к повышению производительности
/fpe:уровень
Контролирует во время выполнения исключения в операциях с плавающей точкой, что приводит, с одной стороны, к повышению надежности программы, а с другой - к понижению производительности. По умолчанию в DVF под Windows применяется опция /fpe:3. Использование другого уровня ведет к снижению производительности
/debug:ключевое слово
Генерирует дополнительную таблицу с отладочной информацией
⎯ 209 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
/inline:none /inline:manual
Предотвращает встраивание процедур; исключение составляют операторные функции
/optimize:0, или /optimize:1, или /optimize:2, или /optimize:3
Задание меньше максимально возможного уровня оптимизации программы (/optimize:4) снижает ее производительность. Такие опции используются на этапе отладки. При построении предназначенного для эксплуатации исполняемого файла используйте /optimize:4
/vms
Обеспечивает генерацию кода, пригодного для использования в OpenVMS VAX-системе. Устанавливает опцию /align:norecords, т. е. для выравнивания потребуется применения опции /align:records. По умолчанию действует опция /novms
6.6. Обобщения Производительность созданной вами программы вырастет, если вы: •
будете разрабатывать быстрые алгоритмы и использовать эффективные структуры данных;
•
освоите и станете правильно применять опции команды DF (опции компилятора и построителя), влияющие на скорость вычислений;
•
создадите и будете придерживаться стиля программирования, который учитывает все факторы, влияющие на скорость исполнения программы. В быстрой программе:
•
используются современные и не используются устаревшие (например, ассоциирование памяти) методы программирования;
•
данные в оперативной памяти выравнены естественным образом;
•
правильно организована работа с массивами и с циклами, в которых выполняется обработка массивов;
•
применены оптимальные приемы В/В данных и организации внешних файлов;
•
учтены и по необходимости реализованы рекомендации, приведенные в разд. 6.4.3.
⎯ 210 ⎯
7. Программирование на нескольких языках 7.1. Введение В 32-битовых приложениях DVF одновременно могут содержаться процедуры, написанные на четырех языках: Фортране, СИ/СИ++, Бейсике и Ассемблере. При работе с разноязычными процедурами должны соблюдаться соглашения о вызовах, включающих соглашения об именах процедур, их параметрах и способе поддержания стека. Возможны, кроме задаваемого по умолчанию, 2 соглашения о вызовах: C и STDCALL. Рассматриваемые далее соглашения заведомо действуют при работе с языками Фортран фирм Digital и Microsoft, СИ, СИ++ (Visual С++), Бейсик (Visual Basic) и Ассемблер фирмы Microsoft. Договариваться об используемых именах необходимо, так как в СИ значим регистр имен. Кроме того, имена процедур, генерируемые компилятором, могут отличаться от имен, присутствующих в исходном коде. Например, размещенная в файле d.f90 подпрограмма subroutine find_cd(c, d) real(4) :: c, d c = 3.0; d = 4.0 end subroutine find_cd
после компиляции посредством команды DF /c d.f90 будет представлена в объектном файле именем _FIND_CD@8. Размещенный в файле cmain.c прототип написанной на Фортране подпрограммы find_cd void main( ) { float c, d; // Прототип Фортран-подпрограммы find_cd extern void find_cd(float *c, float *d); find_cd(&c, &d); // Вызов Фортран-подпрограммы find_cd printf("%6.2f, %6.2f", c, d); // 3.0 4.0 }
после компиляции, выполненной командой CL /c cmain.c, получит в файле cmain.obj внешнее имя _find_cd. Ясно, что из-за разницы имен, не удастся вызвать подпрограмму find_cd из СИ-функции. Выход из положения изменить внешнее, используемое при вызове, имя процедуры find_cd либо в ⎯ 211 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Фортране, либо в СИ. Выполним это в СИ, применив соглашение STDCALL: extern void __stdcall FIND_CD(float *c, float *d); FIND_CD(&c, &d); // Внешнее имя функции - _FIND_CD@8
Тогда компилятор даст ей, функции, имя _FIND_CD@8, т. е. Фортрани СИ-имена согласованы и, значит, вызов подпрограммы Фортрана find_cd из СИ-функции main возможен. Соглашения о параметрах устанавливают, каким образом и в каком порядке передаются параметры процедуре: по значению или ссылке, слева направо или наоборот. Например, если задана СИ-функция void find_ab(float *a, float *b) { *a = 1.0; b = 2.0; }
то при ее вызове из Фортран-программы параметрами функции должны быть не имена переменных, а их адреса, возвращаемые встроенной функцией LOC: call find_ab(loc(a), loc(b))
Второй способ передачи параметров по ссылке - использование атрибута REFERENCE. В части стека соглашения рассматривают 2 аспекта: получает ли процедура фиксированное или переменное число параметров и какая процедура очищает стек после вызова (вызывающая или вызываемая). При составлении программ можно использовать любое из соглашений (задаваемое по умолчанию, C и STDCALL). Можно в одной и той же программе использовать все соглашения одновременно: они в известной степени взаимозаменяемы. Однако проще и понятнее работать с одним соглашением. Замечание. Взаимозаменяемость соглашений не полная. Например, атрибут VARYING, позволяющий осуществлять вызовы с переменным числом параметров, можно применять только при работе с соглашением C. На практике при вызове написанной на ином языке процедуры необходимо решить две задачи: •
выбрать соглашение и установить интерфейсы между вызываемыми разноязычными процедурами;
•
учесть особенности совместного использования различных объектов данных: строк, массивов, указателей и т. д.
⎯ 212 ⎯
7. Программирование на нескольких языках
Приведем в табл. 7.1 для справки способы объявления функций и процедур, применяемые в четырех рассматриваемых языках. Напомним, что функция, в отличие от подпрограммы, возвращает в качестве результата некоторое значение, которое используется в выражении, из которого функция вызвана. Таблица 7.1. Объявление процедур в Фортране, СИ, Бейсике и Ассемблере Язык
Функция
Подпрограмма
Фортран
FUNCTION <имя>
SUBROUTINE <имя>
СИ
<тип> <имя>
void <имя>
Бейсик
Function <имя>
Sub <имя>
Ассемблер
<имя> PROC
<имя> PROC
Замечание. Вызов процедур Бейсика из Фортрана, СИ и Ассемблера невозможен. В то же время вызов из Бейсика процедур СИ и Фортрана допустим.
7.2. Атрибуты DEC Взаимодействие разноязычных программ поддерживается атрибутами DEC, которые являются расширением DVF по отношению к стандарту Фортрана. Эти атрибуты придают объектам DVF (данным и процедурам) дополнительные свойства, благодаря которым становится возможным соединение разноязычных процедур, например написанных на Фортране и СИ. Перечень атрибутов DEC и объектов, к которым они могут быть применены, дан в табл. 7.2. Таблица 7.2. Атрибуты DEC Атрибут
Объявление переменных
Оператор COMMON
Операторы определения процедур и оператор EXTERNAL
ALIAS
Нет
Да
Да
C
“
“
“
DLLEXPORT
“
“
“
DLLIMPORT
“
“
“
⎯ 213 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
EXTERN
С глобальными переменными
Нет
Нет
REFERENCE
С формальными параметрами
“
“
STDCALL
Нет
Да
Да
VALUE
С формальными параметрами
Нет
Нет
VARYING
Нет
“
Да
Атрибуты DEC вводятся метакомандой !DEC$ATTRIBUTES; при задании нескольких атрибутов они разделяются запятой, например: !DEC$ATTRIBUTES STDCALL, ALIAS : '_find_ab@8' :: find_ab
Атрибуты DEC могут быть использованы при определении процедур и при объявлении типов данных с операторами INTERFACE и ENTRY. Действие атрибутов передается в программные единицы и через useассоциирование.
7.2.1. Атрибут ALIAS Атрибут задает внешнее имя (псевдоним) процедуры (подпрограммы или функции) или общего блока данных (common-блока). Синтаксис: ALIAS : внешнее имя внешнее имя - символьная константа (заключенная в кавычки последовательность символов; может быть СИ-строкой). В отличие от имен Фортрана в имени ALIAS является значащим регистр букв. Так, ALIAS : 'PAT' и ALIAS : 'Pat' - разные имена. Для внешнего имени могут быть использованы недопустимые в Фортране, но приемлемые в ином языке имена, например имена, начинающиеся с символа подчеркивания. В исходном файле вызов процедуры может быть выполнен только по ее основному имени. В файле, содержащем код на другом языке, процедура может быть вызвана только по ее псевдониму. Атрибут ALIAS используется в операторе INTERFACE и в самой процедуре для переопределения ее внешнего имени. Атрибут ALIAS имеет больший приоритет, чем атрибуты STDCALL и С. Если ALIAS использован для процедуры, например, совместно с атрибутом С, то при вызове процедуры будут использованы применяемые в СИ соглашения, но внешнее имя процедуры по-прежнему будет определяться атрибутом ALIAS. ⎯ 214 ⎯
7. Программирование на нескольких языках
Атрибут не может быть использован с внутренними процедурами и формальными параметрами. Пример. В СИ-функции cfun подпрограмма iname будет вызываться по имени _ename. program fortmain interface subroutine cfun( ) ! Интерфейс СИ-функции cfun, которая вызывается !dec$attributes c,:: cfun ! в Фортран-программе по имени _cfun end subroutine cfun subroutine iname( ) ! Интерфейс подпрограммы iname, которая !dec$attributes c, alias: '_ename' :: iname! в СИ-функции cfun вызывается end subroutine iname ! по имени _ename end interface call cfun( ) ! Вызов СИ-функции cfun call iname( ) ! В Фортране вызов по-прежнему end program fortmain ! выполняется по имени iname subroutine iname !dec$attributes c, alias: '_ename' :: iname print *, 'Called' end subroutine iname void cfun( ) { iname extern void ename( ); ename( ); }
// СИ-функция, вызывающая подпрограмму // по имени _ename
Компиляцию файла cfun.c, содержащего функцию cfun, выполним командой CL /c cfun.c. Исполняемый файл fmain.exe получим командой DF fmain.f90 cfun.obj /nodebug, в которой fmain.f90 - имя файла с исходным текстом на Фортране.
7.2.2. Атрибуты С и STDCALL При использовании функций СИ, процедур Бейсика или Ассемблера совместно с Фортран-программами необходимо указать, каким образом выполняется передача данных. DVF поддерживает 2 соглашения о вызовах, задаваемых атрибутами C и STDCALL. Эти атрибуты применимы к процедурам и общим блокам. При использовании с процедурой атрибуты определяют набор соглашений о вызовах. Различия между используемыми соглашениями приведены в табл. 7.3. ⎯ 215 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Таблица 7.3. Соглашения о вызовах Соглашение
C
STDCALL
По умолчанию
Добавление к имени ведущего подчеркивания
Да
Да
Да
Добавление размера стека к имени процедуры
Нет
“
“
Чувствительность к регистру букв
Да
“
Нет
Обработка параметров справа налево
“
“
Да
Очистка стека
“
Нет
Нет
Переменное число параметров
Да
Нет
Нет
Передача длины для строки
Нет
“
Да
Передача параметров по значению*
Да
Да
Нет
* Имена массивов и строк в СИ являются указателями, что обеспечивает их передачу в СИ по ссылке.
Задаваемые по умолчанию и STDCALL соглашения добавляют код в вызываемую процедуру, который восстанавливает стек, когда завершаются вычисления в вызванной процедуре. Это обеспечивает генерацию меньших по размеру программ по сравнению с соглашениями C, при которых такой код следует за каждым вызовом процедуры. Это необходимо, поскольку соглашение C допускает вызов с переменным числом параметров; и первый параметр заносится в стек последним, поэтому он всегда располагается в вершине стека и его адрес не зависит от числа переданных параметров. Такая конфигурация стека позволяет, с одной стороны, вызывающей процедуре знать, сколько передано параметров и их реальные размеры, а с другой - осуществлять вызовы с переменным числом параметров. При вызове из Фортрана СИ-функции с переменным числом параметров используются одновременно атрибуты C и VARYING. По умолчанию параметры процедур, определенных с атрибутами C и STDCALL, передаются по значению. Однако их можно передать и по ссылке, применив атрибут REFERENCE. В процедуры, использующие стандартные соглашения Фортрана о вызовах, аргументы передаются по ссылке (при условии, если для них не указан атрибут VALUE). Имена, объявленные в процедурах Фортрана, использующих атрибут С, автоматически модифицируются: внешние имена переводятся на нижний регистр и предваряются знаком подчеркивания. Например, подпрограмма с ⎯ 216 ⎯
7. Программирование на нескольких языках
именем PROC, определенная с атрибутом C, независимо от числа параметров получит внешнее имя _proc. Для использования имен, содержащих заглавные буквы, необходимо применить атрибут ALIAS. Внешние имена в процедурах, использующих атрибут STDCALL, переводятся на нижний регистр, предваряются знаком подчеркивания и завершаются знаком @ и числом передаваемых параметрами байт. Например, подпрограмма с именем PROC, имеющая 4 параметра типа INTEGER(4) и определенная с атрибутом STDCALL, получит внешнее имя _proc@16. Атрибуты не могут быть применены к формальным параметрам за исключением параметров типа INTEGER. В СИ, Бейсике и Ассемблере также могут быть заданы соглашения о вызовах. Соответствие между соглашениями, применяемыми в этих языках, и соглашениями Фортрана приведены в табл. 7.4. Строку таблицы надо понимать так (на примере первой строки): если в CИ используется соглашение cdecl, которое, кстати, задается по умолчанию и может не объявляться явно, то в Фортране-процедуре следует использовать атрибут C. Таблица 7.4. Соответствие между соглашениями о вызовах Язык
Соглашение
Атрибут DEC
СИ
cdecl (задается по умолчанию)
C
СИ
__stdcall
STDCALL
Бейсик
По умолчанию
По умолчанию
Бейсик
CDECL (ключевое слово)
C
Ассемблер
C (в объявлениях PROTO и PROC)
C
Ассемблер
STDCALL (в объявлениях PROTO и PROC)
STDCALL
Примеры вызовов СИ-функции find_ab, размещенной в файле cfun.c, из Фортран-программы, находящейся в файле fmain.f90: 1. Используем соглашение cdecl, поэтому интерфейс функции find_ab в Фортран-программе должен содержать атрибут C. Функции find_ab в процессе компиляции будет присвоено внешнее имя _find_ab. void cdecl find_ab(float *a, float *b) { *a = 1.0; *b = 2.0; } program simple1 interface
// Квалификатор cdecl может быть опущен
! Интерфейс СИ-функции find_ab
⎯ 217 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
subroutine find_ab(ia, ib) !dec$attributes с :: find_ab integer(4) :: ia, ib программе end subroutine find_ab end interface real(4) a, b a = 10.0; b = 20.0 call find_ab(loc(a), loc(b)) функции print '(2f7.2)', a, b end program simple1
! _find_ab - внешнее имя функции find_ab ! ia, ib - адреса объявленных в главной ! вещественных переменных a, b
! Вызов по ссылке; нужен, так как в СИ! использованы указатели
2. Используем в СИ соглашение __stdcall, следовательно, интерфейс функции find_ab в Фортран-программе должен содержать атрибут STDCALL. Функции find_ab будет дано внешнее имя _find_ab@8. void __stdcall find_ab(float *a, float *b) { *a = 1.0; *b = 2.0; // Квалификатор __stdcall задается явно } program simple2 interface subroutine find_ab(ia, ib) find_ab !dec$attributes stdcall :: find_ab integer(4) :: ia, ib программе end subroutine find_ab end interface real(4) :: a, b a = 10.0; b = 20.0 call find_ab(loc(a), loc(b)) print '(2f7.2)', a, b end program simple2
! Интерфейс СИ-функции find_ab ! _find_ab@8 - внешнее имя функции ! ia, ib - адреса объявленных в главной ! вещественных переменных a, b
! Вызов по ссылке, так как в СИ-функции ! использованы указатели
Компиляция файла cfun.c выполняется командой CL /c cfun.c, а получение исполняемого файла go.exe - командой DF fmain.f90 cfun.obj /exe:go.exe.
7.2.3. Атрибут EXTERN Атрибут указывает, что переменная, объявленная в другом файле, написанном на отличном от Фортрана языке программирования, является
⎯ 218 ⎯
7. Программирование на нескольких языках
глобальной. Атрибут не может быть использован для формальных параметров процедур. Глобальные переменные могут быть объявлены в СИ и Ассемблере. В Фортране аналогом глобальных переменных являются объекты, объявленные в операторе COMMON; для доступа к этим объектам в другом языке используется имя common-блока. В Бейсике глобальные переменные не используются, и обмен данными между процедурами Фортрана и Бейсика выполняется через их параметры. Объявление глобальных переменных: •
в СИ глобальные переменные объявляются вне функций, например:
int glovar1; void cfun( ) { glovar1 = 4; }
// glovar1 - глобальная переменная СИ
•
в Ассемблере - оператором PUBLIC, имеющим синтаксис: PUBLIC [соглашение] имя глобальной переменной соглашение - это C или STDCALL. Опция соглашение, если она присутствует в операторе PUBLIC, перекрывает действие соглашения, заданного директивой .MODEL;
•
в Ассемблере заданную в Фортране глобальную переменную объявляют так: EXTRN [соглашение] имя глобальной переменной
•
в Фортране глобальная переменная pival - имя common-блока - задается так:
!dec$attributes alias : '_pival' :: pival pival real(4) :: pi common/pival/ pi pi = 2.0 * asin(1.0)
•
! _pival - внешнее имя глобального имени
в Фортране глобальная переменная glovar1, заданная в СИ, объявляется так:
! Атрибуты EXTERN и ALIAS обязательны; атрибут C может быть опущен !dec$attributes c, extern, alias : '_glovar1' :: glovar1
Задача. Распечатать значение объявленной выше глобальной переменной СИ glovar1 в Фортран-программе. А в СИ-функции вывести значение объявленной в common-блоке Фортрана переменной pi. ⎯ 219 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Пусть СИ-код размещен в файле cfun.c, а текст Фортран-программе - в файле fmain.f90. Компиляцию СИ-файла выполним командой CL /c cfun.c, а исполняемый файл fmain.exe создадим командой DF fmain.f90 cfun.obj /nodebug. ! Программа, вызывающая СИ-функцию cfun и выводящая значение глобальной переменной program showglovar1 ! Текст храним в файле fmain.f90 !dec$attributes c, extern, alias : '_glovar1' :: glovar1 !dec$attributes alias : '_pival' :: pival ! _pival - внешнее имя common-блока pival integer(4) :: glovar1 ! Исполняемый файл получим командой interface ! DF fmain.f90 cfun.obj /nodebug subroutine cfun( ) ! Атрибут ALIAS может быть опущен !dec$attributes c, alias : '_cfun' :: cfun end subroutine cfun end interface real(4) :: pi common/pival/ pi ! Глобальным является имя common-блока pi = 2.0 * asin(1.0) call cfun( ) ! pi = 3.141593 print *, glovar1 ! 4 end program showglovar1
Внешнее имя _pival common-блока можно задать, применив вместо атрибута ALIAS атрибут С: !dec$attributes c :: pival
Текст СИ-функции: int glovar1; void cfun( ) { extern struct { float pi; pival } pival; glovar1 = 4; printf("\npi = %9.6f\n", pival.pi); }
// glovar1 - глобальная переменная СИ // common-блок pival в СИ объявляется // как структура. Доступ к common-блоку // выполняется по внешнему имени _pival // Вывод заданной в Фортране глобальной // переменной pi
7.2.4. Атрибут REFERENCE Атрибут используется только для формальных параметров и обеспечивает их передачу по ссылке, а не по значению. Может быть использован как для отдельных формальных параметров, так и с именем
⎯ 220 ⎯
7. Программирование на нескольких языках
процедуры. В последнем случае все ее параметры будут передаваться по ссылке. При передаче по ссылке формальный параметр адресует фактический параметр, а не его копию. Поэтому изменение в процедуре значения формального параметра приведет к изменению значения фактического параметра. По умолчанию в Фортране параметры-переменные передаются по ссылке.
7.2.5. Атрибут VALUE Атрибут обеспечивает передачу параметров по значению. В этом случае формальный параметр получает копию фактического. Фактический параметр при передаче по значению останется без изменений, даже если в процедуре изменено значение соответствующего формального параметра. Это обусловлено тем, что формальный параметр адресует не фактический параметр, а его копию. Когда формальный параметр имеет атрибут VALUE, то передающий ему значение фактический параметр может быть другого типа. Если необходимо преобразование типа, то оно выполняется до вызова. Если при определении Фортран-процедуры задан атрибут C, то все параметры, кроме массивов, строки и ссылок, принимают атрибут VALUE. Строки, массивы и ссылки не могут быть переданы по значению, и, следовательно, с ними не должен использоваться атрибут VALUE. Пример: program ref_var_test integer(4) :: kr = 2, kv = 2 write(*, *) kr, kv call refval(kr, kv) write(*, *) kr, kv end program ref_var_test subroutine refval(kr, kv) !dec$attributes reference :: kr !dec$attributes value :: kv integer(4) :: kr, kv kr = kr + 2 kv = kv + 2 end subroutine refval
!
2
2
!
4
2
! Параметр kr передается по ссылке ! Параметр kv передается по значению ! Измененное значение формального ! параметра не передается фактическому
⎯ 221 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
7.2.6. Атрибут VARYING Атрибут используется только для подпрограмм и функций, объявленных с атрибутом С. Задание атрибута VARYING позволяет обращаться к процедуре, например к СИ-функции, с разным числом параметров. При этом число фактических (передаваемых) параметров не должно превышать число формальных параметров. Задача. Организовать из Фортрана вызов функции Ассемблера с переменным числом параметров. ! Используем соглашение C с атрибутом VARYING program fortmain ! Код храним в файле fmain.f90 interface function sumcv(nterms, term) !dec$attributes c, varying :: sumcv ! Внешнее имя функции - _sumcv integer(4) :: sumcv, nterms, term ! term - первый переданный параметр end function sumcv end interface integer(4) :: a = 1, b = 2, c = 3 print *, 'Sum of 0 terms is ', sumcv(0) ! 0 print *, 'Sum of 1 terms is ', sumcv(1, a) ! 1 print *, 'Sum of 2 terms is ', sumcv(2, a, b) ! 3 print *, 'Sum of 3 terms is ', sumcv(3, a, b, c) ! 6 end program fortmain ; Процедура Ассемблера, суммирующая заданное число целочисленных параметров .386 .MODEL FLAT, C sumcv PROTO C, nterms:SDWORD, term:VARARG .CODE ; nterms - число суммируемых параметров ; term - последовательность суммируемых параметров sumcv PROC C USES ebx, nterms:SDWORD, term:VARARG sub eax, eax ; Загружаем сумму из нуля параметров sub ebx, ebx ; Очищаем базовый регистр mov ecx, nterms ; Загружаем число переданных параметров .WHILE ecx > 0 add eax, term[ebx] ; Прибавляем очередной параметр dec ecx ; Уменьшаем число неиспользованных параметров add ebx, 4 ; Переходим к следующему параметру .ENDW done: ret ; Оставляем результат в регистре EAX
⎯ 222 ⎯
7. Программирование на нескольких языках
sumcv ENDP END
Поскольку вызов с разным числом параметров предусмотрен стандартом Фортрана, то атрибут VARYING может использоваться только для вызова процедуры из программы, написанной на другом языке, например СИ. В DVF процедура с таким атрибутом, если она написана на Фортране, вызвана быть не может: форма списков аргументов для атрибута VARYING несовместима с формой, поддерживаемой стандартом Фортрана. Напомним, что вызов с переменным числом параметром обеспечивается атрибутом OPTIONAL. Однако в FPS1 (Fortran PowerStation фирмы Microsoft версии 1) вызовы с атрибутом VARYING возможны, но процедура должна сама определять число полученных параметров. Для этого один из передаваемых параметров должен содержать информацию об их общем числе. Использование ключевых слов не поддерживается. Фактические и формальные параметры должны быть совместимы по порядку следования и типам. Пример. Компиляция программы выполнена в среде FPS с опцией /4fps1. В DVF эта опция игнорируется. program testvar interface to subroutine varpar [varying, c] (k, a, n) integer k, a(*), n ! k - число передаваемых параметров end integer a(10) /1, 2, 3, 4, 5, 6, 7, 8, 9, 10/ call varpar(2, a, 6) ! Передаем 2 параметра call varpar(1, a) ! Передаем 1 параметр end subroutine varpar(k, a, n) INTERFACE TO integer a(*), k, n, i if(k .eq. 1) n = 3 write(*, *) (a(i), i = 1, n) end program testvar
!
Атрибуты
указаны
в
операторе
7.2.7. Атрибуты DLLEXPORT и DLLIMPORT Атрибуты DLLEXPORT и DLLIMPORT задают интерфейс к динамически подключаемым библиотекам. Эти же атрибуты могут быть применены и к именам common-блоков. DLLEXPORT объявляет, что процедура или данные (имена общих блоков) экспортируются в другие приложения или динамические ⎯ 223 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
библиотеки DLL. При использовании этого атрибута компьютер создает наиболее эффективный код и нет необходимости задавать файл определения (DEF-файл) для экспорта символов. Задание атрибута DLLEXPORT должно выполняться в той же программной единице, где объявляются процедуры или данные, к которым атрибут применяется. Программа, использующая определенные в DLL символы, импортирует их. В такой программе, если она написана на Фортране, следует объявить атрибут DLLIMPORT. Объявление выполняется в той программной единице, которая импортирует символы. Пример: subroutine arraytest(arr) !dec$attributes dllexport :: arraytest real(4) :: arr(3, 7) integer(4) :: i, j do i = 1, 3 do j = 1, 7 arr(i, j) = 11.0 * i + j end do end do end subroutine arraytest
! Атрибут обеспечивает создание LIB ! и EXP файлов, нужных при генерации ! приложения, использующего DLL
Чтобы включить подпрограмму в динамическую библиотеку, необходимо создать DLL-проект. Для этого используется цепочка File New - Project Workspace - Dynamic-Link-Library - внести имя проекта в поле Name - Create. Пусть имя проекта fdll. Затем, применяя цепочку Insert - Files Into Project, добавим в проект файл, например arr.f90, содержащий текст подпрограммы arraytest. Таким же образом в проект добавляются и другие исходные файлы. После этого создадим DLL-библиотеку: Build - Build fdll.dll. Заметим, что одновременно с динамической библиотекой fdll.dll будет создан и файл fdll.lib, содержащий код, необходимый для организации ссылок на DLL. Пусть файлы библиотеки fdll.lib и fdll.dll размещены в папке fdll. Для использования динамической библиотеки fdll.dll в программе необходимо до генерации содержащего эту программу проекта добавить в опции компоновщика строку с именем библиотеки fdll.lib. Это можно сделать так: Build - Settings - Link - выбрать категорию General - добавить в поле Object/Library modules после библиотеки kernel32.lib строку "c:\fdll\fdll.lib". (Разделителем между именами библиотек является пробел.) Затем сгенерировать проект. Пусть результатом построения проекта является файл ted.exe. При запуске программы ted.exe, использующей библиотеку ⎯ 224 ⎯
7. Программирование на нескольких языках
fdll.dll, ей должен быть известен путь к файлу fdll.dll. Например, файл fdll.dll можно разместить там же, где находится файл ted.exe. program ted !dec$attributes dllimport :: arraytest ! Определяем имя импортируемого символа integer(4) :: i, j real(4) :: arr(3, 7) call arraytest(arr) ! Подпрограмма будет взята из fdll.dll print '(1x, 7f5.1)', ((arr(i, j), j = 1, 7), i = 1, 3) end program ted
7.3. Соглашения об именах Компилятор в зависимости от используемого соглашения и заданного атрибутом ALIAS имени name преобразовывает при компиляции это имя. Имя name может обозначать имя процедуры, переменной, common-блока или модуля. Правила преобразований имен сведены в табл. 7.5, в которой n равен размеру области стека в байтах, занимаемой параметрами процедуры. Таблица 7.5. Соглашения об именах в Фортране, СИ, Бейсике и Ассемблере Язык, соглашение
Имя после компиляции
Регистр букв имени в объектном файле
Фортран, C
_name
Строчные буквы
Фортран, STDCALL
_name@n
Фортран, по умолчанию
_name@n
Прописные буквы
СИ, cdecl или по умолчанию
_name
Прописные и строчные буквы
СИ, __stdcall
_name@n
VISUAL C++
?name@@decoration
”
Ассемблер, С (в PROTO и PROC)
_name
”
Ассемблер, STDCALL (в PROTO и PROC)
_name@n
”
”
“
То же
Замечание. В Фортране регистр важен лишь при задании имени в атрибуте ALIAS. Пример использования приведенной в таблице информации: пусть СИфункция задается кодом ⎯ 225 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
#include <math.h> float cname(float x, float y) { return sqrt(x * x + y * y); }
// Для встроенной функции sqrt // Параметры передаются по значению
Тогда, поскольку соглашение не задано явно и, следовательно, используется соглашение cdecl, для вызова этой функции из Фортрана при объявлении ее интерфейса следует применить атрибут C: program demo interface function cname(x, y) !dec$attributes c :: cname компилятор real(4) :: cname, x, y end function cname end interface print *, cname(3.0, 4.0) _cname end program demo
! Применение атрибута С означает, что ! даст функции cname внешнее имя _cname ! Это имя можно наблюдать в OBJ-файле ! Фактически вызов выполняется по имени
Вызов СИ-функции, как это следует из вышеприведенной таблицы, выполняется по имени _cname: именно в такое имя преобразовывают имя cname компиляторы СИ и Фортрана. В принципе можно изменить, применив атрибут ALIAS, имя, которое используется в Фортране для обращения к СИ-функция cname, например так: program demo2 interface function cname2(x, y) !dec$attributes c, alias : '_cname' :: cname2 real(4) :: cname2, x, y end function cname2 end interface ! Хотя в программе используется имя cname2, print *, cname2(3.0, 4.0) ! фактически вызов СИ-функции cname end program demo2 ! выполняется по внешнему имени _cname
Атрибут ALIAS, имея больший приоритет, чем атрибут C, даст указание компилятору преобразовать имя cname2 во внешнее имя _cname. Однако ясно, что такое применение атрибута ALIAS усложняет восприятие программы.
⎯ 226 ⎯
7. Программирование на нескольких языках
Снабдим теперь СИ-функцию квалификатором __stdcall: #include <math.h> float __stdcall cname(float x, float y) { return sqrt(x * x + y * y); }
// Для вызова встроенной функции sqrt // СИ-компилятор даст функции // внешнее имя _cname@8
Тогда СИ-компилятор даст функции внешнее имя _cname@8, в котором 8 число байт, отводимых в стеке под параметры функции cname. В Фортране то же имя будет сгенерировано компилятором в программе: program demo3 interface function cname(x, y) !dec$attributes stdcall :: cname real(4) :: cname, x, y end function cname end interface print *, cname(3.0, 4.0) end program demo3 _cname@8
! Хотя в программе используется имя cname, ! фактически вызов СИ-функции cname ! выполняется по внешнему имени
7.4. Прописные и строчные буквы в именах 7.4.1. Имена из прописных букв При совместном использовании программ на Фортране, СИ и/или Ассемблере необходимость записывать в двух последних языках имена прописными буквами возникает в том случае, когда файл с кодом на Фортране откомпилирован по заданному по умолчанию соглашению, т. е. без атрибутов C и STDCALL. Тогда сгенерированные компилятором имена будут состоять из прописных букв и, чтобы использовать эти имена в СИ или Ассемблере, следует применить в этих языках соглашение STDCALL, а запись соответствующих имен выполнить прописными буквами. Для Бейсика, как и Фортрана, все равно, из каких букв, прописных или строчных, составлены имена. Пример. Пусть в Фортране задана функция fortfun, компиляция которой выполнена по заданному по умолчанию соглашению: function fortfun(x, y) !dec$attributes value :: x, y вызывается real(4) :: fortfun, x, y
! Текст функции запишем в файл ffun.f90 ! Параметры, поскольку функция ! из СИ, нужно передать по значению
⎯ 227 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
fortfun = sqrt(x * x + y * y) командой end function fortfun /debug:none
! Исполняемый файл ffun.exe создадим !
DF
ffun.f90
cmain.obj
/nodformain
Тогда компилятор даст ей имя _FORTFUN@8. Следовательно, СИфункция, вызывающая функцию fortfun, должна содержать такой ее прототип: void main( ) { // Функция записана в файл cmain.c // Прототип функции fortfun extern float __stdcall FORTFUN(float x, float y); printf("%7.2f", FORTFUN(3.0, 4.0)); // Объектный код получен командой } // CL /c cmain.c
Имя fortfun в СИ-функции main записано прописными буквами: FORTFUN. Описание прототипов функции fortfun: •
в Бейсике:
Declare Function FORTFUN Lib "ffun.dll" (ByVal x As Single, ByVal y As Single) As Single
•
в Ассемблере:
.MODEL FLAT, STDCALL FORTFUN PROTO STDCALL, x, y: PTR REAL4 ... FORTFUN PROC STDCALL, x, y: PTR REAL4
В Ассемблере можно использовать также опцию OPTION CASEMAP ALL, которая переводит буквы всех имен в прописные. Тогда ввод имени функции fortfun может быть выполнен на любом регистре.
7.4.2. Имена из строчных букв Если внешние имена в СИ или Ассемблере заданы строчными буквами, то в Фортране, в зависимости от используемого в другом языке соглашения, следует явно задать соглашение C или STDCALL. Эти соглашения, если имя не задано атрибутом ALIAS, автоматически переводят буквы имен в строчные. Например: void main( ) { // Функция записана в файл cmain.c extern float __stdcall fortfun(float x, float y); // Прототип функции fortfun printf("%7.2f", fortfun(3.0, 4.0)); // Объектный код получен } // командой CL /c c.main.c function fortfun(x, y)
! Текст функции запишем в файл ffun.f90
⎯ 228 ⎯
7. Программирование на нескольких языках
!dec$attributes stdcall :: fortfun real(4) :: fortfun, x, y fortfun = sqrt(x * x + y * y) end function fortfun
! Внешнее имя функции - _fortfun@8 ! Параметры, поскольку задан атрибут ! STDCALL, передаются по значению
7.4.3. Имена из смеси прописных и строчных букв Если внешние имена в СИ или Ассемблере одновременно содержат и прописные и строчные буквы, то в Фортране, помимо задания соглашения о вызове, внешнее имя следует определить атрибутом ALIAS, полностью скопировав в нем внешнее имя СИ или Ассемблера. Например: •
соглашение STDCALL:
void main( ) { // Функция записана в файл cmain.c extern float __stdcall FortFun(float x, float y); // Прототип функции fortfun ... function fortfun(x, y) ! Текст функции запишем в файл ffun.f90 ! Внешнее имя функции - _FortFun@8 !dec$attributes stdcall, alias : '_FortFun@8' :: fortfun real(4) :: fortfun, x, y ! Параметры, поскольку задан атрибут ... ! STDCALL, передаются по значению
•
соглашение C:
void main( ) { extern float FortFun(float x, float y); ...
// Функция записана в файл cmain.c // Прототип функции fortfun
function fortfun(x, y) ! Текст функции запишем в файл ffun.f90 !dec$attributes c, alias : '_FortFun' :: fortfun ! Внешнее имя функции - _FortFun real(4) :: fortfun, x, y ! Параметры, поскольку задан атрибут ... ! C, передаются по значению
7.4.4. Имена VISUAL C++ VISUAL C++ (далее - просто СИ++) использует те же соглашения о вызовах и те же механизмы передачи внешних данных, что и СИ. Однако внешние имена формируются по иным, отличным от действующих в СИ правилам: компилятор преобразовывает внешнее имя name в имя ?name@@decoration. Избавиться от декоративного окончания @decoration позволяет модификатор extern "C". Например, СИ-функцию, если она предназначена для вызова в Фортран-программе, следует объявить так: extern "C" { void find_ab(float *a, float *b) { *a = 1.0; *b = 2.0; // Компилятор преобразовывает имя find_ab }} // в имя _find_ab
⎯ 229 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
При отсутствии модификатора extern "C" сгенерированное компилятором имя может иметь вид: ?find_ab@@YAXPAM0@Z. В случае соглашения STDCALL прототип внешней, написанной на Фортране функции find_cd в СИ++ запишется так: extern "C" { void __stdcall find_cd(float *, float *); }
Модификатор extern "C" используется в СИ++ для изменения соглашений об именах как при вызове внешних, написанных, например, на Фортране процедур, так и записи функций СИ++, вызываемых из процедур, закодированных на другом языке (Фортране, Бейсике или Ассемблере). Если же код функции СИ++, вызываемой, например, из Фортранпрограммы, не использует extern "C" и не может быть изменен, то можно, применив атрибут ALIAS, согласовать имена СИ++ и Фортрана (учесть во внешнем имени Фортрана декоративное окончание). Однако такой подход следует использовать в крайнем случае, поскольку Microsoft не гарантирует сохранение схемы формирования декоративного окончания @decoration в последующих версиях СИ++. На применение extern "C" накладываются ограничения: •
нельзя использовать extern "C" с функциями-членами;
•
в случае перегружаемых функций extern "C" может быть задан лишь для одной из функций, объединенных под одним родовым именем.
7.5. Интерфейс внешней процедуры В Фортране общий вид интерфейса процедуры, написанной на отличном от Фортрана языке, таков: INTERFACE SUBROUTINE | FUNCTION имя процедуры([список формальных параметров]) [DEC-атрибуты процедуры] [DEC-атрибуты формальных параметров] [объявление формальных параметров] END SUBROUTINE | FUNCTION [имя процедуры] END INTERFACE Пример. Пусть из Фортрана вызывается СИ-функция: void __stdcall find_ab2(float *a, float *b, float g) { *a = 2.0 * g; *b = 3.0; // Параметры a и b переданы по ссылке } // Параметр g передан по значению
⎯ 230 ⎯
7. Программирование на нескольких языках
Ее интерфейс и вызов запишутся в Фортране так: program fortmain interface subroutine find_ab2(a, b, g) !dec$attributes stdcall :: find_ab2 !dec$attributes reference :: a, b !dec$attributes value :: g real(4) :: a, b, g end subroutine find_ab2 end interface real(4) :: a, b call find_ab2(a, b, -1.0) print '(1x, 2f6.2)', a, b end program fortmain
! Атрибут подпрограммы ! Параметры a и b передаются по ссылке ! Параметр g передается по значению
! Первые 2 параметра передаются по ссылке ! -2.00 3.00
В приведенном интерфейсе предложение с атрибутом VALUE, может быть опущено, поскольку в случае STDCALL параметры по умолчанию передаются по значению. Можно не использовать и атрибут REFERENCE, но в этом случае СИ-функции find_ab2 надо передать, используя встроенную функцию LOC, адреса переменных a и b. Тогда интерфейс и вызов СИ-функции find_ab2 в программе fortmain2 запишутся так: program fortmain2 interface subroutine find_ab2(ia, ib, g) !dec$attributes stdcall :: find_ab2 integer(4) :: ia, ib real(4) :: g end subroutine find_ab2 end interface real(4) :: a, b call find_ab2(loc(a), loc(b), -1.0) переменных print '(1x, 2f6.2)', a, b end program fortmain
! Атрибут подпрограммы ! ia, ib - адреса переменных a и b ! Параметр g передается по значению
!
Два
первых
параметра
-
адреса
! -2.00 3.00
7.6. Согласование типов данных Обмениваясь данными между разноязычными процедурами, согласовывайте разные типы данных так, как это регламентируется табл. 7.6.
⎯ 231 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Таблица 7.6. Эквивалентные типы данных Фортран
СИ
Бейсик
–
Ассемблер
INTEGER(1), LOGICAL(1)
char
SBYTE
INTEGER(2), LOGICAL(2)
short int
Integer
SWORD
INTEGER(4), LOGICAL(4)
int, long
Long
SDWORD
REAL(4)
float
Single
REAL4
REAL(8)
double
Double
REAL8
CHARACTER(1)
unsigned char
CHARACTER(*)
См. разд. 7.7.1.1
COMPLEX(4)
COMPLEX(8)
–
BYTE
struct complex4 { float real, imag; };
–
COMPLEX4 STRUCT 4 real REAL4 ? imag REAL4 ? COMPLEX4 ENDS
struct complex8 { float real, imag; };
–
COMPLEX8 STRUCT 8 real REAL8 ? imag REAL8 ? COMPLEX8 ENDS
Замечание. Пользовательские функции Фортрана типа COMPLEX размещают скрытый комплексный параметр в начале списка параметров. Вызов такой функции в СИ-функции должен содержать этот параметр в явном виде и использовать его для возврата результата. При описании прототипа комплексной функции Фортрана в СИ-функции следует для задания типа использовать void. Например: function fortcomp(z1, z2) соглашение complex(4) fortcomp, z1, z2 fortcomp = z1 + z2 end function fortcomp void main( ) { struct complex4 { float real, imag;
! Используем
заданное
по умолчанию
! Внешнее имя функции - _FORTCOMP@12 ! Параметры функции передаются по ссылке
// Описание комплексного типа данных
⎯ 232 ⎯
7. Программирование на нескольких языках
} z, z1 = {1.0, 2.0}, z2 = {3.0, 4.0}; extern void __stdcall FORTCOMP(struct complex4 *, struct complex4 *, struct complex4 *); FORTCOMP(&z, &z1, &z2); // z - возвращаемое функцией значение printf("z = (%6.2f, %6.2f)", z.real, z.imag); // z = (4.0, 6.0) }
7.7. Передача данных в программах с разноязычными процедурами Существует 3 возможности обмениваться процедурами, реализованными на разных языках: •
через параметры процедур;
•
через объявленные в модуле данные;
данными
между
•
посредством глобальных (в Фортране - общих) данных. Рассмотрим каждую из них. Замечание. Частично вопросы обмена глобальными рассмотрены выше, в разд. 7.2.3.
данными
7.7.1. Обмен данных через параметры процедур Сведения о том, как передать данные по значению или по ссылке при разных соглашениях, приведены в табл. 7.7. Таблица 7.7. Управление передачей данных по ссылке и значению Язык, атрибут
Фортран, по умолчанию Фортран, C или STDCALL Фортран, по умолчанию Фортран, C или STDCALL СИ/СИ++
Вид параметра
Скаляры “ Массивы “ Скаляры
Как передать по ссылке
Как передать по значению
По умолчанию
Использовать атрибут VALUE
Использовать атрибут REFERENCE
По умолчанию
По умолчанию
Нельзя передать по значению
”
Нельзя передать по значению
“
Передать адрес параметра
⎯ 233 ⎯
По умолчанию
О. В. Бартеньев. Visual Fortran: новые возможности
СИ/СИ++
Массивы
По умолчанию
Создать структуру, компонентом которой является массив
Бейсик
Любой
”
Использовать ключевое слово BYVAL
Ассемблер
“
“
Использовать обозначение PTR
По умолчанию
Замечания: 1. Производные типы данных, даже если они содержат массивы, являются скалярными объектами. 2. Передача строк Фортрана и его ссылок имеет особенности, которые будут рассмотрены ниже. Напишем теперь на Фортране подпрограмму fortsub и приведем варианты ее объявления в СИ, Бейсике и Ассемблере. subroutine fortsub(aref, bval) ffun.f90 !dec$attributes stdcall :: fortsub !dec$attributes reference :: aref !dec$attributes value :: bval aref = 2.0 * bval end subroutine fortsub
! Текст на Фортране разместим в файле ! Используем соглашение STDCALL ! Параметр aref передается по ссылке ! Параметр bval передается по значению
void main( ) { // Код на СИ разместим в файле cmain.c extern void __stdcall fortsub(float *, float); // Прототип процедуры fortsub float aref, bref = -1.0; fortsub(&aref, bref); // Объектный cmain.obj-файл получим printf("aref = %7.2f", aref); // командой CL /c cmain.c }
Файл cmain.exe сгенерирует команда DF cmain.obj ffun.f90 /nodformain /nodebug. В Бейсике при объявлении внешней Фортран-подпрограммы fortsub используем со вторым параметром bval, который передается по значению, ключевое слово BYVAL: Declare Sub fortsub Lib "ffun.dll" (aref As Long, ByVal bval As Long)
Для первого параметра действуют заданные по умолчанию правила: он будет передан по ссылке. ⎯ 234 ⎯
7. Программирование на нескольких языках
В Ассемблере по умолчанию параметры передаются по значению. Чтобы вызвать Фортран-подпрограмму fortsub из Ассемблера, следует при ее объявлении в PROTO и PROC первый передаваемый по ссылке параметр снабдить обозначением PTR: fortsub PROTO STDCALL, aref: PTR SDWORD, bval: SDWORD
Далее используем значение параметра bval, например: mov eax, bval параметра bval
; Помещаем в регистр EAX значение
При работе с параметром, переданным по ссылке, используем его адрес: mov ecx, aref aref mov eax, [ecx] aref
; Загружаем в регистр ECX адрес параметра ; Теперь в регистр EAX загружаем значение
7.7.1.1. Передача символьных данных Если параметром процедуры Фортрана является строка, то по умолчанию вместе с ней передается скрытая длина строки. Длина содержится в целочисленном, типа INTEGER(4), параметре, который передается по значению. Применяя атрибуты, можно изменить способ передачи строк. Как это происходит, отражено в табл. 7.8. Таблица 7.8. Влияние атрибутов на способ передачи строк через параметры процедур Параметр и его атрибут
Строка
Атрибуты процедуры
Способ передачи
По умолчанию
Передается по ссылке вместе с длиной строки, которая передается по значению
”
C или STDCALL
Первый символ преобразовывается в INTEGER(4) и передается по значению
”
C, REFERENCE или STDCALL, REFERENCE
Передается по ссылке вместе с длиной строки, которая передается по значению
По умолчанию*
Ошибка
Строка с атрибутом VALUE
⎯ 235 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
То же
C или STDCALL
Первый символ преобразовывается в INTEGER(4) и передается по значению
C, REFERENCE или STDCALL, REFERENCE
То же
Строка с атрибутом REFERENCE
По умолчанию
Передается по ссылке вместе с длиной строки, которая передается по значению
То же
C или STDCALL
Передается по ссылке без длины строки
C, REFERENCE или STDCALL, REFERENCE
То же
”
”
Из приведенной таблицы следует, что, передавая строки в разноязычных процедурах, следует либо объявлять строку без атрибута и использовать задаваемое по умолчанию соглашение, либо объявлять строку с атрибутом REFERENCE и использовать соглашение C или STDCALL с атрибутом REFERENCE или без него. В других случаях, кроме отмеченного звездочкой, передается лишь первый символ строки. Передавая строки из СИ в Фортран и обратно, следует помнить, что в СИ строка является одномерным массивом символов и завершается nullсимволом. То есть из Фортрана в СИ должны передаваться СИ-строки. Напомним, что СИ-буквальные константы Фортрана завершаются буквой С, например: "It's a C-string"C
! Буквальная СИ-константа Фортрана
Между строками Фортрана и СИ есть еще одно отличие: нумерация индексов в строках СИ-функций начинается с нуля, а в Фортране - с единицы. Так, имя первого элемента строки st из 20 символов в СИ - st[0], а в Фортране - st(1:1). Объявляется эта строка так: char st[20]; character(20) st Фортране
// Объявление строки из 20 символов в СИ ! Объявление строки из 20 символов в
Строки, как и любой массив СИ, являются указателями и, следовательно, будучи параметрами СИ-функции, передаются по ссылке.
⎯ 236 ⎯
7. Программирование на нескольких языках
Пример 1. Организуем вызов символьный параметр, из Фортрана.
СИ-функции
cstring,
имеющей
#include <string.h> // Сохраним код в файле cfun.c; объектный код void cstring(char *st) { // получим командой CL /c cfun.c printf("%*s\n", strlen(st), st); strcpy(st, "Updated in C function cstring"); } program fortmain interface subroutine cstring(st) !dec$attributes c :: cstring !dec$attributes reference :: st character(*) :: st end subroutine cstring end interface character(50) :: string = 'Initial value'C call cstring(string) print *, string end program fortmain
! Программу храним в файле fmain.f90 ! Исполняемый файл создадим командой ! DF fmain.f90 cfun.obj /nodebug ! Используем соглашение С ! Строка передается по ссылке без длины
! Используем в Фортране СИ-строку ! Updated in C function cstring
Пример 2. Выполнить вызов подпрограммы Фортрана с символьным параметром из СИ, использовав в Фортране задаваемое по умолчанию соглашение. subroutine fstring(st) ! Код храним в файле ffun.f90 character(*) st st = 'Updated in Fortran subroutine fstring'C end subroutine fstring #include <string.h> // Сохраним код в файле cmain.c; объектный код void main( ) { // получим командой CL /c cmain.c char st[50] = "Initial value"; extern void __stdcall FSTRING(char *st, int stlength); FSTRING(st, 50); // Передаем строку и ее длину printf("%*s\n", strlen(st), st); // Updated in Fortran subroutine fstring }
Построение приложения выполним командой DF cmain.obj ffun.f90 /nodformain /nodebug Замечание. Функции Фортрана, результирующая переменных которых имеет тип CHARACTER, размещают, подобно комплексным функциям, ⎯ 237 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
скрытый параметр в начале списка параметров. Этот параметр может одновременно содержать как адрес строки, так и ее длину. Вызов такой функции в СИ-функции должен содержать этот параметр в явном виде и использовать его для возврата результата. При описании прототипа символьной функции Фортрана в СИ-функции следует для задания типа использовать void. Однако, чтобы избежать путаницы, лучше не использовать вызовы символьных функций Фортрана из СИ, а выполнить их замену на подпрограммы, введя дополнительный параметр для возврата результата. Другие возможности обмена символьными данными - это применение common-блоков и модулей. В Бейсике, использующем подпрограмму Фортрана с символьными параметрами, строки должны передаваться по значению. Фактически Бейсик хранит строки как структуры, содержащие длину строки и ее адрес. При передаче строки по значению сразу осуществляется доступ к адресу строки, который и передается Фортрану. Пусть дана вызываемая из Бейсика подпрограмма Фортрана: subroutine fstring(st) character(50) st st = 'Initial Fortran string value' end subroutine fstring
Тогда в Бейсике подпрограмма Фортрана fstring объявляется и вызывается так: Declare Sub FSTRING Lib "flib.dll" (ByVAl bst as String) DIM bst As String * 50 ' Строка фиксированной длины CALL FSTRING(bst)
Ассемблер не добавляет ни длины строки, ни завершающего nullсимвола. Чтобы передать длину строки, используйте синтаксис lenstring BYTE "String with length", LENGTHOF lenstring
null-символ добавляется так: nullstring BYTE "Null terminated string", 0
7.7.1.2. Передача массивов При работе с массивами в разноязычном приложении следует учитывать, что:
•
в Ассемблере могут быть использованы только одномерные массивы;
•
в Фортране по умолчанию нижняя граница каждого измерения равна единице, а в СИ и Бейсике - нулю. Например, если задан вещественный массив x из 10 элементов и используются заданные по умолчанию ⎯ 238 ⎯
7. Программирование на нескольких языках
правила, то имя первого элемента массива в Фортране - x(1), в СИ - x[0], а в Бейсике - x(0). В Ассемблере для адресации первого элемента используется имя массива - x, для адресации следующего элемента надо переместиться на 4 байта, например так: x[4] - или так: x + 4. Объявления массив x: real(4) :: x(10) = 1.0 Фортране float x[10] = {1.0, 2.0, 3.0}; DIM x (1 to 10) As Single x REAL4 10 DUP(1.0) Ассемблере
! Объявление и инициализация массива в // Объявление и инициализация массива в СИ ' Объявление массива в Бейсике ; Объявление и инициализация массива в
В Бейсике можно изменить индексацию массива, начав ее с единицы, использовав в программе оператор Option Base 1
•
в Фортране и Бейсике элементы многомерного массива в памяти компьютера расположены иначе, чем в СИ: в двух первых языках при размещении элементов массива быстрее всего меняется левый индекс и медленнее всего правый; в СИ обратная картина: быстрее всего меняется правый индекс и медленнее всего левый. Так, двумерный массив a формы (3, 2) расположен в Фортране и Бейсике в памяти по столбцам, а в СИ - по строкам. Графически это иллюстрирует рис. 7.1. a(1, 1)
a(2, 1)
a(3, 1)
a(1, 2)
a(2, 2)
a(3, 2)
a[1][1]
a[2][0]
a[2][1]
a a[0][0]
a[0][1]
a[1][0]
б Рис. 7.1. Элементы массива в памяти компьютера: а - массив Фортрана; б - массив СИ
Согласование порядка следования элементов массива в Фортране (Бейсике) и СИ обеспечивается надлежащими объявлениями массива. Так, объявлению Фортрана integer(4), dimension(3, 2) :: a
соответствует объявление СИ int a[2][3];
Пример 1. Вызвать из СИ подпрограмму Фортрана ввода массива.
⎯ 239 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Используем соглашение STDCALL. Поскольку вызов выполняется из СИ, где массивы передаются по ссылке, то в подпрограмме Фортрана следует также использовать и атрибут REFERENCE. subroutine inarr(array) !dec$attributes stdcall, reference :: inarr integer(4), dimension(3, 2) :: array print *, 'Enter 6 integer values:' read(*, *) array array(2, 2) = -5 end subroutine inarr
! Текст функции запишем в файл ffun.f90 ! Исполняемый код получим командой ! DF cmain.obj ffun.f90 /nodformain /nodebug ! Введем 6*1 ! Этому элементу соответствует элемент ! array[1][1] в СИ-функции main
void main( ) { // Текст функции храним в файле cmain.c int array[2][3]; extern void __stdcall inarr(int array[][3]); inarr(array); printf("array[1][1] = %3d", array[1][1]); // array[1][1] = -5 }
Пример 2. Вызвать из Фортрана функцию СИ ввода массива. program fortmain interface subroutine cinarr(addr_array) !dec$attributes c :: cinarr integer(4) :: addr_array end subroutine cinarr end interface integer(4) :: array(3, 2) call cinarr(loc(array)) print *, 'array(2, 2) = ', array(2, 2) end program fortmain
! Храним код в файле fmain.f90 ! Интерфейс СИ-функции cinarr ! addr_array - адрес массива ! В СИ массив объявим, как array[2][3] ! Передаем адрес массива ! Исполняемый файл создаем командой ! DF fmain.f90 cfun.obj
void cinarr(int array[][3]) { // Код функции запишем в файл cfun.c int i, j; // Объектный файл cfun.obj получим командой puts("Enter 6 integer values:"); // CL /c cfun.c for(i = 0; i < 2; i++) // Введем 6 целочисленных значений for(j = 0; j < 3; j++) scanf("%d", &array[i][j]); array[1][1] = -5; // Этому элементу соответствует элемент } // array (2, 2) в программе fortmain
Замечание. Если в интерфейсе СИ-функции cinarr объявить не адрес массива, а сам массив: integer(4) :: array(3, 2) cinarr(array)
!
Заголовок
⎯ 240 ⎯
подпрограммы:
subroutine
7. Программирование на нескольких языках
то вызов СИ-функции cinarr выполняется так: call cinarr(array)
! Передаем адрес массива
Этот вызов, как и предыдущий, обеспечивает передачу массива по ссылке. В Бейсике передачу массива в подпрограмму Фортрана проиллюстрируем схемой: ' Код Бейсика Declare Sub FORTARRAY Lib "fortarr.dll" (Barray as Single) DIM barray (1 to 3, 1 to 7) As Single Call FORTARRAY(barray(1, 1)) ! Код Фортрана subroutine FORTARRAY(arr) real arr(3,7)
7.7.1.3. Передача ссылок и размещаемых массивов Фортрана Используя ссылки и размещаемые массивы Фортрана как параметры иноязычных процедур, можно передать либо базовые адреса объектов, либо их описатели. Способ передачи регулируется атрибутами DEC и приведен в табл. 7.9. Таблица 7.9. Влияние атрибутов на способ передачи ссылок и размещаемых массивов Атрибут объекта
Атрибуты передающей процедуры
Что передается
Нет
По умолчанию
Описатель
Нет
C или STDCALL
Базовый адрес
Нет
C, REFERENCE или STDCALL, REFERENCE
Описатель
VALUE
Без атрибута или C или STDCALL с атрибутом REFERENCE или без него
Базовый адрес
REFERENCE
То же
Описатель
Замечание. Атрибут VALUE может быть назначен только размещаемым массивам. Описатель является структурой, содержащей ссылку на адрес области, занимаемой объектом, и описания свойств ссылки или размещаемого массива, таких, как ранг, границы и др. Используемые с описателем структуры и программы приведены в прил. 2. ⎯ 241 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Пример. Выполнить изменение ссылочного массива в СИ-функции cpoint. program fortmain interface subroutine cpoint(addr_parr) ! Интерфейс СИ-функции cpoint !dec$attributes c :: cpoint integer(4) :: addr_parr ! Адрес ссылки end subroutine cpoint end interface integer(4), dimension(:, :), pointer :: parr => null( ) integer(4), target, dimension(3, 2) :: arr parr => arr ! Прикрепляем ссылку к адресату call cpoint(loc(parr)) ! Передаем адрес ссылки print '(10i3)', parr ! 0 1 2 1 -5 3 print *, arr ! 0 1 2 1 -5 3 end program fortmain void cpoint(int parr[][3]) { int i, j; for(i = 0; i < 2; i++) for(j = 0; j < 3; j++) parr[i][j] = i + j; parr[1][1] = -5; 2) }
// Код функции храним в файле cfun.c // Введем 6 целочисленных значений // 0 1 2 1 2 3 // Имя этого элемента в Фортране - parr (2,
7.7.1.4. Передача целочисленных указателей Целочисленные указатели Фортрана подобны указателям СИ и занимают в памяти 4 байта. Передавая целочисленные указатели из Фортрана в процедуру, написанную на другом языке, придерживайтесь правил:
•
передаваемый из Фортрана параметр должен быть целочисленным указателем, а не адресной переменной, с которой этот указатель связан;
•
в нефортрановской процедуре параметр должен быть объявлен как указатель, тип которого соответствует типу адресной переменной Фортрана. Пример:
program fortmain interface subroutine cfun(pa) !dec$attributes c :: cfun integer(4) :: pa
! Код запишем в файле fmain.f90 ! Исполняемый файл создаем командой ! DF fmain.f90 cfun.obj /nodebug
⎯ 242 ⎯
7. Программирование на нескольких языках
end subroutine cfun end interface real(4), dimension(10) :: array, var pointer(pa, var) pa = loc(array) call cfun(pa) print *, ' array(5) = ', array(5) end program fortmain
! var - адресная переменная ! Тип pa - INTEGER(4)
void cfun(float *pa) { pa[4] = 55.5; }
// Код СИ-функции храним в файле cfun.c // Изменение pa повлечет изменение array // Компилируем командой CL /c cfun.c
! array(5) =
55.50000
Принимая в Фортране указатель из процедуры, написанной на другом языке, выполняйте условия: •
параметр, получаемый Фортраном, следует объявить как целочисленный указатель. Оператор POINTER должен ассоциировать его с адресной переменной, тип которой соответствует типу передаваемого указателя;
•
в нефортрановской процедуре параметр должен быть объявлен как указатель соответствующего типа и передан привычным образом. Пример:
subroutine fpoint(pa) !dec$attributes c :: fpoint real(4) :: var(10) pointer(pa, var) var(5) = 55.5 end subroutine fpoint
! Код запишем в файле ffun.f90 ! Исполняемый файл создаем командой ! DF cmain.obj ffun.f90 /nodformain /nodebug
void main( ) { float array[10]; extern void fpoint(float *); массива array fpoint(array); printf("array[4] = %6.2f", array[4]); }
// Код СИ-функции храним в файле cmain.c //
Тип
указателя
определяется
// array[4] = 55.50 // Компилируем командой CL /c cmain.c
7.7.1.5. Имена модулей
Рассмотрим модуль module fortmod integer(4) :: ia contains subroutine modsub(b)
типом
! Разместим модуль в файле fmod.f90 ! Модульная подпрограмма
⎯ 243 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
real(4) :: b b = 3.0 end subroutine modsub end module fortmod
Выполним компиляцию модуля командой DF fmod.f90 /c /nodebug. Тогда в объектном файле fmod.obj компилятор преобразует определенные в модуле fortmod имена ia и modsub следующим образом: •
имя ia - в _FORTMOD_mp_IA
•
имя modsub - в _FORTMOD_mp_MODSUB@4 Общее правило преобразования имен таково: имя entity модуля MODULEMANE преобразовывается в имя _MODULEMANE_mp_ENTITY[@размер стека] Причем независимо от применяемого соглашения составляющие имени MODULEMANE и ENTITY всегда записываются прописными буквами. Составляющая имени @размер стека присутствует только в имени модульной процедуры в случаях, когда не задано соглашение C. Если же оно задано, то составляющая @размер стека компилятором не генерируется, например: module fortmod integer(4) :: ia contains subroutine modsub(b) !dec$attributes с :: modsub объектном ...
! Разместим модуль в файле fmod.f90
!
Имя
модульной
подпрограммы
в
! файле - _FORTMOD_mp_MODSUB
7.7.1.6. Доступ к объектам модулей Фортрана в функциях СИ Рассмотрим модуль fortmod, ссылка на который выполняется в программе fortmain и объекты которого используются СИ-функцией cfun. module fortmod integer(4) :: ia real(4), dimension(5) :: arr character(80) :: string = ' ' строки type modtype logical(4) :: flag character(20) :: name end type modtype
! Разместим модуль fortmod и главную ! программу fortmain в файле fmain.f90 ! Желательно выполнить инициализацию ! иначе она будет заполнена null-символами
⎯ 244 ⎯
7. Программирование на нескольких языках
type(modtype) :: mtp = modtype(.false., ' ') contains subroutine modsub(b) !dec$attributes c, reference :: modsub ! Параметр b передадим по ссылке real(4) :: b b = 3.0 end subroutine modsub end module fortmod program fortmain use fortmod interface subroutine cfun( ) !dec$attributes c :: cfun end subroutine cfun end interface call cfun( ) print *, ia, arr(3), ' ', trim(string) print *, mtp.flag, ' ', trim(mtp%name) end program fortmain
! Интерфейс СИ-функции cfun
! Изменим объявленные в модуле данные ! Данные модифицировались в СИ-функции ! cfun
Доступ к объектам модуля в СИ выполняется непосредственно: объекты модуля объявляются как внешние (внутри или вне функций) и затем они используются в функциях СИ, например: #include <string.h> // Ведущий символ подчеркивания перед внешним extern int FORTMOD_mp_IA; // именем в исходном коде должен отсутствовать extern float FORTMOD_mp_ARR[5]; extern char FORTMOD_mp_STRING[80]; extern struct { int flag; char name[20]; } FORTMOD_mp_MTP; extern void FORTMOD_mp_MODSUB(float *b); void cfun( ) { float b; FORTMOD_mp_IA = -3; // Модифицируем данные модуля fortmod FORTMOD_mp_ARR[2] = 55.0; // Меняем значение третьего элемента массива strcpy(FORTMOD_mp_STRING, "Entered in C function"); FORTMOD_mp_MTP.flag = 1; strcpy(FORTMOD_mp_MTP.name, "Mike & Steve"); FORTMOD_mp_MODSUB(&b);
⎯ 245 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
printf("\nb = %7.3f\n", b); }
Замечание. При работе со строками, полученными из СИ, следует помнить, что они завершаются символом конца строки - null-символом. 7.7.1.7. Определение модульной процедуры в СИ Если модульная процедура написана на другом языке программирования, например на СИ, то для ее использования в Фортране следует в модуле, которому эта процедура принадлежит, объявить ее интерфейс. Пусть задана СИ-функция cname. #include <math.h> float cname(float x, float y) { С return sqrt(x * x + y * y); }
// Для встроенной функции sqrt // По умолчанию используется соглашение // Код разместим в файле cfun.c
которую надо использовать в качестве модульной процедуры модуля fortmod. Чтобы получить такую возможность, достаточно объявить в модуле интерфейс этой функции: module fortmod interface function cname(x, y) !dec$attributes c :: cname real(4) :: cname, x, y end function cname end interface end module fortmod program fortmain use fortmod print *, cname(3.0, 4.0) end program fortmain
! Разместим модуль fortmod и главную ! программу fortmain в файле fmaim.f90 ! Исполняемый файл получим командой ! DF fmain.f90 cfun.obj /nodebug
! Вызов модульной функции cname
7.7.2. Использование common-блоков Фортрана и структур СИ 7.7.2.1. Прямой доступ к common-блокам Фортрана и структурам СИ Данные, объединяемые в Фортране common-блоком, в СИ представляются в виде структуры. Однако при построении структуры СИ, соответствующей common-блоку Фортрана, следует учитывать, как выравнены данные в Фортране.
⎯ 246 ⎯
7. Программирование на нескольких языках
По умолчанию данные Фортрана выравниваются так: •
переменные common-блока типа BYTE, INTEGER(1), LOGICAL(1) и CHARACTER размещаются немедленно вслед за последним занятым байтом памяти;
•
переменные других типов начинаются на следующем четном байте, ближайшем к последнему байту, занятому переменными common-блока; то же справедливо и для массивов всех, кроме CHARACTER, типов;
•
массивы типа CHARACTER начинаются немедленно на первом свободном байте;
•
сами common-блоки начинаются на адресе, кратном числу 4.
Если переменные common-блока размещены в памяти по приведенным правилам (а это достигается, когда при компиляции Фортран-файла отсутствует опция /align:commons или /align:dcommons), то необходимо изменить правила размещения данных в соответствующей СИ-структуре, применив директиву #pragma pack(2). Пусть, например, задан common-блок a1: program fortmain fmain.f90 !dec$attributes с :: a1 вызовах, logical(2) :: flag имя integer(4), dimension(3) :: iarray character(len = 7) string1 common/a1/ flag, iarray, string1 interface subroutine cfun( ) !dec$attributes c :: cfun end subroutine cfun end interface call cfun( ) print *, flag, iarray(3), ' ', string1(1:1) end program fortmain
! Программу fortmain разместим в файле !
Обязательное
задание
соглашения
о
! определяющего в данном случае внешнее ! common-блока a1 как _a1
!T
55 R
Построение EXE-файла выполним командой DL fmain.f90 cfun.obj /nodebug, в которой cfun.obj - объектный файл СИ-функции cfun, использующей данные common-блока a1 и размещенной в файле cfun.c. Тогда common-блоку a1 соответствует приведенная в функции cfun СИструктура a1: ⎯ 247 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
void cfun( ) { #pragma pack(2) (компонентов и extern struct { short int flag; int iarray[3]; char string1[7]; } a1; #pragma pack( ) a1.flag = 1; a1.iarray[2] = 55; a1.string1[0] = 'R'; }
//
Выравнивание
СИ-структуры
// short int и int) по 2-байтовой границе
// Внешнее имя структуры - _a1 // Восстанавливаем заданное по умолчанию // выравнивание
Если же при построении исполняемого файла добавить опцию /align:commons, то директивы #pragma pack должны быть опущены: void cfun( ) { extern struct { // Естественное выравнивание компонентов short int flag; // структуры a1 int iarray[3]; char string1[7]; } a1; // Внешнее имя структуры - _a1 a1.flag = 1; a1.iarray[2] = 55; a1.string1[0] = 'R'; }
Замечание. Атрибут, задающий внешнее имя common-блока a1, !dec$attributes с :: a1
! Внешнее имя common-блока a1 - _a1
можно заменить выполняющим те же функции атрибутом ALIAS: !dec$attributes alias : '_a1' :: a1
! _a1 - внешнее имя common-блока a1
7.7.2.2. Передача адреса common-блока Чтобы передать адрес common-блока, нужно передать адрес его первой переменной. При этом СИ-функция, принимающая этот адрес, должна в качестве параметра принять адрес структуры, соответствующей commonблоку Фортрана. Например: program fortmain fmain.f90 integer(4) :: ival character(len = 7) string1 common/b2/ ival, string1 interface subroutine cfun(block) !dec$attributes c :: cfun integer(4) :: block
! Программу fortmain разместим в файле ! Исполняемый файл получим командой ! DF fmain.f90 cfun.obj /nodebug
! block -адрес common-блока b2
⎯ 248 ⎯
7. Программирование на нескольких языках
end subroutine cfun end interface call cfun(loc(ival)) блока b2 print *, ival, ' ', string1(1:1) end program fortmain extern struct b2 { int ival; используем char string1[7]; } block;
! Передаем адрес первого элемента common!
22 R
// СИ-код храним в файле cfun.c // Для получения объектного
кода
// команду CL /c cfun.c // Внешнее имя структуры - _b2
void cfun(struct b2 *block) { block->ival = 55; block->string1[0] = 'R'; }
Адрес b2 будет также передан в СИ-функцию cfun, если в нее передать первый элемент common-блока по ссылке. Для этого в Фортран-интерфейсе СИ-функции cfun используем атрибут REFERENCE: ... interface subroutine cfun(block) !dec$attributes c :: cfun !dec$attributes reference :: block integer(4) :: block end subroutine cfun end interface call cfun(ival) ...
! Соответствующий фактический параметр ! передается по ссылке ! Параметр ival передается по ссылке
Замечания: 1. Используйте в команде DF опции /align:commons или /align:dcommons, которые обеспечивают естественное выравнивание данных commonблоков. Следствием этого является не только согласованность данных со структурами СИ, но и повышение быстродействия приложения. 2. Если первым элементом common-блока является массив, то применение для него атрибута REFERENCE будет ошибкой, поскольку массив всегда передается по ссылке.
7.7.3. Передача производных типов данных Производные типы данных Фортрана подобны структурам СИ. Переменные этих типов могут быть переданы через модули Фортрана и common-блоки. Разумеется, процедурам на другом языке, в которые ⎯ 249 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
передаются объекты производных типов, должны быть известны определения этих типов. Пример. Изменить переменную tp типа newtype в СИ-функции change_tp. program fortmain ! Код запишем в файле fmain.f90 interface ! Исполняемый файл создаем командой subroutine change_tp( ) ! DF fmain.f90 cfun.obj /nodebug !dec$attributes c :: change_tp end subroutine change_tp end interface type newtype sequence ! Обязательный при размещении данных integer(4) :: array(10) ! в common-блоке атрибут SEQUENCE complex(4) :: z character(30) :: string end type newtype type(newtype) :: tp = newtype(1, (1.0, 1.0), ' ') !dec$attributes alias : '_a_block' :: a_block common/a_block/ tp call change_tp( ) print *, tp%array(5), tp%z, ' ', tp%string(1:10) ! Результат: 55 (3.000000, 4.000000) New value end program fortmain #include <string.h> extern struct { struct { int array[10]; struct{ float real, imag; } z; char string[30]; } tp; } a_block;
// СИ-код храним в файле cfun.c // и компилируем командой CL /c cfun.c // Комплексный тип данных
void change_tp( ) { a_block.tp.array[4] = 55; // Изменяем данные структуры a_block.tp.z.real = 3.0; a_block.tp.z.imag = 4.0; strcpy(a_block.tp.string, "New value"); }
⎯ 250 ⎯
7. Программирование на нескольких языках
7.8. Особенности одновременного использования Фортрана и СИ Приложение, использующее Фортран и СИ, может быть создано командами CL и DF, запускаемыми из командной строки, либо в среде Visual Studio DVF. В последнем случае в проект Фортрана следует включить объектные файлы СИ либо указать пути к библиотекам, содержащим эти файлы. При выводе данных из процедур, написанных как на Фортране, так и на СИ, если не принять специальных мер, результаты могут появляться не в том порядке, какой предусмотрен алгоритмом. Это объясняется тем, что СИ накапливает данные в буфере и выполняет вывод после того, как буфер заполнился целиком. Чтобы восстановить требуемый порядок вывода, следует принудительно освобождать СИ-буфер, применяя функции flushall, fflush, fclose, setbuf или setvbuf. При многониточном программировании должны быть использованы соответствующие библиотеки и в Фортране и в СИ: DFORMT.LIB - для Фортрана и LIBCMT.LIB - для СИ.
7.9. Включение Фортран-процедур в приложения на Бейсике При совмещении Фортрана и Бейсика (32-битовый Microsoft Visual Basic 4-й версии) используются вычислительные возможности Фортрана и средства Бейсика по построению интерфейсов. Включение файлов Бейсика в Фортран невозможно, поскольку Бейсик не компилируется, а интерпретируется без создания объектного кода. Вместо этого Бейсик создает OLE-объекты, в которые экспортируются свойства и процедуры и которые можно использовать для вызова Бейсика из Фортрана. Создадим простое приложение, содержащее кнопку, активизирующую код Бейсика, в котором выполняется вызов процедуры Фортрана. Откроем Visual Basic и сделаем следующее: 1) выполним File - New project. Добавим из панели инструментов в форму командную кнопку и текстовое поле. Изменим название (Caption) командной кнопки, ударив по ней мышью и выбрав в появившемся списке свойств поле Caption; 2) ударим дважды мышью по командной кнопке и в появившееся окно занесем код: ⎯ 251 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Private Sub Command1_Click( ) Static arr(1 To 5) As StringArray Call FORSTRARR(arr(1)) Text1.Text = "" For i% = 1 To 5 Text1.Text = arr(i%).strings + Chr$(13) + Chr$(10) Next i% End Sub
3) выполним File - Save project As и дадим форме имя vbfort.frm; проект сохраним под именем vbfort.mak; 4) выполним File - New Module и добавим в появившееся окно модуля код: Type StringArray strings As String * 35 End Type Declare Sub FORSTRARR Lib "d:\programs\vb4\fortvb.dll" (Myarray As StringArray)
5) сохраним модуль под именем vbfort.bas. Подпрограмму FORSTRARR Фортрана оформим в виде DLL-файла. Создадим для этого в среде VS DVF проект DLL и добавим в него файл под именем fortvb.f90 с кодом: subroutine forstrarr(arr) character(35) arr(5) arr = "This is a string from Fortran." end subroutine
Выполним построение файла fortvb.dll и скопируем его в директорию d:\programs\vb4. Выберем пункт меню Run и запустим проект на исполнение.
7.10. Создание приложений на Фортране и Ассемблере Ассемблер (Microsoft Macro Assembler) работает с Фортраном и СИ/СИ++. Порядок соединения Фортрана и Ассемблера такой же, как и в случае Фортрана и СИ: прежде получается объектный код программы на Ассемблере, затем командой DF создается приложение, в котором используются и код Фортрана и код Ассемблера.
⎯ 252 ⎯
7. Программирование на нескольких языках
7.10.1. Формирование результата функцией Ассемблера Процедура Ассемблера может возвращать значение, если ее прототип описан как функция. Данные размеров в 4 байта или меньше, кроме чисел с плавающей точкой, возвращаются в регистр EAX. Процедуры, возвращающие значение с плавающей точкой, помещают результат в стек процессора с плавающей точкой. Чтобы вернуть из Ассемблера в Фортран значения, большие 4 байтов, например комплексные числа и символьные строки, необходимо использовать особые соглашения. Функции Фортрана, возвращающие подобные величины, работают так: функция создает область в стеке для хранения реально возвращаемого значения. Когда вызывается процедура Ассемблера, в нее передается дополнительный параметр - адрес созданного пространства. Этот параметр передается последним. Процедура Ассемблера размещает результат по адресу, переданному дополнительным параметром, а затем копирует результат в регистр EAX. Возможные способы размещения результата приведены в табл. 7.10. Таблица 7.10. Размещение результата функцией Ассемблера Тип возвращаемого результата
Куда направляется результат
Целая или логическая величина размером в 4 байта или меньше
В регистр EAX
Переменная с плавающей точкой
В стек процессора с плавающей точкой
Структуры, размер которых превышает 4 байта
Возвращаемый результат - в стек; адрес результата - в регистр EAX
В случае структур следует обращать внимание на выравнивание данных, которое выполняется в Ассемблере и Фортране, по-разному: по умолчанию Ассемблер выравнивает структуры по 1-байтовой границе, а Фортран - по 8-байтовой. Это относится и ко вложенным структурам. Выход из положения - задать в Ассемблере выравнивание структур по слову.
7.10.2. Примеры программ на Фортране и Ассемблере В приводимых примерах рассмотрены варианты передачи различных типов данных из Фортрана в Ассемблер и обратно, а также варианты использования в Фортране процедуры Ассемблера и в качестве функции, и
⎯ 253 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
в качестве подпрограммы. Темы примеров взяты из поставляемой с Фортраном документации. В каждом примере получение объектного кода процедуры Ассемблера выполняется командой ML /c asmfile.asm генерирующей файл asmfile.obj. Используемая версия Ассемблера - MASM 6.0 фирмы Microsoft. Исполняемый файл fmain.exe получается командой DF fmain.f90 asmfile.obj /nodebug в которой fmain.f90 - имя файла, содержащего главную программу Фортрана, выполняющую вызов процедуры Ассемблера. Пример 1. Вызвать из Фортрана процедуру Ассемблера, которая вычисляет значение выражения a * 2 ** b. ! Текст главной Фортран-программы, вызывающей процедуру Ассемблера program fortmain ! Код храним в файле fmain.f90 interface function power2(a, b) ! _POWER2 - внешнее имя функции power2 !dec$attributes alias : '_POWER2' :: power2 integer(2) :: power2, a, b end function power2 end interface integer(2) :: a, b a = 3; b = 5 print *, '3 * 2 ** 5 = ', power2(a, b) end program fortmain ; Код на Ассемблере, вычисляющий выражение value * 2 ** exponent .386 .MODEL FLAT, STDCALL POWER2 PROTO STDCALL, value:PTR SDWORD, exponent:PTR SBYTE .CODE POWER2 PROC STDCALL, value:PTR SDWORD, exponent:PTR SBYTE mov ecx, value ; Загружаем адрес множителя value mov eax, [ecx] ; Загружаем сам множитель mov ecx, exponent ; Загружаем адрес показателя степени mov cl, [ecx] ; Загружаем показатель степени shl eax, cl ; Вычисляем 2 ** exponent ret ; Оставляем результат в регистре EAX POWER2 ENDP END
⎯ 254 ⎯
7. Программирование на нескольких языках
Пример 2. Вызвать из Фортрана процедуру Ассемблера, которая преобразовывает английские буквы строки Фортрана в прописные. ! Текст главной Фортран-программы, вызывающей процедуру Ассемблера, ! преобразующей строчные буквы в прописные program fortmain ! Код храним в файле fmain.f90 interface ! Используем заданное по умолчанию соглашение subroutine upper(string) ! Внешнее имя подпрограммы - _UPPER !dec$attributes alias : '_UPPER' :: upper character(*) string end subroutine upper end interface character(20) :: string = 'This is some text' print *, 'Mixed case: ', string ! Mixed case: This is some text ! Вместе со строкой передается и второй, скрытый, параметр - длина строки call upper(string) print *, 'Upper case: ', string ! Upper case: THIS IS SOME TEXT end program fortmain ; Код на Ассемблере, переводящий строчные английские буквы ; строки Фортрана в прописные .386 .MODEL FLAT, C ; Строка благодаря обозначению PTR передается по ссылке ; Вместе со строкой передается и второй, скрытый, параметр - длина строки ; text и ltext - строка, подлежащая преобразованию, и ее длина UPPER PROTO C, text:PTR SBYTE, ltext:DWORD .CODE UPPER PROC C, text:PTR SBYTE, ltext:DWORD mov edx, text ; Загружаем адрес начала строки mov ecx, edx ; Копируем стартовый адрес add ecx, ltext ; Вычисляем адрес конца строки jmp first ; Переход на начало цикла next: mov al, [edx] ; Загружаем следующий символ inc edx ; Увеличиваем адрес символа cmp al, 'a' ; Сравниваем с первой строчной буквой ; Переход к следующему символу, если он меньше 'a', иначе - продолжить jb first cmp al, 'z' ; Сравниваем с последней строчной буквой ; Переход к следующему символу, если он больше 'z', иначе - продолжить ja first sub al, 32 ; Преобразовываем в прописную букву
⎯ 255 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
mov [edx-1], al first: cmp edx, ecx jbe next ret UPPER ENDP END
; Помещаем символ обратно в строку ; Проверяем, достигнут ли конец строки ; Продолжаем, если это не так ; Выход при достижении конца строки
Пример 3. Вызвать из Фортрана функцию Ассемблера, возвращающую строку, в которой строчные английские буквы преобразованы в прописные. В этой задаче выполняются те же преобразования, что и в предыдущей: отличие в том, что процедура Ассемблера UPPER2 вызывается в Фортране как функция, а не как подпрограмма. ! Текст главной Фортран-программы, вызывающей функцию Ассемблера, ! преобразующей строчные буквы в прописные program fortmain ! Код храним в файле fmain.f90 interface ! Используем заданное по умолчанию соглашение function upper2(string) ! Внешнее имя подпрограммы - _UPPER2 !dec$attributes alias : '_UPPER2' :: upper2 character(25) :: upper2 character(*) :: string end function upper2 end interface character(20) :: string1 = 'This is some text.' character(30) :: string2 = 'This is some more longer text.' print *, 'UPPER1 = ', upper2(string1) ! UPPER1 = THIS IS SOME TEXT. print *, 'UPPER2 = ', upper2(string2) ! UPPER2 = THIS IS SOME MORE LONGER end program fortmain ; Функция UPPER2 возвращает строку, в которой строчные буквы преобразованы ; в прописные .386 .MODEL FLAT, C ; Строка благодаря обозначению PTR передается по ссылке ; Вместе со строкой передается и второй, скрытый, параметр - длина строки ; retval и lretval - скрытый адрес результирующей строки и ее длина ; text и ltext - строка, подлежащая преобразованию, и ее длина ; В Фортране функция типа CHARACTER(*) передает адрес, по которому ; размещается результат, и длину результата UPPER2 PROTO C, retval:PTR SBYTE, lretval:DWORD, text:PTR SBYTE, ltext:DWORD .CODE
⎯ 256 ⎯
7. Программирование на нескольких языках
UPPER2 PROC C USES edi esi, retval:PTR SBYTE, lretval:DWORD, text:PTR SBYTE, ltext:DWORD mov esi, text ; Загружаем адреса обеих строк mov edi, retval mov edx, ltext ; Загружаем длину обеих строк mov ecx, lretval cmp ecx, edx ; Сравниваем длину строк jbe next ; Усекаем строку text, если больше результата, xchg ecx, edx ; иначе добавляем хвостовые пробелы sub edx, ecx ; Вычисляем число добавляемых пробелов next: lodsb ; Загружаем следующий символ cmp al, 'a' ; Сравниваем с первой строчной буквой ; Переход к следующему символу, если он меньше 'a', иначе - продолжить jb gotcase cmp al, 'z' ; Сравниваем с последней строчной буквой ; Переход к следующему символу, если он больше 'z', иначе - продолжить ja gotcase sub al, 32 ; Преобразовываем в прописную букву gotcase: stosb ; Размещаем символ в строку - результат loop next ; Продолжаем, пока не скопированы все символы cmp edx, ltext ; Если EDX не изменен, готово je done mov al, ' ' ; Загружаем пробел mov ecx, edx ; Копируем число оставшихся пробелов rep stosb ; Сохраняем их по адресу результата done: mov eax, retval ; Загружаем адрес результата ret ; Выход. Адрес результата размещен в EAX UPPER2 ENDP END
Пример 4. Вызвать из Фортрана функции Ассемблера, возвращающие вещественную и комплексную величину. program fortmain ! Код храним в файле fmain.f90 interface function returnr( ) !dec$attributes alias : '_RETURNR' :: returnr real(4) :: returnr
⎯ 257 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
end function returnr function returnc( ) !dec$attributes alias : '_RETURNC' :: returnc complex(4) :: returnc end function returnc end interface print *, 'Real = ', returnr( ) ! Real = 5.000000 print *, 'Complex = ', returnc( ) ! Complex = (1.000000, 2.000000) end program fortmain ; Процедура в зависимости от использованного при вызове имени возвращает ; либо вещественное, типа REAL4, либо комплексное, типа COMPLEX8, число .386 .MODEL FLAT, STDCALL COMPLEX8 STRUCT 4 real REAL4 ? imag REAL4 ? COMPLEX8 ENDS RETURNR PROTO STDCALL RETURNC PROTO STDCALL, retval: PTR COMPLEX8 .DATA ; Объявление формирующих результат данных r REAL45.0 cr REAL4 1.0 ci REAL4 2.0 .CODE RETURNR PROC STDCALL fld r ret RETURNR ENDP
; Загружаем результат типа REAL4 ; Результат находится в стеке процессора
RETURNC PROC STDCALL, retval: PTR COMPLEX8 mov edx, retval ; Загружаем адрес возвращаемой структуры fld cr ; Загружаем вещественный компонент fst (COMPLEX8 PTR [edx]).real ; Сохраняем вещественный компонент fld ci ; Загружаем мнимый компонент fst (COMPLEX8 PTR [edx]).imag ; Сохраняем мнимый компонент mov eax, edx ; Перемещаем адрес возвращаемой структуры в EAX ret ; Возвращаем адрес результата в регистре EAX RETURNC ENDP END
⎯ 258 ⎯
Приложение 1. Директивы DVF П. 1.1. Обзор директив Директивы - это специальные включаемые в программу инструкции, которые сообщают компилятору, какие следует предпринять действия при компиляции программы. Помимо директив процесс компиляции может управляться и из командной строки (см. гл. 6). Однако управление директивами является более гибким, поскольку последние могут быть включены, затем выключены или изменены в различных местах исходного текста программы. При возникновении конфликта между опцией компилятора и директивой приоритет имеет директива. Воздействие директивы распространяется на часть кода, расположенного после появления в нем директивы. В большинстве случаев действие директивы можно отменить или изменить. Так, многие директивы имеют директиву, отменяющую их действие, например $DECLARE и $NODECLARE, или могут быть заданы с различными значениями, например $REAL:8 устанавливает задаваемый по умолчанию размер вещественного типа данных равным 8 байтам, а $REAL:4 устанавливает этот размер равным 4 байтам. Таким образом, одна часть программы может быть откомпилирована согласно директиве $REAL:8, а другая - $REAL:4; в одной части программы код может быть записан в фиксированном (директива $NOFREEFORM), а в другой - в свободном ($FREEFORM) формате. И т. д. Директивы подразделяются на категории: •
директивы $STRICT, $NOFREEFORM и $FIXEDFORMLINESIZE, сообщающие компилятору о необходимости выявления в исходном коде отличий от стандарта ($STRICT) или заданного формата. Используются, если необходимо создать переносимый на другие компиляторы код. Для первых двух директив существуют обратные: $NOSTRICT и $FREEFORM;
•
директивы, обеспечивающие компиляцию фрагмента программы при выполнении некоторого условия: $DEFINE, $UNDEFINE, $IF, $IF DEFINED, $ELSE, $ELSEIF и $ENDIF;
•
директивы, управляющие $NODECLARE и $MESSAGE;
процессом
⎯ 259 ⎯
отладки:
$DECLARE,
О. В. Бартеньев. Visual Fortran: новые возможности
•
директивы, изменяющие задаваемый по умолчанию размер встроенных целого и вещественного типов данных: $INTEGER и $REAL;
•
директивы, управляющие печатью листинга исходного кода: $TITLE и $SUBTITLE;
•
директива $OBJCOMMENT, помещающая полное имя библиотеки в объектный код для ее поиска компоновщиком;
•
директива $OPTIONS управляет упаковкой данных производных типов и общих блоков;
•
директива $PACK, управляющая начальным адресом сохранения компонентов производных типов;
•
директива $PSECT изменяет некоторые характеристики общих блоков;
•
директива $ATTRIBUTES объявляет DEC-атрибуты;
•
директива $ALIAS задает альтернативное внешнее имя процедуры или общего блока;
файла
•
директива $IDENT задает идентификатор объектного файла. Замечание. Необязательные элементы директив будут заключаться в квадратные скобки.
П. 1.2. Использование директив Директиве компилятора предшествует префикс [cDEC]$ в котором c - это C (или c) или !; первые 4 символа префикса - cDEC - могут быть опущены. Любая строка исходного текста, содержащая символ доллара ($) в первой позиции или начинающаяся с символов cDEC$, интерпретируется как директива. С префиксом связаны правила: 1) префикс, начинающийся с C (или c), допустим лишь в коде, написанном в фиксированном формате, причем он должен появляться в позициях 15; 2) префикс, начинающийся с !, допустим при любом формате исходного кода, однако при фиксированном формате размещение префикса подчиняется правилам, приведенным в пп. 1 и 3. Символы !DEC$ могут начинаться в любой позиции строки. Им могут предшествовать только пробелы и символы табуляции; ⎯ 260 ⎯
Приложение 1. Директивы DVF
3) буквы DEC могут быть прописными, строчными или смешанными; 4) четыре символа сDEC$ должны быть записаны без встроенных пробелов; пробелы после $ игнорируются; 5) любая директива может начинаться с префикса !MS$. Примеры: !dec$strict $strict !dec$ freeform
! Пробелы между !DEC$ и FREEFORM допустимы
После директивы можно добавить комментарий, который должен предваряться восклицательным знаком, например: !dec$freeform
! Свободный формат записи исходного кода
Замечание. В случае ошибочного задания директивы она, если в качестве префикса используется CDEC$ или !DEC$, воспринимается как комментарий и ожидаемого эффекта не достигается. Поэтому надо предельно внимательно отслеживать синтаксис используемых директив. Директива и ее параметры должны располагаться на одной строке программы, поскольку продолжение строки с директивой невозможно. Большинство директив могут размещаться на любой строке исходного файла. Их действие, если они не отменены другой директивой, распространяется до конца файла. Это позволяет задавать разные директивы компилятору для разных фрагментов исходного кода. Однако существуют ограничения. Нельзя разместить директиву $OPTIONS среди исполняемых операторов. Директивы $INTEGER, $REAL, $STRICT и $NOSTRICT могут появляться только в верхней части программной единицы: в головной программе, во внешней процедуре, в модуле и BLOCK DATA. Действие директив распространяется на любой включаемый (INCLUDE) файл, который, правда, может содержать свои собственные директивы. Директивы, расположенные внутри включаемого файла, оказывают действие как на код включаемого файла, так и на расположенный вслед за ним код файла-хозяина, т. е. файла, в который выполняется вставка кода. Однако если директива внутри включаемого файла изменяет формат записи исходного кода или длину строки ($FREEFORM, $NOFREEFORM или $FIXEDFORMLINESIZE), то ее действие распространяется только на включаемый файл и не затрагивает код файла-хозяина.
⎯ 261 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Директивы программы, которая подключает модуль оператором USE, не оказывают влияния на код модуля. Поэтому для управления компиляцией модуля в него следует включить свои собственные директивы. Поскольку директива $PACK оказывает воздействие на распределение памяти, то ее нельзя размещать внутри управляющих конструкций (например, внутри конструкции IF - THEN - ELSE) или внутри определений производных типов данных. Конструкция с директивой $IF может включать обычные операторы Фортрана. Правда, эти операторы должны быть исключены при использовании кода в других системах программирования.
П. 1.3. Директивы, контролирующие правила написания исходного кода П. 1.3.1. Директивы $STRICT и $NOSTRICT Директива $STRICT указывает компилятору на необходимость выявления элементов DVF, не соответствующих стандарту Фортрана. $NOSTRICT (задается по умолчанию) позволяет использование всех имеющихся в DVF расширений. Синтаксис директив: cDEC$STRICT и cDEC$NOSTRICT Директивы $STRICT и $NOSTRICT могут появляться только в верхней части программной единицы: главной программе, внешней процедуре, модуле или BLOCK DATA - и не могут появляться между программными единицами или в начале внутренней процедуры. Они не оказывают влияния на включаемые оператором USE модули. Пример: type stuff integer(4) :: k, m character(40) :: name end type stuff type (stuff) :: examp double complex :: cd=(3.0d0,4.0d0) examp.k = 4 end
! $NOSTRICT по умолчанию
! Нестандартный тип - нет ошибки ! Нестандартный селектор ! компонента - нет ошибки
subroutine strictdemo( ) !dec$strict type stuff
⎯ 262 ⎯
Приложение 1. Директивы DVF
integer(4) :: k, m character(40) :: name end type stuff type (stuff) :: samp double complex :: cd = (3.0d0,4.0d0) samp.k = 4 end subroutine
! Ошибка. Правильно COMPLEX(8) ! Ошибка. Правильно samp%k = 4
П. 1.3.2. Директивы $FREEFORM и $NOFREEFORM Директива $FREEFORM позволяет записывать исходный код в свободном формате. Директива $NOFREEFORM, наоборот, указывает на необходимость записи текста программы в фиксированном формате (см. прил. 4). Синтаксис: cDEC$FREEFORM и cDEC$NOFREEFORM Существует 2 способа установить по умолчанию свободный формат записи исходного кода: •
использовать расширение F90 для файлов с исходным кодом;
•
использовать опцию компилятора.
Если ни один из этих способов не использован, то по умолчанию устанавливается фиксированный формат записи исходного кода. Дополнительно к существующим правилам умолчания можно установить директивой $FREEFORM свободный, а директивой $NOFREEFORM фиксированный формат записи исходного кода. Когда применена директива $FREEFORM или $NOFREEFORM, она действует на всю расположенную вслед за ней часть файла или до тех пор, пока не использована отменяющая ее директива. В случае применения директива воздействует на INCLUDE-файлы, но не действует на USEмодули, которые компилируются отдельно. Длину строки фиксированного формата можно изменить директивой $FIXEDFORMLINESIZE. Пример: !dec$freeform integer(4) :: k, m k = 4; m = 5 print 1, k, m 1 format(2i4) call demo( ) end
! Свободный формат записи исходного кода
subroutine demo( )
⎯ 263 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
$nofreeform integer(4) :: k = 4, m = 5 print 1, k, m 1 format(2i4) end subroutine demo
! Фиксированный формат. Для отображения ! в среде DVF разделяющих полос ! директиву следует начать с первой ! позиции строки и опустить !DEC
П. 1.3.3. Директива $FIXEDFORMLINESIZE Директива $FIXEDFORMLINESIZE устанавливает длину строки фиксированного формата исходного кода. Ее синтаксис: cDEC$FIXEDFORMLINESIZE: 72 | 80 | 132 Применяя директиву, можно установить длину строки в фиксированном формате равной 72, 80 или 132 символам. По умолчанию длина строки составляет 72 символа. $FIXEDFORMLINESIZE действует либо до конца файла, либо до следующей, изменяющей ее директивы. Как и другие директивы, она воздействует на INCLUDE-файлы, но не действует на USEмодули. Если INCLUDE-файл изменяет длину строки, то изменения не влияют на длину строки файла-хозяина. Директива $FIXEDFORMLINESIZE не оказывает действия на код, написанный в свободном формате. Пример: !dec$nofreeform !dec$fixedformlinesize:132 print *, 'Пишем эту строку за пределами 72-й колонки без продолжения'
П. 1.4. Условная компиляция программы П. 1.4.1. Директивы $DEFINE и $UNDEFINE Директива $DEFINE создает символическую переменную, существование которой может быть проверено во время условной компиляции. $UNDEFINE удаляет созданную $DEFINE переменную. Синтаксис: cDEC$DEFINE symbol-name [ = val] и cDEC$UNDEFINE symbol-name symbol-name - имя переменной, содержащее до 31 символа; может начинаться с $ и знака подчеркивания и не может начинаться с цифры. val - присвоенное symbol-name значение. Тип val - INTEGER(4). Директивы $DEFINE и $UNDEFINE создают и удаляют переменные для использования с директивами $IF и $IF DEFINED. Имя, определенное директивой $DEFINE, является локальным (используется только другими ⎯ 264 ⎯
Приложение 1. Директивы DVF
директивами и недоступно в программе) и поэтому может быть повторно объявлено в Фортран-программе. То есть $DEFINE-имена могут дублировать имена объектов данных программы без каких-либо конфликтов. Для проверки того, определен символ или нет, используется директива $IF DEFINED (symbol-name). Для проверки присвоенного symbol-name значения используется директива $IF. В логических выражениях директивы $IF могут использоваться большинство логических и арифметических операций Фортрана. Попытка удалить директивой $UNDEFINE символ, который не был определен, приведет к ошибке компиляции. Директивы $DEFINE и $UNDEFINE могут появляться в любом месте программы. Пример: !dec$define tflag real(4) :: tflag = 4 !dec$if defined (tflag) write (*,*) 'Компилирую строку A' !dec$else write (*,*) 'Компилирую строку B' !dec$endif print *, tflag !dec$define tf2 = 2 !dec$if (tf2 == 1) write (*, *) 'Компилирую строку C' !dec$else write (*, *) 'Компилирую строку D' !dec$endif end
! Нет конфликта между именами ! 4.000000 ! или (tf2 .EQ. 1)
Потребность в применении директив условной компиляции возникает на этапе создания исходного кода. Например, используя символическое имя, можно иметь в исходном тексте 2 варианта кода для некоторого фрагмента алгоритма, компилируя тот или иной вариант кода в зависимости от значения введенного директивой имени. После отладки неиспользуемый код может быть удален. Если в директиве $DEFINE имени присваивается целочисленное значение, то такое имя можно использовать в другой директиве $DEFINE для определения значения другого символического имени, например: !dec$define firstsym = 100000
⎯ 265 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
!dec$define receiver = firstsym ! Имя firstsym получило ... ! целочисленное значение в одной из !dec$if receiver .ne. 100000 ! предшествующих метакомад $define write(*, *) "Компилируем эту часть кода" ... !dec$else write(*, *) "Компилируем другую часть кода" ... !dec$endif
П. 1.4.2. Конструкции директив $IF и $IF DEFINED Конструкции директив условной компиляции $IF и $IF DEFINED используются для выполнения условной компиляции. Директивы условной компиляции начинаются с $IF или $IF DEFINED и заканчиваются директивой $ENDIF. Конструкция может включать одну или несколько директив $ELSEIF и одну директиву $ELSE. Если некоторое логическое условие в конструкции вычисляется со значением .TRUE., а все предшествующие логические условия в конструкции $IF вычисляются со значением .FALSE., то выполняется компиляция содержащихся в блоке директивы операторов. Синтаксис директив: cDEC$IF[(]expr[)] или cDEC$IF DEFINED(symbol_name) statementblock [cDEC$ELSEIF[(]expr[)] statementblock] [cDEC$ELSE statementblock] cDEC$ENDIF expr - вычисляемое со значением .TRUE. или .FALSE. логическое выражение. symbol_name - определяемое директивой $DEFINE или опцией компилятора /define символическое имя. В директиве $IF DEFINED проверяется существование символа. Удаление ранее определенного символа выполняется директивой $UNDEFINE. statementblock - операторы программы, которые компилируются или не компилируются в зависимости от значения логических выражений в конструкции $IF. $IF DEFINED(symbol_name) - вычисляется со значением .TRUE., если symbol_name определено директивой $DEFINE (со значением или без) и не удалено директивой $UNDEFINE, в противном случае $IF ⎯ 266 ⎯
Приложение 1. Директивы DVF
DEFINED(symbol_name) вычисляется со значением .FALSE.. Директиве $IF DEFINED (symbol_name) недоступны определенные в тексте программы имена. Если логическое условие директив $IF или $IF DEFINED есть .TRUE., то компилируются операторы следующего за этими директивами statementblock. Если условие вычисляется со значением .FALSE., то управление передается следующим директивам $ELSEIF или $ELSE (при наличии таковых). Таким же образом обрабатывается логическое условие директивы $ELSEIF. Если управление передано директиве $ELSE, то выполняется следующий за директивой statementblock. Наличие закрывающей конструкцию $IF директивы $ENDIF обязательно. В логических выражениях директив можно использовать любые логические операции и операции отношения Фортрана: .LT., <, .GT., >, .EQ., ==, .LE., <=, .GE., >=, .NE., /=, .EQV., .NEQV., .NOT., .AND., .OR. и .XOR.. Логические выражения могут быть любой сложности, но вся директива (включая условие) должна быть размещена на одной строке. Приоритет выполнения логических операций такой же, как и в стандартных выражениях отношения и логических выражениях Фортрана. В выражениях директив условной компиляции логические операции .EQV., .NEQV., .NOT., .AND., .OR. и .XOR. могут быть использованы только с логическими величинами. Пример: !dec$define flag = 3 !dec$if (flag .lt. 2) print *, "Компилирую этот блок, если flag < 2" !dec$elseif (flag >= 8) print *, "Компилирую этот блок, если flag >= 8" !dec$else print *, "Компилирую этот блок, если предшествующие условия - ложь" !dec$endif end
Условная компиляция может быть использована и в модуле. Однако нельзя использовать условную компиляцию для всего модуля в среде DVF, поскольку невозможно будет установить необходимые связи в проекте до компиляции. Например, следующий фрагмент ошибочен: !dec$if defined (debug) module mod real :: b = 3.0
⎯ 267 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
end module !dec$endif
А этот текст компилируется без ошибок: module mod !dec$if defined (debug) real :: b = 3.0 !dec$endif end module
Директивы условной компиляции ($IF, $IF DEFINED, $ELSE, $ELSEIF и $ENDIF) упрощают процесс разработки программы, позволяя пропускать незавершенный код, использовать или пропускать код тестовых вставок, настраивать код для специальных приложений путем подключения и исключения соответствующих фрагментов.
П. 1.5. Управление отладкой программы П. 1.5.1. Директивы $DECLARE и $NODECLARE Директива $DECLARE вырабатывает предупреждение для переменных, если они не появились в операторах объявления типа (работает подобно IMPLICIT NONE). $NODECLARE (устанавливается по умолчанию) отключает выработку предупреждений. Синтаксис: cDEC$DECLARE или cDEC$NODECLARE Директива $DECLARE преимущественно используется на этапах отладки программы для выявления необъявленных либо объявленных, но неиспользующихся переменных.
П. 1.5.2. Директива $MESSAGE Директива посылает символьную строку на стандартное устройство вывода (экран) во время первого прохода компилятора. Предназначена для облегчения процесса отладки. Синтаксис: cDEC$MESSAGE:string string - заключенная в одинарные или двойные кавычки строка сообщений. В среде DVF сообщения выводятся в окно Build. Пример: !dec$message: 'Компиляция подпрограммы setpoint( )'
⎯ 268 ⎯
Приложение 1. Директивы DVF
П. 1.6. Выбор задаваемой по умолчанию разновидности типа П. 1.6.1. Директива $INTEGER Директива устанавливает задаваемое по умолчанию значение параметра разновидности целого типа. Синтаксис: cDEC$INTEGER: 2 | 4 Пользуясь директивой $INTEGER, можно установить задаваемую по умолчанию разновидность целого типа с параметрами разновидности KIND = 2 или KIND = 4. Директива оказывает действие только на объекты данных, объявляемые оператором INTEGER без указания параметра разновидности, и не оказывает действия на буквальные константы и на объекты, введенные без объявления типа. При отсутствии директивы (если не задана опция компилятора /4I2) по умолчанию устанавливается разновидность целого типа с KIND = 4. Директива $INTEGER может появляться только в верхней части программной единицы: головной программы, внешней процедуры, модуля или BLOCK DATA. $INTEGER не может появляться между программными единицами или в начале внутренней процедуры. Директива не воздействует на модули, включаемые оператором USE. Задаваемые по умолчанию разновидности логического и целого типа данных всегда совпадают. Таким образом, действие директивы $INTEGER распространяется и на логические объявленные оператором LOGICAL объекты данных. Пример: integer :: i logical :: f k=2 print *, kind(i), kind(k) print *, kind(fl), kind(.false.) call integer2( ) print *, kind(i), kind(k) end
! 4-байтовая целочисленная переменная ! 4-байтовая логическая переменная ! 4-байтовая целочисленная переменная ! 4 4 ! 4 4
subroutine integer2( ) !dec$integer:2 integer :: j logical :: fl print *, kind(j), kind(3)
! $INTEGER:2 не оказывает действия ! на вызывающую программную единицу ! 2-байтовая целочисленная переменная ! 2-байтовая логическая переменная ! 2 4
!
4
⎯ 269 ⎯
4
О. В. Бартеньев. Visual Fortran: новые возможности
print *, kind(fl), kind(.true.) end subroutine
!
2
4
П. 1.6.2. Директива $REAL Директива устанавливает задаваемое по умолчанию значение параметра разновидности вещественного типа. Синтаксис: cDEC$REAL: 4 | 8 Пользуясь директивой $REAL, можно установить задаваемую по умолчанию разновидность вещественного типа с параметрами разновидности KIND = 4 или KIND = 8. Директива оказывает действие только на объекты данных, объявляемые оператором REAL без указания параметра разновидности, и не оказывает действия на буквальные константы и на объекты, введенные без объявления типа. При отсутствии директивы (если не задана опция компилятора /4R8) по умолчанию устанавливается разновидность вещественного типа с KIND = 4. Директива $REAL может появляться только в верхней части программной единицы: головной программы, внешней процедуры, модуля или BLOCK DATA. Директива $REAL не может появляться между программными единицами или в начале внутренней процедуры. Директива не воздействует на модули, включаемые оператором USE. Пример: real :: r write(*, *) kind(r) call real8( ) write(*, *) kind(r) end subroutine real8( ) !dec$real:8 real :: s write(*, *) kind(s) end subroutine
! 4-байтовая вещественная переменная ! 4 !
4
! 8-байтовая вещественная переменная ! 8
⎯ 270 ⎯
Приложение 1. Директивы DVF
П. 1.7. Управление печатью листинга исходного кода П. 1.7.1. Директива $TITLE Директива задает вывод специального заголовка на каждой странице листинга. Заголовок выводится до тех пор, пока не изменен другой директивой $TITLE. Синтаксис: cDEC$TITLE: title title - символьная буквальная константа. $TITLE может появляться в любом месте исходного кода. Для отключения вывода заголовка следует задать директиву $TITLE, в которой опция title является пустой строкой. При отсутствии директивы никакого заголовка не выводится. Пример: !dec$title: 'Расчет прочности кузова. Версия 2.0 25.11.99' integer i, j, k real a, b, c call track(i, j, k, a, b, c) ...
П. 1.7.2. Директива $SUBTITLE Директива задает вывод специального подзаголовка на каждой странице листинга. Подзаголовок выводится до тех пор, пока не изменен другой директивой $SUBTITLE. Синтаксис: cDEC$SUBTITLE: subtitle subtitle - символьная буквальная константа. При отсутствии директивы никакого подзаголовка не выводится. $SUBTITLE может появляться в любом месте исходного кода. Для отключения вывода заголовка следует задать директиву $SUBTITLE, опция subtitle которой является пустой строкой. Пример: !dec$title: 'Расчет прочности кузова. Версия 2.0 25.11.99 integer(4) :: i, j, k real(4) :: a, b, c call track (i, j, k, a, b, c) call odat (b, c) end
⎯ 271 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
subroutine track (i, j, k, a, b, c) !dec$subtitle: 'Подпрограмма track' integer(4) :: i, j, k real(4) :: a, b, c call stat(a, b) !dec$subtitle: '' end subroutine track
П. 1.8. Директива $OBJCOMMENT Директива помещает полное имя библиотеки с указанием пути в объектный файл. Синтаксис: cDEC$OBJCOMMENT LIB: "library" library - символьная буквальная константа, содержащая имя и при необходимости путь библиотеки, которую должен использовать компоновщик. Компоновщик ищет библиотеку, указанную в $OBJCOMMENT (тот же эффект имеет указание имени библиотеки в командной строке), прежде чем им выполняется поиск установленной по умолчанию библиотеки. Можно в одном исходном файле задать несколько директив, ссылающихся на разные библиотеки. Имена библиотек, которые должен использовать компоновщик, появляются в объектном файле в порядке их введения директивами $OBJCOMMENT в исходном коде. Если директива $OBJCOMMENT появляется в пределах модуля, то каждая использующая модуль программная единица также включает эту директиву. Если необходимо включить директиву в модуль, но не передавать ее в использующие модуль программные единицы, то следует поместить $OBJCOMMENT перед предложением MODULE имя_модуля, например: module a !dec$objcomment lib: "opengl32.lib" ... end module a
! файл mod1.f90
!dec$objcomment lib: "graftools.lib" module b ... end module b
! файл mod2.f90
program go use a объектный код
! файл user.f90 ! Поиск библиотеки
⎯ 272 ⎯
включается
в
Приложение 1. Директивы DVF
use b объектный код ... end
! Поиск библиотеки не включается в ! файла user.obj
П. 1.9. Директива $OPTIONS Директива $OPTIONS управляет выравниванием данных в структурах и общих блоках. Данные указанных объектов могут быть либо выравнены естественным образом, что, как известно, повышает производительность приложения, либо упакованы по произвольным байтовым границам. Синтаксис: cDEC$OPTIONS /[NO]ALIGN ( = p) p - спецификатор, принимающий одно из следующих значений: class = rule, или (class = rule, ...), или ALL, или NONE; class - ключевое слово: COMMONS - для общих блоков; RECORDS и STRUCTURES - для записей; rule - одна из следующих опций: •
PACKED - упаковывает записи или данные общих блоков по произвольным байтовым границам;
•
NATURAL - естественно выравнивает поля записей и данные общих блоков; наибольшее значение байтовой границы выравнивания - 8 байт. Опция несовместима со стандартом Фортрана, но позволяет выравнивать данные с параметром разновидности типа KIND = 8 и комплексные данные с любым значением параметра разновидности;
•
STANDARD - естественно выравнивает поля записей и данные общих блоков; наибольшее значение байтовой границы выравнивания - 4 байта. Опция совместима со стандартом Фортрана. Она применима только с общими блоками, поэтому можно задать /ALIGN = COMMONS = STANDARD, но вызовет ошибку задание правила /ALIGN = STANDARD; ALL - то же, что и задание /ALIGN, или /ALIGN = NATURAL, или /ALIGN = (RECORDS = NATURAL, COMMONS = NATURAL); NONE - то же, что и задание /NOALIGN, или /ALIGN = PACKED, или /ALIGN = (RECORDS = PACKED, COMMONS = PACKED). Директива $OPTIONS и сопровождающая ее директива $END OPTIONS должны следовать после операторов OPTIONS, или SUBROUTINE, или ⎯ 273 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
FUNCTION, или BLOCK DATA, если таковые присутствуют в программной единице, и перед ее исполняемыми операторами. DVF выравнивает данные по естественным границам, однако данные в общих блоках и поля структур могут быть невыравнены (см. гл. 6). Это приводит к замедлению быстродействия программы. Компилятор обнаруживает такие объекты данных и сообщает о них программисту. Выравнивание данных общих блоков достигается действиями: •
задайте /ALIGN = COMMONS = STANDARD для выравнивания по 32битовой границе;
•
задайте /ALIGN = COMMONS = NATURAL для выравнивания по 64-битовой границе;
•
разместите данные в общем блоке в порядке убывания их параметра разновидности типа. При необходимости создания упакованных структур выполните: 1) задайте /ALIGN = RECORDS = PACKED; 2) разместите в структуре объявления данных таким образом, чтобы они оказались выравненными естественным образом. Пример:
! Уровень вложенности директив OPTIONS - 100 !dec$ options /align = packed ! Начало группы А объявления !dec$ options /align = records = natural ! Начало вложенной группы В объявления !dec$ end options ! Конец группы В объявления !dec$ end options ! Конец группы А
Замечание. Директива $OPTIONS в пределах группы В окажет влияние только на структуры; общие блоки в пределах этой группы будут упакованы. Это объясняется тем, что общие блоки сохраняют предшествующие, заданные в группе А установки.
П. 1.10. Директива $PACK Директива $PACK управляет начальными компонентов производных типов. Синтаксис: cDEC$PACK: [1 | 2 | 4]
⎯ 274 ⎯
адресами
памяти
Приложение 1. Директивы DVF
Элементы производных типов данных (структур) и объединений (UNION) выравниваются в памяти ЭВМ двумя способами: по размеру типов элементов или в соответствии с текущими установками выравнивания. Текущие установки выравнивания могут принимать значения 1, 2, 4 или 8 байт. Начальное значение выравнивания памяти может быть выбрано равным 1, 2 или 4 байтам посредством опций компилятора /alignment или /Zp. По умолчанию начальная установка составляет 8 байт. Уменьшая значение установки выравнивания, можно более плотно паковать данные производных типов в памяти компьютера. Директива $PACK позволяет дополнительно управлять упаковкой компонентов производных типов данных внутри программы, изменяя заданную начальную установку выравнивания. Директива может появляться в любом месте программы перед объявлением производного типа данных (структуры) и не может появляться внутри объявлений производных типов. Если директива $PACK не задана, то ко всей программе применяется или установленное по умолчанию значение выравнивания компонентов структур в памяти (8 байт), или значение, заданное опциями компилятора /alignment или /Zp. Задание $PACK:1 означает, что все переменные начинаются на следующем свободном байте (четном или нечетном). Таким образом, между компонентами не образуется свободной памяти, хотя это и приводит к некоторому увеличению времени доступа. Если задана директива $PACK:4 INTEGER(1), LOGICAL(1) и все символьные переменные начинаются на следующем свободном байте (четном или нечетном), INTEGER(2) и LOGICAL(2) начинаются на следующем четном байте, все другие переменные начинаются на 4-байтовой границе. Если директива $PACK: задана без числа, то установка выравнивания возвращается к заданному опциями компилятора /alignment или /Zp значению или к задаваемым по умолчанию 8 байтам. Пример. Выведем расстояние в памяти между двумя компонентами структуры при различных упаковках. Типы компонентов - INTEGER(1) и INTEGER(4). Напомним, что адрес объекта данных возвращается функцией LOC. !dec$pack: 1 type pair1 integer(1) a integer(4) b end type pair1
⎯ 275 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
type (pair1) :: x1 = pair1(3, 3) !dec$pack: 2 type pair2 integer(1) :: a integer(4) :: b end type pair2 type (pair2) :: x2 = pair2(3, 3) !dec$pack: 4 type pair4 integer(1) :: a integer(4) :: b end type pair4 type (pair4) :: x4= pair4(3, 3) !dec$pack: type pair8 integer(1) :: a integer(4) :: b end type pair8 type (pair8) :: x8= pair8(3, 3) print *, loc(x1.b) - loc(x1.a) print *, loc(x2.b) - loc(x2.a) print *, loc(x4.b) - loc(x4.a) print *, loc(x8.b) - loc(x8.a) end
! ! ! !
1 2 4 4
П. 1.11. Директива $PSECT Директива изменяет характеристики общего блока. Имеет синтаксис: cDEC$PSECT /имя общего блока/ a[, a] ... a - одно из следующих ключевых слов: •
ALIGN = val или ALIGN = keyword - задает выравнивание данных общего блока; val - это целочисленная константа, изменяющаяся от 0 до 8 на процессорах Интел и от 0 до 16 на процессорах Альфа; является степенью числа 2; keyword принимает приведенные в табл. П. 1.1 значения. Таблица П. 1.1. Значения константы keyword KEYWORD
Значение
BYTE
0
WORD
1
⎯ 276 ⎯
Приложение 1. Директивы DVF
LONG
2
QUAD
3
OCTA
4
PAGE
9 на Интел и 16 на Альфа
•
GBL (только для VMS) - задает глобальный диапазон видимости;
•
LCL (только для VMS) - задает локальный диапазон видимости; опция противоположна GBL и не может задаваться вместе с ней;
•
[NO]MULTILANGUAGE (только для VMS) - управляет выравниванием размера общего блока для обеспечения совместимости с другими платформами;
•
[NO]SHR (только для VMS) - определяет, могут ли данные общего блока использоваться разными процессами;
•
[NO]WRT (только для VMS) - определяет, можно ли изменять содержимое общего блока во время исполнения программы.
П. 1.12. Директива $ATTRIBUTES Директива $ATTRIBUTES объявляет DEC-атрибуты процедур и переменных. Эти атрибуты являются расширением DVF по отношению к предусмотренным стандартом Фортран 90 атрибутам. Синтаксис: cDEC$ATTRIBUTES attribute-list :: variable-list attribute-list - один или более объявляемых для variable-list атрибутов DEC. При объявлении более одного атрибута последние разделяются запятыми. variable-list - список переменных или имя процедуры или общего блока. При наличии в списке более одного элемента последние разделяются запятыми. Атрибуты DEC упрощают передачу данных и процедур между Фортраном и другим языком программирования, например СИ. Объявление атрибутов должно появляться в разделе объявления данных программной единицы. Атрибуты DEC: ALIAS, C, DLLEXPORT, DLLIMPORT, EXTERN, REFERENCE, STDCALL, VALUE, VARYING. Детальное рассмотрение атрибутов DEC выполнено в гл. 7.
⎯ 277 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
П. 1.13. Директива $ALIAS Директива задает альтернативное внешнее имя для внешней, реализованной на другом языке процедуры. Имеет синтаксис: cDEC$ALIAS внутреннее имя, внешнее имя внутреннее имя - имя процедуры, которое использует Фортран в исполняемом приложении. внешнее имя - имя внешней процедуры; задается либо как имя, написанное прописными буквами, либо как символьная буквальная константа, которая обрамляется двойными или одинарными кавычками. Символьная константа может содержать и прописные и строчные буквы.
П.1.14. Директива IDENT Задает строку, которая используется для идентификации объектного файла. Строка посылается компилятором в идентификационное поле объектного файла для каждой программной единицы. Имеет синтаксис: cDEC$IDENT string string - буквальная символьная константа, содержащая до 31 символа. Замечание. Не следует задавать более одной директивы $IDENT в программной единице, поскольку компилятор воспринимает только первую директиву, а все последующие игнорирует.
П.1.15. Директивы и опции компилятора Некоторые директивы и опции компилятора оказывают одинаковый эффект на компилятор. Список таких директив приведен в табл. П.1.2. Таблица П. 1.2. Эквивалентные директивы и опции компилятора Директива
Эквивалентная опция компилятора
!DEC$DECLARE
/warn:[kyeword] или /4Yd
!DEC$NODECLARE
/warn:none или /4Nd
!DEC$DEFINE symbol
/define:symbol или /Dsymbol
!DEC$INTEGER:option
/integer_size:size или /4Ioption
!DEC$FIXEDFORMLINESIZE:option
/extended_source[:nn] или /4Loption
!DEC$FREEFORM
/free, или /nofixed, или /4Yf
!DEC$NOFREEFORM
/nofree, или /fixed, или /4Nf
⎯ 278 ⎯
Приложение 1. Директивы DVF
!DEC$PACK:option
/alignment:kyeword или /Zpoption
!DEC$REAL:option
/real_size:size или /4Roption
!DEC$STRICT
/std:strict или /4Ys
!DEC$NOSTRICT
/4Ns
Директива в отличие от опции компилятора может оказывать действие на часть программы и при необходимости может быть отключена. Опция компилятора действует во время всего процесса компиляции, если только ее действие не прервано или изменено расположенной в исходном коде директивой. Замечание. Любая директива может начинаться с префикса !MS$.
⎯ 279 ⎯
Приложение 2. Описатели ссылок и размещаемых массивов Фортрана Описатель (ссылки, ссылочного или размещаемого массива) содержит указатель на область памяти и также определяет, как следует осуществлять доступ к этой области после присоединения ссылки к адресату или размещения массива. По своей форме описатель является структурой типа Descriptor (содержащей, в свою очередь, структуру типа DescriptorTriplet) описывающей индексный триплет для каждого измерения массива. Помимо структур приложение содержит код процедур (на Фортране и СИ), позволяющих выполнять некоторые действия с описателями. Так, вычисление адреса размещения первого элемента описателя выполняется функцией DescriptorElementAddress. Прикрепление ссылки осуществляется подпрограммой DescriptorAssign. Приводимые процедуры выполняют те же функции, что и стандартные средства Фортрана. Например, подпрограмма DescriptorAssign выполняет функции оператора прикрепления ссылки =>. Приводимый код поставляется с DVF и находится в подразделе Advanced раздела Samples, расположенного во вкладке Info Viewer.
П. 2.1. Код на Фортране module descript ! Максимальное число измерений массива integer(4), parameter :: DescriptorMaxRank = 7 ! Поля Reserved и Rank являются резервными; в процессе работы ! в некоторых случаях они могут заполняться приложением, ! однако лучше самостоятельно заполнить поле Rank ! для ссылки или массива, а поле Reserved положить равным нулю type DescriptorTriplet integer(4) :: Extent integer(4) :: Mult integer(4) :: LowerBound end type ! Len - длина символьной строки type Descriptor integer(4) :: Base integer(4) :: Len integer(4) :: Offset
! Число элементов в измерении ! Множитель для измерения ! Нижняя граница измерения
! Базовый адрес ! Длина; используется только для строк ! Отступ
⎯ 280 ⎯
Приложение 2. Описатели ссылок и размещаемых массивов Фортрана
integer(4) :: Reserved ! Зарезервировано использования integer(4) :: Rank ! Ранг ссылки type(DescriptorTriplet) Dim(DescriptorMaxRank) end type
для
! Структура используется для передачи данных об измерениях массива ! в подпрограмму DescriptorAssign type DAssign integer(4) :: LowerBound integer(4) :: UpperBound integer(4) :: Stride integer(4) :: RealSize end type ! Значения параметра OrderArg подпрограммы DescriptorAssign logical(4), parameter :: DescriptColumnOrder = .true. logical(4), parameter :: DescriptRowOrder = .false. ! Родовой интерфейс функции DescriptorLoc для базовых типов данных interface DescriptorLoc ! INTEGER(1) для значений компонента Rank в диапазоне [0, 3] integer(4) function DescriptorLoc$I10(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$I10 integer(1), pointer :: p; end function DescriptorLoc$I10 integer(4) function DescriptorLoc$I11(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$I11 integer(1), pointer :: p(:); end function DescriptorLoc$I11 integer(4) function DescriptorLoc$I12(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$I12 integer(1), pointer :: p(:, :); end function DescriptorLoc$I12 integer(4) function DescriptorLoc$I13(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$I13 integer(1), pointer :: p(:, :, :); end function DescriptorLoc$I13 ! INTEGER(4) для значений компонента Rank в диапазоне [0, 3] integer(4) function DescriptorLoc$I40(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$I40 integer(4), pointer :: p; end function DescriptorLoc$I40 integer(4) function DescriptorLoc$I41(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$I41 integer(4), pointer :: p(:); end function DescriptorLoc$I41 integer(4) function DescriptorLoc$I42(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$I42 integer(4), pointer :: p(:, :); end function DescriptorLoc$I42 integer(4) function DescriptorLoc$I43(p)
⎯ 281 ⎯
будущего
О. В. Бартеньев. Visual Fortran: новые возможности
!dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$I43 integer(4), pointer :: p(:, :, :); end function DescriptorLoc$I43 ! REAL(4) для значений компонента Rank в диапазоне [0, 3] integer(4) function DescriptorLoc$R40(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$R40 real(4), pointer :: p; end function DescriptorLoc$R40 integer(4) function DescriptorLoc$R41(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$R41 real(4), pointer :: p(:); end function DescriptorLoc$R41 integer(4) function DescriptorLoc$R42(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$R42 real(4), pointer :: p(:, :); end function DescriptorLoc$R42 integer(4) function DescriptorLoc$R43(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$R43 real(4), pointer :: p(:, :, :); end function DescriptorLoc$R43 ! REAL(8) для значений компонента Rank в диапазоне [0, 3] integer(4) function DescriptorLoc$R80(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$R80 real(8), pointer :: p; end function DescriptorLoc$R80 integer(4) function DescriptorLoc$R81(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$R81 real(8), pointer :: p(:); end function DescriptorLoc$R81 integer(4) function DescriptorLoc$R82(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$R82 real(8), pointer :: p(:, :); end function DescriptorLoc$R82 integer(4) function DescriptorLoc$R83(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$R83 real(8), pointer :: p(:, :, :); end function DescriptorLoc$R83 end interface contains ! Функция DescriptorElementAddress возвращает адрес элемента описателя ! Замечание. Стандартный Фортран может, используя функцию LOC, вычислять ! адрес элемента описателя иным образом. integer(4) function DescriptorElementAddress(dparg, dims) integer(4) dp, dparg integer(4) dims(:) ! Массив размеров type(Descriptor) d pointer(dp, d) integer(4) p ! Результирующий указатель integer(4) r ! Счетчик измерений dp = dparg
⎯ 282 ⎯
Приложение 2. Описатели ссылок и размещаемых массивов Фортрана
if(lbound(dims, 1) /= 1 .or. ubound(dims, 1) > DescriptorMaxRank) then DescriptorElementAddress = -1 return end if p = d%Base + d%Offset do r = 1, ubound(dims, 1) p = p + dims(r) * d%dim(r)%Mult end do DescriptorElementAddress = p end function DescriptorElementAddress ! Подпрограмма DescriptorAssign выполняет операцию прикрепления ссылки ! к адресату. Она работает так же, как и стандартный оператор Фортрана ! прикрепления ссылки (=>) ! Однако она имеет больше возможностей. Так, параметром orderarg учитывается, ! каким образом элементы массива размещены в памяти: по столбцам или по строкам ! Параметры: ! dparg – адрес ссылки Фортрана; возвращается функцией LOC или DescriptorLoc ! base -- базовый адрес, выделяемый ссылке памяти ! size -- значение параметра разновидности типа данных, например 4 для INTEGER(4) ! dims -- массив с данными о каждом измерении ссылки или размещаемого массива ! LowerBound – нижняя граница измерения ! UpperBound—нижняя граница измерения ! Stride -- шаг для измерения ! RealSize -- протяженность измерения ! orderarg (необязательный) – способ размещения в памяти (по столбцам ! или по строкам) ! Пример. Пусть выполнены объявления: ! integer(4), target :: arr(10, 10) ! integer(4), pointer :: dp(:, :) ! Тогда вызов DescriptorAssign выполняется так: ! call DescriptorAssign(DescriptorLoc(dp), loc(arr(1, 1)), 4, & ! (/ DAssign(l1, u1, s1, 10), DAssign(l2, u2, s2, 10) /)) ! Результат такой же, как и после прикрепления ссылки стандартными средствами Фортрана: ! dp => arr(l1:u1:s1, l2:u2:s2) subroutine DescriptorAssign(dparg, base, size, dims, orderarg) integer(4) :: dparg, dp ! dp - целочисленный указатель logical(4), optional :: orderarg logical(4) columnorder
⎯ 283 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
type(DAssign) dims(:) type(Descriptor) d ! Адресная переменная pointer(dp, d) ! Связываем указатель с переменной integer(4) :: r, mult, size, base integer(4) :: todo, dir dp = dparg if(present(orderarg)) then; columnorder = orderarg else; columnorder = .true. end if if(columnorder) then; dir = 1; r = 1 else; dir = -1; r = ubound(dims, 1) end if mult = size d%Base = base d%Offset = 0 d%Len = size d%Rank = ubound(dims, 1) d%Reserved = 0 do todo = 1, ubound(dims, 1) d%Base = d%Base + (dims(r)%LowerBound - 1) * mult d%dim(r)%extent = (dims(r)%UpperBound - dims(r)%LowerBound + & dims(r)%Stride) / dims(r)%Stride if(d%dim(r)%extent < 0) d%dim(r)%extent = 0 d%dim(r)%mult = dims(r)%Stride * mult d%dim(r)%lowerbound = 1 d%Offset = d%Offset - d%dim(r)%mult mult = mult * dims(r)%RealSize r = r + dir end do end subroutine DescriptorAssign ! Подпрограмма DescriptorPrint выводит значения компонентов описателя ! Используется как информационная на этапах отладки приложений subroutine DescriptorPrint(dparg, rank) integer(4) :: dparg, dp type(Descriptor) :: d pointer(dp, d) integer(4) :: r, rank dp = dparg print *, ' Descriptor at Address: ', dparg print *, ' Base Address: ', d%Base print *, ' Length: ', d%Len print *, ' Offset: ', d%Offset
⎯ 284 ⎯
адресной
Приложение 2. Описатели ссылок и размещаемых массивов Фортрана
print *, ' Rank: do r = 1, rank print '(" Dimension ", i1," Extent: print *,' Mult: print *,' LowerBound: end do end subroutine DescriptorPrint
', d%rank ", i12)', r, d%dim(r)%extent ', d%dim(r)%Mult ', d%dim(r)%LowerBound
end module descript
Функция DescriptorLoc возвращает адрес описателя. Она используется в качестве фактического параметра подпрограммы DescriptorAssign, в которую передается не сам описатель, а его адрес. integer(4) function DescriptorLoc(in) integer(4) :: in !dec$attributes value :: in DescriptorLoc = in end function DescriptorLoc
Прежде чем использовать функцию DescriptorLoc, надо проверить, есть ли в родовом интерфейсе DescriptorLoc, приведенном в модуле descript, необходимый специфический интерфейс (родовой интерфейс DescriptorLoc написан для ссылок ранга от 0 до 3 типа INTEGER(1), INTEGER(4), REAL(4) и REAL(8)). Если его там нет, то, заменив надлежащим образом typefoo и type(foo) в нижеприводимом шаблоне, пополните родовой интерфейс DescriptorLoc недостающим специфическим. interface DescriptorLoc ! Шаблон пополнения интерфейса DescriptorLoc integer(4) function DescriptorLoc$typefoo(p) !dec$attributes alias:'_DESCRIPTORLOC@4' :: DescriptorLoc$typefoo type(foo), pointer :: p ! Не забудьте указать ранг ссылки end function end interface
Пример. Сравнить результаты, получаемые приведенными в модуле descript процедурами.
встроенными
program test use descript integer(4), target :: arr(10, 10) ! Тестовые адресат и ссылочный массив integer(4), pointer :: dp(:, :) print *, loc(dp), DescriptorLoc(dp) ! Прикрепим ссылку к адресату, применив встроенный оператор Фортрана dp => arr
⎯ 285 ⎯
и
О. В. Бартеньев. Visual Fortran: новые возможности
call DescriptorPrint(DescriptorLoc(dp), 2) ! Вывод дескриптора nullify(dp) ! Открепляем ссылку от адресата ! Прикрепим ссылку к адресату, использовав подпрограмму DescriptorAssign call DescriptorAssign(DescriptorLoc(dp), loc(arr(1, 1)), 4, & (/ DAssign(1, 10, 1, 10), DAssign(1, 10, 1, 10) /)) call DescriptorPrint(DescriptorLoc(dp), 2) ! Вновь печатаем дескриптор end program test ! Результаты те же
П. 2.2. Код на СИ При работе с дескрипторами Фортрана в СИ можно использовать приводимый ниже код. Пояснения и комментарий к коду размещены в предшествующем разделе. #include <stdarg.h> #include <stdio.h> #define DescriptorMaxRank 7 struct DescriptorTriplet { long Extent; long Mult; long LowerBound; }; struct Descriptor { long Base; long Len; long Offset; long Reserved; long Rank; struct DescriptorTriplet Dim[DescriptorMaxRank]; }; enum {DescriptRowOrder, DescriptColumnOrder}; void *DescriptorElementAddress(struct Descriptor *dp, int rank, ...); void DescriptorAssign(struct Descriptor *dp, void *base, long size, int columnorder, int rank, ...); void DescriptorPrint(struct Descriptor *dp, long rank); // Задайте #define DESCRIPTCODE в одном из файлов, // куда будет включен файл descript.h #ifdef DESCRIPTCODE void *DescriptorElementAddress(struct Descriptor *dp, int rank, ...) { long p; va_list vl; int i;
⎯ 286 ⎯
Приложение 2. Описатели ссылок и размещаемых массивов Фортрана
va_start(vl, rank); p = dp->Base + dp->Offset; for(i = 0; i < rank; i++) p += (dp->Dim[i].Mult * va_arg(vl, long)); va_end(vl); return ((void *) p); } // Пример использования DescriptorAssign в C: // int arr[10][10]; // struct Descriptor dp; // DescriptorAssign(&dp, &arr[0][0], sizeof(arr[0][0]), // DescriptRowOrder, 2, l1, u1, s1, 10, l2, u2, s2, 10); // Приведенный код СИ эквивалентен следующим операторам Фортрана: // integer(4), target :: arr(10,10) // integer(4), pointer :: dp(:, :) // dp => arr(l1:u1:s1,l2:u2:s2) void DescriptorAssign(struct Descriptor *dp, void *base, long size, int columnorder, int rank, ...) { va_list vl; long lbound, ubound, stride, realsize; long mult, r, dir, todo; mult = size; dp->Base = (long) base; dp->Offset = 0; dp->Len = size; dp->Rank = rank; dp->Reserved = 0; if(columnorder) {dir = 1; r = 0;} else {dir = -1; r = rank-1;} va_start(vl, rank); for(todo = 0; todo < rank; todo++) { lbound = va_arg(vl, long); ubound = va_arg(vl, long); stride = va_arg(vl, long); realsize = va_arg(vl, long); dp->Base += ((lbound-1) * mult); dp->Dim[r].Extent = (ubound - lbound + stride) / stride; if(dp->Dim[r].Extent < 0) dp->Dim[r].Extent = 0; dp->Dim[r].Mult = stride * mult; dp->Dim[r].LowerBound = 1; dp->Offset = dp->Offset - dp->Dim[r].Mult; mult *= realsize; r += dir; }
⎯ 287 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
va_end(vl); } void DescriptorPrint(struct Descriptor *dp, long rank) { int r; printf("Descriptor at Address: 0x%08x\n", (void *) dp); printf("Base Address: 0x%08x\n", dp->Base); printf("Length: %d\n", dp->Len); printf("Offset: %d\n", dp->Offset); printf("Rank: %d\n", dp->Rank); for(r = 0; r < rank; r++) { printf(" Dimension %d Extent: %d\n", r, dp->Dim[r].Extent); printf(" Mult: %d\n", dp->Dim[r].Mult); printf(" LowerBound: %d\n", dp->Dim[r].LowerBound); } } #endif
// DESCRIPTCODE
⎯ 288 ⎯
Приложение 3. Вывод русских сообщений в DOS-окно П. 3.1. Преобразования “символ - код символа” и “код символа - код” Пусть в файле work1.txt дана строка, содержащая символы как русского, так и латинского алфавитов. Состав файла work1.txt. Пример символьной строки. An example of a symbol string. Заметим, что каждый символ данной, да и любой, строки имеет код целое положительное число от 1 до 255. Существует также и null-символ, код которого равен нулю. Задача 1. Найти сумму кодов всех символов строки файла work1.txt за вычетом пробелов. Вывести также код каждого символа строки. Используем при решении встроенную функцию IACHAR(c), которая возвращает значение стандартного целого типа, равное коду символа c. Тип параметра c - CHARACTER(1). Длину строки без концевых пробелов найдем функцией LEN_TRIM; i-й символ строки string - это string(i:i). program symbol_codes integer(4) :: i, ico, sco ! sco - искомая сумма кодов символов character(120) :: string character(1) :: ch open(10, file = 'work1.txt') ! Подсоединяем файл к устройству В/В read(10, '(a)') string ! Ввод строки (одной записи) файла do i = 1, len_trim(string) ! LEN_TRIM(string) - возвращает длину ch = string(i:i); ico = iachar(ch) ! строки без концевых пробелов if(ch /= ' ') sco = sco + ico ! Сумма кодов, не включая коды пробелов print *, 'Code of symbol ', ch, ' = ', ico read * ! Ожидаем нажатия Enter end do print *, 'Сумма кодов символов строки без кодов пробелов sco = ', sco end program symbol_codes
Просматривая выводимые данные, мы обнаружим, что пробел имеет код 32, а точка - 46. Буквы английского алфавита имеют код в диапазоне от IACHAR('A') = 65 до IACHAR('z') = 122. Буквы русского алфавита в случае DOS-кодовой страницы 866 имеют код в диапазоне от IACHAR('A') = 128 до ⎯ 289 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
IACHAR('я') = 239. То есть прописные буквы алфавита имеют меньший код, чем соответствующие им строчные буквы. Из сопоставления минимального (128) и максимального кодов (239) букв следует, что в этом диапазоне кодов находятся не только коды русских букв, но и коды иных символов. Коды русских букв в DOS-кодовой странице 866 изменяются в диапазонах: •
128 - 159 - коды прописных букв от А до Я;
•
160 - 175 - коды строчных букв от а до п;
•
224 - 239 - коды строчных букв от р до я. Можно, применив встроенную функцию CHAR(i), выполнить и обратное преобразование: “код символа - символ”. Задача 2. Вывести все буквы русского алфавита. program russian_letters integer(4) :: i character(1) :: ch do i = 128, 128 + 31 ! Вывод прописных букв print '(a, i3, a, a)', 'Capital latter with code ', i, ' - ', char(i) read * ! Ожидаем нажатия Enter end do do i = 160, 160 + 15 ! Вывод первых 16 строчных букв print '(a, i3, a, a)', 'Small letter with code ', i, ' - ', char(i) read * ! Ожидаем нажатия Enter end do do i = 224, 239 ! Вывод следующих 16 строчных букв print '(a, i3, a, a)', 'Small letter with code ', i, ' - ', char(i) read * ! Ожидаем нажатия Enter end do end program russian_letters
П. 3.2. Преобразование DOS-букв русского алфавита в Windows-буквы русского алфавита и обратно Сохраним после ввода с клавиатуры строку текста в файле, использовав, например, такую программу: program string_to_file character(120) string open(10, file = 'work1.txt') read '(a)', string
! Подсоединяем файл к устройству В/В ! Введем: Пример DOS-строки текста.
⎯ 290 ⎯
Приложение 3. Вывод русских сообщений в DOS-окно
write(10, '(a)') string end program string_to_file
! Вывод DOS-строки в файл work1.txt
Откроем файл work1.txt, например, в среде MS Developer Visual Studio, в которой функционирует Digital Visual Fortran или в стандартной Windowsпрограмме NotePad (Блокнот). Тогда введенная в DOS-режиме строка файла work1.txt предстанет в виде следующего набора символов: ЏаЁ¬Ґа DOS-бва®ЄЁ ⥪бв . Задача 3. Преобразовать строку из DOS-представления в Windowsпредставление. Задача 4. Преобразовать строку из Windows-представления в DOSпредставление. Решим прежде промежуточную задачу. Задача 5. Вывести в файл work2.txt Windows-коды букв русского алфавита, принимая во внимание, что Windows-код русской буквы больше ее DOS-кода. Воспользуемся для этого программой: program windows_codes integer(2) :: i open(11, file = 'work2.txt') do i = 160, 255 write(11, '(i4, 2x, a1)') i, char(i) end do end program windows_codes
! Подсоединяем файл к устройству В/В ! Вывод Windows-кодов и символов ! в файл work2.txt
Проанализировав файл work2.txt, мы обнаружим, что коды русских букв в Windows-кодовой странице 1251 изменяются в диапазонах: •
192 - 223 - коды прописных букв от А до Я;
•
224 - 255 - коды строчных букв от а до я. Таким образом, чтобы преобразовать DOS-букву ru_letter русского алфавита в Windows-букву ru_letter русского алфавита потребуется выполнить для букв с DOS-кодами от 128 до 175 (буквы А - Я, а - п) оператор ru_letter = CHAR(ru_letter + (192 - 128)) А для букв с DOS-кодами от 224 до 239 (буквы р - я) следует применить оператор ru_letter = CHAR(ru_letter + (255 - 239)) Понятно, что обратное преобразование потребует уменьшение Windows-кода буквы до соответствующего ее DOS-кода. ⎯ 291 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Оформим преобразования “DOS - Windows” и “Windows - DOS” в виде внешней символьной функции string = ru_doswin(string, dos_win) интерфейс которой опишем в модуле strings_interfaces. Если параметр dos_win функции равен .TRUE., то выполняется преобразование “DOS Windows”, в противном случае (dos_win = .FALSE.) выполняется преобразование “Windows - DOS”. Создадим также модуль strings_consts, в котором объявим константу ncresults, определяющую длину результирующих переменных создаваемых символьных функций. module strings_consts ! Длина строк - результирующих переменных символьных функций integer(4), parameter :: ncresults = 250 end module strings_consts module strings_interfaces обработки строк interface function ru_doswin(string, dos_win) use strings_consts character(ncresults) :: ru_doswin character(*), intent(in) :: string logical(4), intent(in) :: dos_win end function ru_doswin end interface end module strings_interfaces
!
Модуль
с
интерфейсами
процедур
! Параметры string и dos_win имеют вид ! связи IN и, следовательно, не должны ! изменяться в функции ru_doswin
program tasks_3_4 use strings_interfaces ! Ссылка на модуль с интерфейсами character(120) :: string ! Перед выводом текста на консоль выполняем преобразование “Windows - DOS” ! Используем встроенную функцию TRIM для отсечения концевых пробелов print *, trim(ru_doswin('Введите строку на русском языке', .false.)) read(*, '(a)') string ! Ввод DOS-строки с клавиатуры open(11, file = 'work2.txt') ! Вывод Windows-строки в текстовый файл write(11, '(a)') ru_doswin(string, .true.) end program tasks_3_4 ! Файл можно открыть, например, в блокноте function ru_doswin(string, dos_win) use strings_consts character(ncresults) :: ru_doswin character(*), intent(in) :: string
! Получаем доступ к константе ncresults ! Длина строки string не должна превышать ! ncresults символов
⎯ 292 ⎯
Приложение 3. Вывод русских сообщений в DOS-окно
logical(4), intent(in) :: dos_win integer(2) :: i, dos_win_code, dif ! dif - величина, на которую при преобразовании ru_doswin = string ! изменяется код буквы do i = 1, len_trim(ru_doswin) dos_win_code = iachar(ru_doswin(i:i)) ! DOS или Windows код символа dif = 0 ! dif больше нуля, если символ - русская буква if(dos_win) then ! Если преобразование “DOS - Windows” select case(dos_win_code) ! Найдем величину dif case(128 : 175) ! DOS-русские буквы от А до Я и от а до п dif = 64 case(224 : 239) ! DOS-русские буквы от р до я dif = 16 end select else ! Преобразование “Windows - DOS” select case(dos_win_code) case(192 : 239) ! Windows-русские буквы от А до Я и от а до п dif = -64 case(240 : 255) ! Windows-русские буквы от р до я dif = -16 end select end if ! Выполняем преобразование символа, если он является буквой русского алфавита if(dif /= 0) ru_doswin(i:i) = char(dos_win_code + dif) end do end function ru_doswin
Главная программа tasks_3_4 содержит 2 примера использования разработанной функции ru_doswin: 1) вывод после преобразования “Windows - DOS” русского текста на DOSконсоль; 2) вывод после преобразования “DOS - Windows” введенного с клавиатуры русского текста в текстовый файл work2.txt.
⎯ 293 ⎯
Приложение 4. Нововведения стандарта Фортран 95 Стандарт Фортран 95 был опубликован ISO в октябре 1996 года и заместил с того времени Фортран 90. Помимо описания языка стандарт включает перечни удаленных и устаревших свойств Фортрана, которые приведены в двух последних разделах приложения.
П. 4.1. Оператор и конструкция FORALL Оператор и конструкция FORALL, наряду с сечениями массивов и оператором и конструкцией WHERE, используются для выборочного присваивания массивов. FORALL может заменить любое присваивание сечений или WHERE. Но возможности FORALL шире: оператором и особенно конструкцией FORALL можно выполнять присваивания несогласованных массивов, т. е. массивов разной формы. Подобно WHERE и сечениям, FORALL заменяет циклы с присваиванием массивов, например вместо цикла do i = 1, 100 d(i, i) = 2 * g(i) end do
лучше использовать forall(i = 1:100) d(i, i) = 2 * g(i)
Синтаксис оператора: FORALL(спецификация триплета & [, спецификация триплета] ... & [, выражение-маска]) оператор присваивания Синтаксис конструкции: FORALL(спецификация триплета & [, спецификация триплета] ... & [, выражение-маска]) операторы конструкции FORALL END FORALL спецификация триплета имеет вид: индекс = триплет ⎯ 294 ⎯
Приложение 4. Нововведения стандарта Фортран 95
где триплет - это тройка: [нижняя граница]:[верхняя граница]:[шаг]. Каждый из параметров триплета является целочисленным выражением. Шаг изменения индексов может быть и положительным и отрицательным, но не может быть равным нулю. Все параметры триплета являются необязательными. Шаг, если он отсутствует, принимается равным единице. В выражениях, задающих нижнюю, верхнюю границы триплета и его шаг, не должно быть ссылок на индекс. Оценка какого-либо выражения триплета не должна влиять на результат его иного выражения. Индекс - это скаляр целого типа. Область видимости индекса - оператор или конструкция FORALL. После завершения FORALL значение индекса не определено. выражение-маска - логическое выражение-массив; при отсутствии принимается равным .TRUE.. Содержит, как правило, имена индексов, например: forall(i = 1:n, i = 1:n, a(i, j) /= 0.0) b(i, j) = 1.0 / a(i, j)
Переменная, которой в операторе присваивания присваивается значение, должна быть элементом массива или его сечением и содержать имена всех индексов, включенных в спецификации триплетов. Правая часть оператора присваивания не может быть символьного типа. операторы конструкции FORALL - это: • оператор присваивания, обладающий рассмотренными выше свойствами; • оператор или конструкция WHERE; • оператор или конструкция FORALL. Присутствующие в FORALL операторы выполняются для тех значений индексов, задаваемых индексными триплетами, при которых выражениемаска вычисляется со значением .TRUE.. В DO-цикле операторы выполняются немедленно при каждой итерации. FORALL работает иначе: первоначально вычисляется правая часть выражения для всех итераций и лишь затем выполняется присваивание. То же справедливо и для выражений с сечениями, например: integer(4), parameter :: n = 5 integer(4), dimension(n) :: a = 1 integer(4) :: k do k = 2, n a(k) = a(k - 1) + 2 end do print *, a
! Объявляем и инициализируем массив a ! Выполним присваивание в цикле !
1
⎯ 295 ⎯
3
5
7
9
О. В. Бартеньев. Visual Fortran: новые возможности
a=1 forall(k = 2:n) a(k) = a(k - 1) + 2 print *, a a=1 a(2:n) = a(1:n-1) + 2 print *, a
! Присваивание в FORALL ! 1 3 3 3 3 ! Используем выражение с сечениями !
1
3
3
3
3
Ни один из элементов массива не может быть изменен в FORALL более одного раза. Любая процедура, вызываемая в выражении-маске FORALL, должна быть чистой. Любую конструкцию или оператор WHERE можно заменить FORALL, обратное утверждение несправедливо. Примером служит оператор forall(i = 1:n, j = 1:n) h(i, j) = 1.0 / real(i + j)
в котором элементами выражения являются изменяемые индексы, что для WHERE недопустимо. Пример 1: type monarch integer(4), pointer :: p end type monarch type(monarch), dimension(8) :: pattern integer(4), dimension(8), target :: object forall(j=1:8) pattern(j)%p => object(1+ieor(j - 1, 2))
Этот оператор FORALL прикрепляет элементы с номерами 1-8 ссылки pattern соответственно к элементам 3, 4, 1, 2, 7, 8, 5 и 6 адресата object. Встроенная функция IEOR может быть использована, так как она является чистой. Пример 2. Использование конструкции FORALL. forall(i = 3:n + 1, j = 3:n + 1) c(i, j) = c(i, j + 2) + c(i, j - 2) + c(i + 2, j) + c(i - 2, j) d(i, j) = c(i, j) ! Массиву d присваиваются вычисленные в end forall ! предыдущем операторе элементы массива c
Пример 3. Операторы FORALL, которые не заменяются сечениями или WHERE. real(4), dimension(100, 100) :: a = 1.0, b = 2.0 real(4), dimension(300) :: c = 3.0 integer(4) :: i, j forall(i = 1:100, j = 1:100) a(i, j) = (i + j) * b(i, j)
⎯ 296 ⎯
Приложение 4. Нововведения стандарта Фортран 95
forall(i = 1:100) a(i, i) = c(i)
Заметьте, что в последнем случае FORALL обеспечивает доступ к диагонали матрицы, чего нельзя сделать при помощи сечений массивов. Пример 4. Сформировать вектор a из сумм вида
m
∑ x i ,( m = 1, 2, ..., n ). i =1
program vec_a integer(4), parameter :: n = 10 integer(4) i real(4), dimension(n) :: x = (/ (i, i = 1, n) /), a ! Инициализация массива x ! Массив x после инициализации: ! 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 forall(i = 1:n) a(i) = sum(x(1:i)) print '(1x, 10f5.1)', a(1:n) ! 1.0 3.0 6.0 10.0 15.0 21.0 28.0 36.0 45.0 55.0 end program vec_a
Пример 5. Решить методом Гаусса систему линейных уравнений: a11 x1 + a12 x 2 + a13 x 3 + a14 x 4 + a15 = 0 a 21 x1 + a 22 x 2 + a 23 x 3 + a 24 x 4 + a 25 = 0 a 31 x1 + a32 x 2 + a 33 x 3 + a34 x 4 + a35 = 0 a 41 x1 + a 42 x 2 + a 43 x 3 + a 44 x 4 + a 45 = 0 Идея метода Гаусса в том, чтобы исходную систему привести к треугольному виду: * * * * − x1 + a12 x 2 + a13 x 3 + a14 x 4 + a15 = 0 * * * − x 2 + a 23 x 3 + a 24 x 4 + a 25 =0 * * − x3 + a34 x 4 + a 35 =0 * − x 4 + a 45 =0
Процесс приведения называется прямым ходом. После приведения выполняется обратный ход, в результате которого вычисляются искомые значения неизвестных: сначала x4, затем x3, затем x2 и в последнюю очередь x1. Алгоритм выполнения обратного хода тривиален, поэтому будет опущен. Реализацию прямого хода рассмотрим на примере системы уравнений
⎯ 297 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
2 x1 + 3x 2 + 11x 3 + 5x 4 − 2 = 0 x1 + x 2 + 5 x 3 + 2 x 4 − 1 = 0 2 x1 + x 2 + 3x 3 + 2 x 4 + 3 = 0 x1 + x 2 + 3x 3 + 4 x 4 + 3 = 0 Выразим x1 через другие неизвестные, разделив первое уравнение на –a11 = –2: x1 = –1,5x2 – 5,5x3 – 2,5x4 + 1. (*) Теперь, заменив x1 в трех оставшихся уравнениях системы на правую часть уравнения (*), мы получим систему: − x1 − 1,5x 2 − 5,5x 3 − 2,5x 4 + 1 = 0 − 0,5x 2 − 0,5x 3 − 0,5x 4 =0 − 2 x 2 − 8 x 3 − 3x 4 + 5 = 0 − 0,5x 2 − 2,5x 3 + 1,5 x 4 + 4 = 0 Понятно, что на следующем шаге следует взять второе уравнение и выразить x2 через x3 и x4, а затем заменить в третьем и четвертом уравнениях x2 на полученное выражение. Повторив далее эти операции для x3, мы получим систему − x1 − 1,5x 2 − 5,5x3 − 2,5x 4 + 1 = 0
− x2 −
x3 − − x3 −
x4
=0
1 5 x4 + = 0 6 6 − x4 − 1 = 0
Выполнив обратный ход, найдем корни x4 = -1, x3 = 1, x2 = 0, x1 = -2. В приводимой ниже программе, реализующей метод Гаусса, искомые корни заносятся в массив x. Коэффициенты и свободные члены системы уравнений хранятся в массиве a. Функция RESHAPE преобразовывает одномерный массив, задаваемый посредством конструктора, в двумерный массив формы (n, n + 1). Параметр order = (/ 2, 1 /) функции обеспечивает запись каждых n + 1 элементов конструктора в соответствующую строку массива-результата. При его отсутствии формирование массива-результата будет выполнено по столбцам. Массив b, в котором запоминается исходная матрица, введен для выполнения проверки вычислений. Суть проверки в том, что произведение матрицы коэффициентов уравнения и найденного
⎯ 298 ⎯
Приложение 4. Нововведения стандарта Фортран 95
вектора x (выполняется встроенной функцией MATMUL) должно быть равно вектору, состоящему из свободных членов уравнения. В отлаженной программе матрица b, конечно же, избыточна. Компактность программ обеспечивается не только применением встроенных функций, но и заменой DO-циклов с присваиваниями массива сечениями массива. program Gauss integer(4), parameter :: n = 4 real(8) :: a(n, n + 1), b(n, n+1) ! Используем двойную точность real(8) :: x(n) = 0.0 a = reshape((/ 2, 3, 11, 5, -2, & 1, 1, 5, 2, -1, & 2, 1, 3, 2, 3, & 1, 1, 3, 4, 3 /), & shape = (/ n, n + 1 /), order = (/ 2, 1 /)) b=a do i = 1, n - 1 ! Прямой ход a(i, i:) = -a(i, i:) / a(i, i) ! Используем сечение a(i, i:). Записи a(i, i:) forall(j = i+1:n) ! и a(i, i:n+1) эквивалентны a(j, i+1:) = a(j, i+1:) + a(j, i) * a(i, i+1:) end forall if(i > 1) a(i, :i-1) = 0.0 ! Этот оператор может отсутствовать end do a(n, n:) = -a(n, n:) / a(n, n) ! Завершаем прямой ход a(n, :n-1) = 0.0 ! Этот оператор может отсутствовать x(n) = a(n, n+1) ! Выполняем обратный ход do i = n - 1, 1, -1 a(i, i+1:n) = a(i, i+1:n) * x(i+1:n) x(i) = sum(a(i, i+1:)) end do print '(4f9.3)', x print '(4f9.6)', matmul(b(:, :n), x) - pack(-b(:, n+1:n+1), .true.) end program Gauss
Функция PACK возвращает одномерный массив (вектор) из свободных членов исходного уравнения. В выполняющем прямой ход DO-цикле присутствует конструкция FORALL, которая заменяет цикл do j = i + 1, n a(j, i+1:) = a(j, i+1:) + a(j, i) * a(i, i+1:) end do
Конструкция FORALL будет работать быстрее, чем DO-цикл. Это, однако, обнаружится в том случае, если вы работаете на ⎯ 299 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
многопроцессорной машине и используете, например, компилятор Digital Visual Fortran (DVF) фирмы Digital с дополнительным программным обеспечением (Digital Parallel Software Environment), позволяющим распараллеливать вычисления. То же справедливо и для конструкций (операторов) WHERE и для выражений, в которых используются сечения массивов.
П. 4.2. Оператор ELSEWHERE Теперь конструкция WHERE может включать оператор ELSEWHERE, например: where(a >= 0.0) b = 3.0 + sin(a) - sum(a) elsewhere b = 0.0 end where
Приведенный код эквивалентен следующему: t1 = sum(a) do i = 1, n if(a(i) >= 0.0) b(i) = 3.0 + sin(a) - t1 else b(i) = 0.0 end if end do
П. 4.3. Чистые процедуры Чистыми называются процедуры, не имеющие побочных эффектов. Пример побочного эффекта демонстрирует программа program side_effect real(4) :: dist, d, p = 3.0, q = 4.0, r = 5.0 d = max(dist(p, q), dist(q, r)) print *, d end program side_effect function dist(p, q) real(4) :: dist, p, q dist = sqrt(p * p + q * q) q = dist end function dist
!
7.071068
! Изменение q - побочный эффект
⎯ 300 ⎯
Приложение 4. Нововведения стандарта Фортран 95
Суть его в том, что функция dist переопределяет значение параметра q. А это означает, что второй вызов функции dist при вычислении d выполняется при q, равном 5.0, возможно вместо ожидаемого первоначального значения q = 4.0. Такие эффекты запрещены стандартом и должны отслеживаться и устраняться программистом. Сообщение о том, что процедура является чистой, обеспечивается ключевым словом PURE, применяемым в заголовке процедуры: [type-spec] PURE SUBROUTINE | FUNCTION name [RESULT (resultname)] & или PURE [type-spec] SUBROUTINE | FUNCTION name [RESULT (resultname)] & type-spec - тип результирующей переменной функции. name - имя процедуры. resultname - имя результирующей переменной функции. Чистая процедура характеризуется тем, что: • •
функция возвращает значение и не меняет ни одного из своих параметров; подпрограмма изменяет только те параметры, которые имеют вид связи INTENT(OUT) и INTENT(INOUT). По умолчанию чистыми являются:
•
все встроенные функции и встроенная подпрограмма MVBITS;
•
процедуры библиотеки высокоскоростного Фортрана, применяемого для паралльных вычислений под Юниксом.
В чистых процедурах все формальные параметры, кроме формальных процедур и ссылок, должны иметь вид связи: •
для функций - только INTENT(IN);
•
для подпрограмм - любой INTENT(IN, или OUT, или INOUT).
Никакие локальные переменные чистой процедуры, в том числе и относящиеся к внутренним процедурам, не должны: •
обладать атрибутом SAVE;
•
быть инициализированными в операторах объявления или DATA. В чистых процедурах имеются ограничения на использование:
•
глобальных переменных; ⎯ 301 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
•
формальных параметров с необъявленным видом связи;
•
объектов, ассоциируемых по памяти с какими-либо глобальными переменными.
Ограничения использоваться:
таковы:
видом
связи
перечисленные
INTENT(IN)
объекты
не
или
с
должны
•
в случаях, когда возможно изменение их значения. Это может произойти, если переменная является: • левой частью оператора присваивания или прикрепления ссылки (если объект является ссылкой); • фактическим параметром, ассоциированным с формальным параметром с видом связи INTENT(OUT или INOUT) или обладающим атрибутом POINTER; • индексной переменной операторов DO, FORALL или встроенного DO-цикла; • переменной оператора ASSIGN; • элементом списка ввода оператора READ; • именем внутреннего файла оператора WRITE; • объектом операторов ALLOCATE, DEALLOCATE или NULLIFY; • спецификаторами IOSTAT или SIZE операторов В/В или STAT операторов ALLOCATE и DEALLOCATE;
•
в создании ссылки, например, в качестве адресата или в качестве элемента правой части оператора присваивания переменной производного типа, если он имеет ссылочный компонент на любом из его уровней. Чистые процедуры не должны содержать:
•
операторов В/В во внешние файлы или устройства;
•
операторы PAUSE и STOP.
Чистые процедуры предназначены для вызова в тех случаях, когда вызов иных, не владеющих ключевым словом PURE, процедур недопустим: •
в операторе FORALL или его выражении-маске;
•
из другой чистой процедуры. Также только чистую процедуру можно использовать в качестве параметра другой чистой процедуры. ⎯ 302 ⎯
Приложение 4. Нововведения стандарта Фортран 95
Если чистая процедура используется в приведенных ситуациях, то ее интерфейс должен быть задан явно и она должна быть объявлена в нем с ключевым словом PURE. Напомним, что все встроенные процедуры являются чистыми и по умолчанию обладают явным интерфейсом. Пример: pure function decr(k, m) real(4) :: decr integer(4), intent(in) :: k, m decr = real(m) / real(k) end function decr
! Формальные параметры чистой функции ! должны иметь вид связи INTENT(IN)
program pudem real(4), dimension(5, 5) :: array = 5.0 interface pure function decr(k, m) ! Поскольку функция используется FORALL, real(4) :: decr ! то необходимо задать ее интерфейс integer(4), intent(in) :: k, m end function decr end interface forall(i = 1:5, j = 1:5) array(i, j) = decr(i, j) print '(10f5.1)', array(1, :) ! 1.0 2.0 3.0 4.0 5.0 end program pudem
в
П. 4.4. Элементные процедуры Элементные пользовательские процедуры подобно встроенным элементным процедурам могут иметь в качестве фактических параметров либо скаляры, либо массивы. В последнем случае массивы должны быть согласованы, т. е. иметь одинаковую форму; результатом процедуры является поэлементная обработка массивов - фактических параметров. Приведем пример выполнения встроенной элементной функции MOD, возвращающей остаток от деления первого параметра на второй: integer(4), dimension(5) :: a = (/ 1, 2, 3, 4, 5 /), b = (/ 1, 2, -2, 4, 3 /), c integer(4) :: d c = mod(a, b) ! Параметры функции - массивы print *, c ! 0 0 1 0 2 d = mod(b(4), a(3)) ! Параметры функции - скаляры print *, d ! 1
Программная единица, вызывающая элементную функцию, должна содержать ее интерфейс, в котором явно указано слово ELEMENTAL. ⎯ 303 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
Цель введения элементных функций - упростить распараллеливание вычислений на многопроцессорных машинах: компилятор, имеющий сведения о том, что функция элементная, выполняет распараллеливание по заложенным в него правилам. Элементные функции - это чистые функции, имеющие только скалярные формальные параметры, не являющиеся ссылками или процедурами. Вид связи параметров - INTENT(IN). Результирующая переменная элементной функции также является скаляром и не может быть ссылкой. Элементная функция снабжается ключевым словом ELEMENTAL, которое автоматически подразумевает ключевое слово PURE. Элементные функции не могут быть оснащены ключевым словом RECURSIVE. Если фактическими параметрами элементной функции являются массивы, то они должны быть согласованы; результатом такой функции является массив, согласованный с массивами-параметрами. Например: elemental integer(4) function find_c(a, b) ! Не забываем задать вид связи INTENT(IN) integer(4), intent(in) :: a, b if(a > b) then find_c = a else if(b < 0) then find_c = abs(b) else find_c = 0 end if end function find_c
program etest interface ! Интерфейс обязателен elemental integer(4) function find_c(a, b) ! Обязательное задание вида связи integer(4), intent(in) :: a, b end function find_c ! INTENT(IN) end interface integer(4), dimension(5) :: a = (/ -1, 2, -3, 4, 5 /), b = (/ 1, 2, -2, 4, 3 /), c integer(4) :: d = 5 c = find_c(a, b) ! Параметры функции - массивы print *, c ! 0 0 2 0 5 d = find_c(-1, 1) ! Параметры функции - скаляры print *, d ! 0 end program etest
Замечание. Поскольку элементные функции являются чистыми, они могут быть использованы в операторе и конструкции FORALL.
⎯ 304 ⎯
Приложение 4. Нововведения стандарта Фортран 95
Элементные подпрограммы задаются подобно элементным функциям. В теле процедуры могут изменяться параметры с видом связи OUT и INOUT. Пример: elemental subroutine find_c(a, b, c) integer(4), intent(in) :: a, b integer(4), intent(out) :: c if(a > b) then c=a else if(b < 0) then c = abs(b) else c=0 end if end subroutine find_c
program etest2 interface elemental subroutine find_c(a, b, c) integer(4), intent(in) :: a, b integer(4), intent(out) :: c end subroutine end interface integer(4), dimension(5) :: a = (/ -1, 2, -3, 4, 5 /), b = (/ 1, 2, -2, 4, 3 /), c integer(4) :: d = 5 call find_c(a, b, c) ! Параметры и результат - массивы print *, c ! 0 0 2 0 5 call find_c(-1, 1, d) ! Параметры и результат - скаляры print *, d ! 0 end program etest2
П. 4.5. Встроенные функции MINLOC и MAXLOC Функции для массивов MAXLOC и MINLOC дополнены необязательным параметром dim и имеют теперь синтаксис: MAXLOC(array [, dim] [, mask]) и MINLOC(array [, dim] [, mask]) MAXLOC - возвращает индексы максимального элемента массива array или максимальных элементов по заданному измерению dim. Значение каждого максимального элемента удовлетворяет заданным (необязательным) условиям mask. Если несколько элементов содержат максимальное значение, то берется первый по порядку их следования в ⎯ 305 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
array. Результат MAXLOC, если не задан параметр dim, записывается в одномерный массив, размер которого равен числу измерений array. Если параметр dim задан, то: •
массив-результат имеет ранг, на единицу меньший ранга массива array, и форму (d1, d2, ..., ddim-1, ddim+1, ..., dn), где (d1, d2, ..., dn) - форма массива array;
•
если array имеет ранг, равный единице, то MAXLOC(array, dim [, mask]) возвращает то же, что и функция MAXLOC(array [, MASK = = mask]); иначе значение элемента (s1, s2, ..., sdim-1, sdim+1, ..., sn) результата функции MAXLOC(array, dim [, mask]) равно MAXLOC(array(s1, s2, ..., sdim-1, sdim+1, ..., sn), [,MASK = mask(s1, s2, ..., sdim-1, sdim+1, ..., sn)]); MINLOC - выполняет те же действия, что и MAXLOC, но для минимальных элементов массива array. Пример:
integer(4), parameter :: m = 3, n = 5 real(4) :: a(m, n) integer ip(n) a = reshape((/ 3.0, 4.0, 5.0, 6.0, 7.0, 2.0, 3.0, 4.0, 5.0, 6.0, & 1.0, 2.0, 3.0, 4.0, 5.0 /), & shape = (/ m, n /), order = (/ 2, 1 /)) ip = maxloc(array = a, mask = a < 5, dim = 1) print *, ip ! 1 1 2 3 0
&
П. 4.6. Расширение функций CEILING и FLOOR Функции для массивов CEILING и FLOOR имеют теперь необязательный параметр kind и выполняются следующим образом: CEILING(a, [kind]) - возвращает наименьшее целое, большее или равное значению вещественного аргумента a. Разновидность типа результата совпадает со значением аргумента kind, если он задан, или - в противном случае - со стандартной разновидностью целого типа; FLOOR(a, [kind]) - возвращает наибольшее целое, меньшее или равное значению вещественного аргумента a. Разновидность типа результата совпадает со значением аргумента kind, если он задан, или - в противном случае - со стандартной разновидностью целого типа. Пример: integer(4) :: i
⎯ 306 ⎯
Приложение 4. Нововведения стандарта Фортран 95
integer(2) :: iarray(2) i = ceiling(8.01) iarray = floor((/ 8.01, -5.6 /), kind = 2)
! Возвращает 9 типа INTEGER(4) ! Возвращает (8, -6) типа INTEGER(2)
П. 4.7. Инициализация ссылки и функция NULL Ссылку можно инициализировать, применив функцию NULL: real(4), dimension(::), pointer :: pa => null( )
Функция NULL дает ссылке статус "не ассоциирована с адресатом". Этот статус позволяет, например, использовать ссылку в качестве фактического параметра до ее прикрепления к адресату, например: program null_test real(4), dimension(:), pointer :: pa => null( ) interface subroutine poas(pa) real(4), dimension(:), pointer :: pa end subroutine poas end interface call poas(pa) ! Параметр - неприкрепленная ссылка print *, pa(2) ! 3.500000 end program null_test subroutine poas(pa) real(4), dimension(:), pointer :: pa allocate(pa(5)) pa = 3.5 end subroutine poas
Функция NULL может быть использована и среди исполняемых операторов: pa => null( )
П. 4.8. Инициализация компонентов производного типа Компонентам производного типа при его объявлении можно присвоить начальные значения, которые по умолчанию будут являться начальными значениями соответствующих компонентов всех объектов этого типа. Инициализированы могут быть как все, так и отдельные компоненты, например: type entry real(4) :: val = 3.0
! Объявление типа entry ! Инициализация компонента val
⎯ 307 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
integer(4) :: index type(entry), pointer :: next => null( ) end type entry type(entry) :: erray(10) print *, erray(5)%val
! Инициализация не выполняется ! Инициализация компонента next !
3.000000
П. 4.9. Встроенная подпрограмма CPU_TIME Подпрограмма CPU_TIME(time) возвращает процессорное время time, тип которого - REAL(4). Единицы измерения времени - секунды; после десятичной точки time содержит две значащие цифры. Может быть использована для оценки продолжительности вычислений, например: real(4) :: start_time, finish_time call cpu_time(start_time) <вычисления...> call cpu_time(finish_time) print *, 'Calculation time = ', finish_time - start_time
П. 4.10. Автоматическое освобождение размещаемых массивов Если при выходе из процедуры приложение явно оператором DEALLOCATE не освобождает занимаемую размещаемым массивом память, то теперь это произойдет автоматически. Это новое свойство приводит к снижению недоступной, ранее выделенной под размещаемые массивы памяти.
П. 4.11. Комментарии в NAMELIST-списке Именованный, предназначенный для ввода список с данными теперь может содержать комментарий, следующий, как и в исходном коде, после восклицательного знака, например: integer(4) :: k, iar(5) logical(4) :: fl real(4) :: r4 complex(4) :: z4 character(10) :: c10 namelist /mesh/ k, fl, r4, z4, c10, c4, iar open(1, file = 'a.txt') read (1, mesh) write(*, *) k, iar, fl
⎯ 308 ⎯
Приложение 4. Нововведения стандарта Фортран 95
write(*, *) r4, z4, ' ', c10
Состав файла a.txt: &Mesh K = 100, FL = T, Z4 = (38, 0), ! Z4 - переменная типа COMPLEX(4) C10 = 'abcdefgh' ! Значение символьной переменной r4 = 24.0, iar = 1, 2, 3, 5, 5 /
П. 4.12. Вычисляемая длина поля при форматном выводе В Фортране, если при форматном выводе число полученных в результате преобразования символов превосходит длину поля w, все поле заполняется звездочками (*). Теперь, однако, можно задать значение w, равное нулю, например I0 или F0.5. В этом случае длина поля определяется значением выводимого числа. Это свойство применимо с дескрипторами B, F, I, O и Z.
П. 4.13. Полная версия оператора END INTERFACE Ранее, в стандарте Фортран 90, родовой интерфейс мог быть задан так: interface mymax module procedure inmax, remax, chmax end interface
Теперь же END INTERFACE может завершаться родовым именем: interface mymax module procedure inmax, remax, chmax end interface mymax
П. 4.14. Исключенные из Фортрана свойства Стандарт 95-го года исключил из Фортрана: 1) DO-цикл с вещественным и двойной точности параметром, например запрещен цикл: real(4) :: x, xs = 1.0, xf = 3.0, dx = 0.1 do x = xs, xf, df print *, x*sin(x) end do
2) переход на END IF из внешнего блока; ⎯ 309 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
3) оператор PAUSE; 4) оператор ASSIGN присваивания меток и назначаемый GO TO; ясно, что теперь нельзя использовать в качестве метки целочисленную переменную, получившую значение в результате выполнения оператора ASSIGN: integer :: label, m = 55 assign 20 to label print label, m 20 format(1x, i5)
! ASSIGN - исключенный оператор ! Ошибка. Использование label недопустимо
5) символьные константы с указателем длины, называемые также холлеритовскими константами, например: 16HThis is a string.
П. 4.15. Устаревшие свойства Фортрана Приводимые ниже устаревшие свойства языка будут удалены из Фортрана в последующих версиях, и, следовательно, их применение нежелательно. В приводимом перечне тремя звездочками (***) отмечены свойства, которые, начиная с версии Фортран 95, классифицируются как устаревшие. Прочие свойства отнесены к устаревшим еще стандартом Фортран 90. В списке 9 устаревших свойств: 1) арифметический оператор IF; 2) завершение нескольких DO-циклов одним оператором и завершение DO-цикла оператором, отличным от CONTINUE или END DO; 3) альтернативный возврат из процедуры; 4) вычисляемый оператор GO TO (***); 5) операторная функция (***); 6) размещение оператора DATA среди исполняемых операторов (***); 7) символьные функции предполагаемой - CHARACTER(len = *) - длины (***); 8) фиксированный формат исходного кода (***); 9) форма CHARACTER* для объявления символьных типов данных (***). Предполагается изъять из Фортрана первые 6 свойств уже в следующем стандарте.
⎯ 310 ⎯
Литература 1) Бартеньев О. В. Современный Фортран. - М.: Диалог-МИФИ, 1998. 397 с. 2) Бартеньев О. В. Фортран для студентов. - М.: Диалог-МИФИ, 1999. 400 с. 3) Березин Б. И., Березин С. Б. Начальный курс С и С++. - М.: ДиалогМИФИ, 1999. - 288 с. 4) Боресков А. В., Шикин Е. В., Шикина Г. Е. Компьютерная графика: первое знакомство. - М.: Финансы и статистика, 1996. - 176 с. 5) Любимский Э. З., Мартынюк В. В., Трифонов Н. П. Программирование. - М.: Наука, 1980. - 608 с. 6) Меткалф М., Рид Дж. Описание языка программирования Фортран 90. М.: Мир, 1995. - 302 с. 7) Пильщиков В. Н. Программирование на языке ассемблера IBM PC. - М.: Диалог-МИФИ, 1999. - 288 с. В. А. Язык С++ и объектно-ориентированное 8) Скляров программирование. - Минск: Высш. шк., 1997. - 478 с. 9) Фортран 90. Международный стандарт. - М.: Финансы и статистика, 1998. - 416 с. 10) Шикин Е. В., Боресков А. В. Компьютерная графика. Динамика, реалистические изображения. - М.: Диалог-МИФИ, 1995. - 288 с.
⎯ 311 ⎯
Содержание ПРЕДИСЛОВИЕ ................................................................................................3 1. ИСПОЛЬЗОВАНИЕ ДИАЛОГОВ ..............................................................5
1.1. ПОСТАНОВКА ЗАДАЧИ ...............................................................................5 1.2. ПОСТРОЕНИЕ ДИАЛОГОВОГО ОКНА ...........................................................6 1.2.1. Проект для диалога.................................................................................. 6 1.2.2. Задание параметров диалога .................................................................. 6 1.2.3. Задание и обработка статического текста ........................................ 7 1.2.4. Обработка редактируемых полей .......................................................... 9 1.2.5. Кнопки OK и Cancel................................................................................ 10 1.2.6. Меню диалога.......................................................................................... 11 1.2.7. Доступ к файлам хранения диалога...................................................... 12 1.2.8. Работа с диалогом в программе ........................................................... 13
1.3. УСОВЕРШЕНСТВОВАНИЕ ПРОГРАММЫ
ТАБУЛЯЦИИ ФУНКЦИИ ..............17
1.3.1. Вывод сообщения.................................................................................... 17 1.3.2. Задание числа итераций ........................................................................ 18 1.3.2.1. Зачем группировать радиокнопки ............................................ 18 1.3.2.2. Элементы управления, задающие число итераций ................. 19 1.3.2.3. Устройство элементов управления ......................................... 20 1.3.2.4. Использование радиокнопок ...................................................... 20 1.3.2.5. Изменения в тексте программы .............................................. 21
1.4. УПРАВЛЯЮЩИЕ ЭЛЕМЕНТЫ ДИАЛОГА ....................................................23 1.5. ПРОЦЕДУРЫ ДЛЯ РАБОТЫ С ДИАЛОГОМ...................................................24 1.6. УПРАВЛЯЮЩИЕ ИНДЕКСЫ.......................................................................25 1.7. ПРИМЕНЕНИЕ СПИСКОВ ...........................................................................29 1.7.1. Открытые списки.................................................................................. 29 1.7.2. Списки с редактируемым полем ........................................................... 35 1.7.3. Список без редактируемого поля.......................................................... 38
1.8. ВЫХОД ИЗ ДИАЛОГА ................................................................................39 1.9. ИЗМЕНЕНИЕ ВОЗВРАЩАЕМОЙ ДИАЛОГОМ ВЕЛИЧИНЫ ...........................40 2. ВЫВОД ГРАФИЧЕСКИХ ДАННЫХ ......................................................41
2.1. ГРАФИЧЕСКИЙ ДИСПЛЕЙ .........................................................................41 2.2. РАСТРОВОЕ ИЗОБРАЖЕНИЕ ......................................................................42 2.3. ВИДЕОАДАПТЕР .......................................................................................43 2.4. ВИДЕООКНО И ОКНА ВЫВОДА..................................................................44 ⎯ 312 ⎯
Содержание
2.5. ЗАДАНИЕ КОНФИГУРАЦИИ ВИДЕООКНА ..................................................45 2.6. СИСТЕМЫ ГРАФИЧЕСКИХ КООРДИНАТ. ОКНО ВЫВОДА ..........................48 2.7. ОЧИСТКА И ЗАПОЛНЕНИЕ ЭКРАНА ЦВЕТОМ ФОНА ..................................53 2.8. УПРАВЛЕНИЕ ЦВЕТОМ .............................................................................54 2.8.1. Система цветов RGB. Цветовая палитра .......................................... 54 2.8.2. Цветовая палитра VGA......................................................................... 56 2.8.3. Не RGB-функции управления цветом ................................................... 60 2.8.3.1. Управление цветом фона .......................................................... 60 2.8.3.2. Управление цветом неграфического текста .......................... 62 2.8.3.3. Управление цветом графических примитивов ........................ 63 2.8.4. RGB-функции управления цветом ......................................................... 63 2.8.4.1. Управление RGB-цветом фона ................................................. 63 2.8.4.2. Управление RGB-цветом неграфического текста ................. 64 2.8.4.3. Управление RGB-цветом графических примитивов ............... 64
2.9. ТЕКУЩАЯ ПОЗИЦИЯ ГРАФИЧЕСКОГО ВЫВОДА ........................................65 2.10. ГРАФИЧЕСКИЕ ПРИМИТИВЫ ..................................................................66 2.10.1. Вывод пикселей ..................................................................................... 67 2.10.2. Вывод отрезка прямой линии .............................................................. 72 2.10.3. Вывод прямоугольника ......................................................................... 72 2.10.4. Вывод многоугольника ......................................................................... 73 2.10.5. Вывод эллипса и окружности ............................................................. 74 2.10.6. Вывод дуги эллипса и окружности ..................................................... 75 2.10.7. Вывод сектора...................................................................................... 76 2.10.8. Координаты конечных точек дуги и сектора ................................... 77 2.10.9. Пример вывода графических примитивов.......................................... 77
2.11. ВЫВОД ТЕКСТА ......................................................................................79 2.11.1. Вывод текста без использования шрифтов ...................................... 79 2.11.2. Вывод зависимого от шрифта текста ............................................. 82
2.12. УПРАВЛЕНИЕ ТИПОМ ЛИНИЙ .................................................................87 2.13. ЗАПОЛНЕНИЕ ЗАМКНУТЫХ ОБЛАСТЕЙ ...................................................89 2.14. ПЕРЕДАЧА ОБРАЗОВ ...............................................................................92 2.14.1. Обмен с оперативной памятью .......................................................... 92 2.14.2. Обмен с внешней памятью .................................................................. 98
2.15. СТАТУС ВЫПОЛНЕНИЯ ГРАФИЧЕСКИХ ПРОЦЕДУР ...............................100 3. ПРИЛОЖЕНИЯ QUICKWIN ..................................................................102
3.1. ВОЗМОЖНОСТИ QUICKWIN ................................................................102 3.2. ОПЕРАЦИИ НАД ОКНАМИ QUICKWIN .................................................103 3.2.1. Виды окон QUICKWIN ......................................................................... 103 3.2.2. Создание дочернего окна ..................................................................... 103 3.2.3. Активизация дочернего окна ............................................................... 104
⎯ 313 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
3.2.4. Размещение дочернего окна в фокусе................................................. 105 3.2.5. Закрытие устройства дочернего окна .............................................. 105 3.2.6. Изменение свойств дочернего окна .................................................... 105 3.2.7. Изменение размеров и позиции обрабляющего и дочернего окна..... 106
3.3. ИЗМЕНЕНИЕ СИСТЕМНОГО МЕНЮ..........................................................111 3.4. ИНИЦИАЛИЗАЦИЯ МЕНЮ И ОБРАМЛЯЮЩЕГО ОКНА .............................114 3.5. СОЗДАНИЕ СПИСКА ИМЕЮЩИХСЯ ДОЧЕРНИХ ОКОН .............................116 3.6. ИМИТАЦИЯ ВЫБОРА КОМАНД МЕНЮ .....................................................117 3.7. ИЗМЕНЕНИЕ СООБЩЕНИЙ QUICKWIN.................................................118 3.8. ВЫВОД СТАНДАРТНОГО ОКНА СООБЩЕНИЙ ..........................................118 3.9. ПЕРЕОПРЕДЕЛЕНИЕ СООБЩЕНИЯ О ПРОГРАММЕ ...................................120 3.10. КОПИРОВАНИЕ ТЕКСТА И ГРАФИКИ ОКНА QUICKWIN......................120 3.11. ПРИМЕНЕНИЕ ПОЛЬЗОВАТЕЛЬСКИХ ИКОН ...........................................122 3.12. ИСПОЛЬЗОВАНИЕ МЫШИ .....................................................................123 3.12.1. Связанные с мышью события ........................................................... 123 3.12.2. Функции обработки событий ........................................................... 124 3.12.3. Блокирующая функция WAITONMOUSEEVENT .............................. 128 3.12.4. Особенности работы с блокирующими процедурами .................... 131 3.12.5. Особенности подпрограмм обработки событий............................ 132
4. МНОГОНИТОЧНОЕ ПРОГРАММИРОВАНИЕ ................................133
4.1. ПОСТАНОВКА ЗАДАЧИ ...........................................................................133 4.2. НИТИ И ПРОЦЕССЫ ................................................................................135 4.3. ОРГАНИЗАЦИЯ НИТЕЙ ............................................................................136 4.3.1. Модули для многониточного программирования .............................. 136 4.3.2. Построение проекта с несколькими нитями .................................... 137 4.3.3. Создание нити ...................................................................................... 137 4.3.4. Реализующая нить процедура ............................................................. 138 4.3.5. Пример создания нити......................................................................... 139 4.3.6. Использование значения параметра argument................................... 140 4.3.7. Обеспечение независимости переменных процедуры нити............. 141 4.3.8. Способы синхронизации нитей при доступе к ресурсам ................. 141
4.4. ПРОГРАММИРОВАНИЕ ОБЪЕКТОВ СИНХРОНИЗАЦИИ НИТЕЙ .................142 4.4.1. Критические секции ............................................................................. 142 4.4.2. Текст программы вывода бегущих полос с использованием критической секции ....................................................................................... 144 4.4.3. Устранение недостатка в работе программы BARS2 .................... 146 4.4.4. Исключения ........................................................................................... 147 4.4.5. Применение исключений в рассматриваемой задаче ........................ 149 4.4.6. Семафоры ............................................................................................. 150 4.4.7. Применение семафоров в рассматриваемой задаче ......................... 151
⎯ 314 ⎯
Содержание
4.5. ОРГАНИЗАЦИЯ НИТЕЙ ПРИ МНОГООКОННОМ ВЫВОДЕ .........................152 4.6. ПЕРЕЧЕНЬ МНОГОНИТОЧНЫХ ПРОЦЕДУР ...............................................158 5. КОМПИЛЯЦИЯ И ПОСТРОЕНИЕ ПРОГРАММ ..............................161
5.1. НАЗНАЧЕНИЕ КОМАНДЫ DF ..................................................................161 5.2. ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ ....................................................................161 5.3. ФОРМАТ КОМАНДЫ DF..........................................................................163 5.4. ПРАВИЛА ЗАДАНИЯ ОПЦИЙ ...................................................................164 5.5. ВХОДНЫЕ И ВЫХОДНЫЕ ФАЙЛЫ ............................................................164 5.6. ФОРМИРОВАНИЕ ИМЕН ВЫХОДНЫХ ФАЙЛОВ ........................................165 5.7. ВРЕМЕННЫЕ ФАЙЛЫ ..............................................................................165 5.8. УПРАВЛЕНИЕ БИБЛИОТЕКОЙ ОБЪЕКТНЫХ ФАЙЛОВ...............................166 5.9. ВАРИАНТЫ ИСПОЛЬЗОВАНИЯ КОМАНДЫ DF.........................................167 5.9.1. Компиляция и построение с одним исходным файлом ..................... 167 5.9.2. Применение переменной окружения DF ............................................ 168 5.9.3. Компиляция и построение с несколькими исходными файлами....... 168 5.9.4. Использование последовательности команд ..................................... 169 5.9.5. Подключение библиотек объектных файлов ..................................... 170 5.9.6. Использование динамических библиотек ........................................... 170 5.9.7. Компиляция и построение приложений с текстами программ на Фортране и СИ................................................... 171 5.9.8. Оптимизация при компиляции и построении .................................... 175 5.9.9. Команда DF, параметры которой хранятся в текстовом файле ....................................................................................... 175 5.9.10. Примеры ошибочного использования команды DF ......................... 176
5.10. ОГРАНИЧЕНИЯ КОМПИЛЯТОРА И ПОСТРОИТЕЛЯ .................................176 5.11. ПЕРЕЧЕНЬ ОПЦИЙ КОМПИЛЯТОРА И ПОСТРОИТЕЛЯ ............................177 5.12. РАСПРЕДЕЛЕНИЕ ОПЦИЙ ПОСТРОИТЕЛЯ ПО КАТЕГОРИЯМ VS .....................................................................................186 5.13. ИСПОЛЬЗОВАНИЕ ОПЦИЙ FPS В КОМАНДЕ DF....................................188 6. ПОВЫШЕНИЕ БЫСТРОДЕЙСТВИЯ ПРОГРАММ .........................193
6.1. ВВЕДЕНИЕ ..............................................................................................193 6.2. ВРЕМЯ ВЫПОЛНЕНИЯ ПРОГРАММЫ .......................................................193 6.3. ВЫРАВНИВАНИЕ ДАННЫХ .....................................................................195 6.3.1. Размещение данных в памяти............................................................. 195 6.3.2. Невыравненные данные........................................................................ 196 6.3.3. Сообщения о невыравненных данных ................................................. 197 6.3.4. Как выравнивать данные..................................................................... 197 6.3.5. Опции компилятора, управляющие выравниванием .......................... 198
⎯ 315 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
6.4. ОПТИМИЗАЦИЯ ИСХОДНОГО КОДА ........................................................199 6.4.1. Эффективное использование массивов .............................................. 199 6.4.2. Организация быстрого ввода/вывода................................................. 202 6.4.3. Дополнительные приемы оптимизации кода..................................... 204
6.5. ВЛИЯНИЕ ОПЦИЙ КОМАНДЫ DF НА ПРОИЗВОДИТЕЛЬНОСТЬ ................205 6.6. ОБОБЩЕНИЯ...........................................................................................210
⎯ 316 ⎯
Содержание
7. ПРОГРАММИРОВАНИЕ НА НЕСКОЛЬКИХ ЯЗЫКАХ ................211
7.1. ВВЕДЕНИЕ ..............................................................................................211 7.2. АТРИБУТЫ DEC.....................................................................................213 7.2.1. Атрибут ALIAS .................................................................................... 214 7.2.2. Атрибуты С и STDCALL ..................................................................... 215 7.2.3. Атрибут EXTERN ................................................................................ 218 7.2.4. Атрибут REFERENCE......................................................................... 220 7.2.5. Атрибут VALUE................................................................................... 221 7.2.6. Атрибут VARYING............................................................................... 222 7.2.7. Атрибуты DLLEXPORT и DLLIMPORT............................................. 223
7.3. СОГЛАШЕНИЯ ОБ ИМЕНАХ .....................................................................225 7.4. ПРОПИСНЫЕ И СТРОЧНЫЕ БУКВЫ В ИМЕНАХ ........................................227 7.4.1. Имена из прописных букв..................................................................... 227 7.4.2. Имена из строчных букв...................................................................... 228 7.4.3. Имена из смеси прописных и строчных букв ..................................... 229 7.4.4. Имена VISUAL C++ ............................................................................. 229
7.5. ИНТЕРФЕЙС ВНЕШНЕЙ ПРОЦЕДУРЫ.......................................................230 7.6. СОГЛАСОВАНИЕ ТИПОВ ДАННЫХ ..........................................................231 7.7. ПЕРЕДАЧА ДАННЫХ В ПРОГРАММАХ С РАЗНОЯЗЫЧНЫМИ ПРОЦЕДУРАМИ233 7.7.1. Обмен данных через параметры процедур ........................................ 233 7.7.1.1. Передача символьных данных ................................................. 235 7.7.1.2. Передача массивов................................................................... 238 7.7.1.3. Передача ссылок и размещаемых массивов Фортрана........ 241 7.7.1.4. Передача целочисленных указателей ..................................... 242 7.7.1.5. Имена модулей.......................................................................... 243 7.7.1.6. Доступ к объектам модулей Фортрана в функциях СИ ....................................................................................... 244 7.7.1.7. Определение модульной процедуры в СИ............................... 246 7.7.2. Использование common-блоков Фортрана и структур СИ ............................................................................................... 246 7.7.2.1. Прямой доступ к common-блокам Фортрана и структурам СИ ................................................................................. 246 7.7.2.2. Передача адреса common-блока.............................................. 248 7.7.3. Передача производных типов данных ................................................ 249
7.8. ОСОБЕННОСТИ ОДНОВРЕМЕННОГО ИСПОЛЬЗОВАНИЯ ФОРТРАНА И СИ ..............................................................251 7.9. ВКЛЮЧЕНИЕ ФОРТРАН-ПРОЦЕДУР В ПРИЛОЖЕНИЯ НА БЕЙСИКЕ ..................................................................................................251 7.10. СОЗДАНИЕ ПРИЛОЖЕНИЙ НА ФОРТРАНЕ И АССЕМБЛЕРЕ ...................252 7.10.1. Формирование результата функцией Ассемблера.......................... 253
⎯ 317 ⎯
О. В. Бартеньев. Visual Fortran: новые возможности
7.10.2. Примеры программ на Фортране и Ассемблере ............................. 253
ПРИЛОЖЕНИЕ 1. ДИРЕКТИВЫ DVF .......................................................259 П. 1.1. ОБЗОР ДИРЕКТИВ ...............................................................................259 П. 1.2. ИСПОЛЬЗОВАНИЕ ДИРЕКТИВ .............................................................260 П. 1.3. ДИРЕКТИВЫ, КОНТРОЛИРУЮЩИЕ ПРАВИЛА НАПИСАНИЯ ИСХОДНОГО КОДА .............................................................................................................262 П. 1.3.1. Директивы $STRICT и $NOSTRICT................................................ 262 П. 1.3.2. Директивы $FREEFORM и $NOFREEFORM................................ 263 П. 1.3.3. Директива $FIXEDFORMLINESIZE .............................................. 264
П. 1.4. УСЛОВНАЯ КОМПИЛЯЦИЯ ПРОГРАММЫ ...........................................264 П. 1.4.1. Директивы $DEFINE и $UNDEFINE ............................................. 264 П. 1.4.2. Конструкции директив $IF и $IF DEFINED................................. 266
П. 1.5. УПРАВЛЕНИЕ ОТЛАДКОЙ ПРОГРАММЫ .............................................268 П. 1.5.1. Директивы $DECLARE и $NODECLARE ...................................... 268 П. 1.5.2. Директива $MESSAGE.................................................................... 268
П. 1.6. ВЫБОР ЗАДАВАЕМОЙ ПО УМОЛЧАНИЮ РАЗНОВИДНОСТИ ТИПА .................................................................................269 П. 1.6.1. Директива $INTEGER ..................................................................... 269 П. 1.6.2. Директива $REAL............................................................................ 270
П. 1.7. УПРАВЛЕНИЕ ПЕЧАТЬЮ ЛИСТИНГА
ИСХОДНОГО КОДА ...................271
П. 1.7.1. Директива $TITLE ........................................................................... 271 П. 1.7.2. Директива $SUBTITLE.................................................................... 271
П. 1.8. ДИРЕКТИВА $OBJCOMMENT .........................................................272 П. 1.9. ДИРЕКТИВА $OPTIONS ...................................................................273 П. 1.10. ДИРЕКТИВА $PACK........................................................................274 П. 1.11. ДИРЕКТИВА $PSECT ......................................................................276 П. 1.12. ДИРЕКТИВА $ATTRIBUTES ..........................................................277 П. 1.13. ДИРЕКТИВА $ALIAS.......................................................................278 П.1.14. ДИРЕКТИВА IDENT .........................................................................278 П.1.15. ДИРЕКТИВЫ И ОПЦИИ КОМПИЛЯТОРА .............................................278 ПРИЛОЖЕНИЕ 2. ОПИСАТЕЛИ ССЫЛОК И РАЗМЕЩАЕМЫХ МАССИВОВ ФОРТРАНА.....................................280 П. 2.1. КОД НА ФОРТРАНЕ ............................................................................280 П. 2.2. КОД НА СИ........................................................................................286 ПРИЛОЖЕНИЕ 3. ВЫВОД РУССКИХ СООБЩЕНИЙ В DOS-ОКНО ..................................................................................................289
⎯ 318 ⎯
Содержание
П. 3.1. ПРЕОБРАЗОВАНИЯ “СИМВОЛ - КОД СИМВОЛА” И “КОД СИМВОЛА - КОД” ..............................................................................289 П. 3.2. ПРЕОБРАЗОВАНИЕ DOS-БУКВ РУССКОГО АЛФАВИТА В WINDOWSБУКВЫ РУССКОГО АЛФАВИТА И ОБРАТНО ...................................................290 ПРИЛОЖЕНИЕ 4. НОВОВВЕДЕНИЯ СТАНДАРТА ФОРТРАН 95...................................................................................................294 П. 4.1. ОПЕРАТОР И КОНСТРУКЦИЯ FORALL..............................................294 П. 4.2. ОПЕРАТОР ELSEWHERE .................................................................300 П. 4.3. ЧИСТЫЕ ПРОЦЕДУРЫ .........................................................................300 П. 4.4. ЭЛЕМЕНТНЫЕ ПРОЦЕДУРЫ ................................................................303 П. 4.5. ВСТРОЕННЫЕ ФУНКЦИИ MINLOC И MAXLOC ..............................305 П. 4.6. РАСШИРЕНИЕ ФУНКЦИЙ CEILING И FLOOR..................................306 П. 4.7. ИНИЦИАЛИЗАЦИЯ ССЫЛКИ И ФУНКЦИЯ NULL................................307 П. 4.8. ИНИЦИАЛИЗАЦИЯ КОМПОНЕНТОВ ПРОИЗВОДНОГО ТИПА ...............307 П. 4.9. ВСТРОЕННАЯ ПОДПРОГРАММА CPU_TIME .....................................308 П. 4.10. АВТОМАТИЧЕСКОЕ ОСВОБОЖДЕНИЕ РАЗМЕЩАЕМЫХ МАССИВОВ 308 П. 4.11. КОММЕНТАРИИ В NAMELIST-СПИСКЕ .........................................308 П. 4.12. ВЫЧИСЛЯЕМАЯ ДЛИНА ПОЛЯ ПРИ ФОРМАТНОМ ВЫВОДЕ ..............309 П. 4.13. ПОЛНАЯ ВЕРСИЯ ОПЕРАТОРА END INTERFACE ..........................309 П. 4.14. ИСКЛЮЧЕННЫЕ ИЗ ФОРТРАНА СВОЙСТВА......................................309 П. 4.15. УСТАРЕВШИЕ СВОЙСТВА ФОРТРАНА..............................................310 ЛИТЕРАТУРА ................................................................................................311
⎯ 319 ⎯