Описание пользователя
Глава 3. Использование стандартных модулей автокомпиляции
§3.8. Настройки стандартного модуля автокомпиляции
§3.8.9. Параметры формирования исходного текста
Описываются различные настройки, управляющие формированием исходного текста программы по введенным пользователем фрагментам: как называется главная функция DLL, как выглядит заголовок экспортированной функции, какие описания RDS нужно автоматически включать в программу и т.п.
В настройках модуля автокомпиляции можно указать, как именно формируются те части исходного текста программы модели, за которые не отвечает пользователь: главная функция DLL, экспортированная функция модели, код инициализации глобальных переменных и т.п. Чтобы настроить эти параметры, следует вызвать окно настройки модуля и на его вкладке «» нажать кнопку «». В открывшемся дополнительном окне настроек следует выбрать вкладку «» (рис. 503).
Рис. 503. Настройка параметров исходного текста (на примере Borland C++ 5.5)
Первый флажок на вкладке отвечает за автоматическое включение в текст программы файлов, содержащих описания, необходимые для взаимодействия модели блока с RDS. Включение флажка добавляет в текст непосредственно после общих описаний следующий фрагмент:
#include <RdsDef.h> #define RDS_SERV_FUNC_BODY rdsbcppGetService #include <RdsFunc.h> #include <CommonBl.h> #include <CommonAC.hpp>
Он содержит команды «#include» для стандартных файлов заголовков, входящих в состав RDS. Чтобы эти команды могли выполниться, путь к папке с этими файлами должен быть передан в параметрах запуска компилятора (имена параметров для указания папок заголовков должны быть указаны в описании компилятора). В настройках его запуска для этого пути можно использовать символическое обозначение «$RDSINCLUDE$» (см. §3.8.4). Перед включением файла «RdsFunc.h» расположена команда «#define», вводящая макроопределение RDS_SERV_FUNC_BODY – его наличие приведет к автоматической вставке в текст программы функции для доступа к сервисным функциям DLL, которая, в данном случае, будет называться rdsbcppGetService (подробнее об этом – в §2.2 руководства программиста).
Если не устанавливать флажок «», приведенные выше описания добавлены не будут, а без них модель скомпилировать невозможно. Все необходимые команды в этом случае следует добавить вручную в общие описания модуля. Это менее удобно, поэтому отключать автоматическое добавление описаний RDS следует только в том случае, если в них необходимо что-либо изменить – например, ввести какие-либо настроечные макроопределения (см. А.5.1 приложений).
Ниже флажка «» расположен зависящий от него флажок «» – если не включен первый, второй будет заблокирован. Справа от него можно ввести значение для используемой в RDS константы RDSCALL, обозначающей тип вызова всех сервисных функций. Если не включать в текст программы определение для RDSCALL, она будет автоматически правильно определена при включении файла «RdsDef.h» как CALLBACK – это именно тот тип, который нужен, поэтому устанавливать флажок «» в большинстве случаев не нужно. Он оставлен в настройках для совместимости со старыми версиями файла «RdsDef.h», в которых автоматическое определение для RDSCALL отсутствовало. Если включить его, в поле ввода справа необходимо ввести либо «CALLBACK», либо другой тип вызова, поддерживаемый компилятором, при котором аргументы функции передаются в стеке справа налево и стек освобождается вызванной функцией. Например, для Borland С++ можно использовать тип __stdcall (с двумя знаками подчеркивания в начале слова). При этом непосредственно перед командой «#include» для «RdsDef.h» будет вставлено описание:
#define RDSCALL введенный_в_поле_текст
Ниже располагается флажок «», при установке которого главная функция DLL будет добавлена в текст автоматически. Главная функция (точка входа) – это функция, которая вызывается при загрузке библиотеки в память и ее выгрузке (подробнее об этом – в А.2.2 приложений). Каждая DLL должна иметь главную функцию, поэтому, если не включать этот флажок, эту функцию нужно вручную добавить в общие описания модуля. Заголовок формируемой функции вводится полностью в одну строчку в поле «», расположенное непосредственно под флажком. В этот заголовок должно входить полное описание функции – тип, имя и список параметров, причем второй параметр в списке обязательно должен называться «reason» – внутри автоматически формируемой функции используется его значение. После этой строки заголовка модуль автокомпиляции вставляет открывающую фигурную скобку и начинает тело функции, поэтому точку с запятой в конце заголовка добавлять не следует. Имя главной функции DLL обычно указывается в описании используемого компилятора. Например, для Borland С++ следует использовать такой заголовок:
int WINAPI DllMain(HINSTANCE hinst,unsigned long reason,void *lpReserved)
При этом автоматически сформированная главная функция DLL будет иметь примерно следующий вид:
int WINAPI DllMain(HINSTANCE hinst,unsigned long reason,void *lpReserved) { if(reason==DLL_PROCESS_ATTACH) { if(!RDS_SERV_FUNC_BODY()) { // No access to RDS functions if(rdsIncompatibleDll) rdsIncompatibleDll(); } else // Service functions accessible { rdsGetHugeDouble(&rdsbcppHugeDouble); // Registering block function rdsfuncControlValueChanged.Register( "Common.ControlValueChanged"); } } return 1; }
Конкретное наполнение тела главной функции DLL зависит от настроек модуля автокомпиляции и от наличия функций блока в создаваемой модели (см. §3.5.5).
Далее располагается выпадающий список «», который указывает модулю, в какое именно место формируемого текста программы следует добавить команды присвоения начальных значений глобальным переменным, необходимым для работы модели. К этим переменным относятся переменные-указатели на сервисные функции RDS, глобальные объекты функций блоков и т.п. Возможен выбор одного из трех вариантов:
- «в главной функции DLL» – команды присвоения начальных значений будут добавлены внутрь автоматически формируемой главной функции DLL, как в примере выше (инициализация при этом производится в момент загрузки DLL в память);
- «при первом вызове функции блока» – команды будут добавлены в автоматически формируемую функцию модели блока, изнутри которой вызываются все пользовательские реакции (инициализация при этом производится в момент первого обращения к модели блока);
- «не инициализировать» – глобальные переменные автоматически не инициализируются, все необходимые команды нужно добавлять вручную – например, в реакцию на инициализацию блока.
Выполнять инициализацию глобальных переменных по возможности рекомендуется в главной функции DLL. Если при настройке модуля на работу с компилятором возникли проблемы, связанные с главной функцией (например, пришлось добавлять ее вручную в общие описания модуля), инициализацию следует проводить при первом вызове функции модели блока. Выбирать в выпадающем списке вариант «не инициализировать» рекомендуется только в самом крайнем случае, когда компилятор по каким-либо причинам выдает ошибки в автоматически добавленных командах инициализации.
Следует учитывать, что, хотя регистрация функций блока тоже относится к инициализации глобальных переменных, она будет выполнена даже в том случае, если в выпадающем списке выбран вариант «не инициализировать». В этом случае регистрация функций будет выполнена в конструкторе класса блока.
Под выпадающим списком выбора места инициализации глобальных переменных находятся поля ввода «» и «». С их помощью задается тип и имя автоматически формируемой функции модели блока, то есть общей функции, отвечающей за всю работу блока, и имя, под которым эта функция видна «снаружи» библиотеки при ее подключении.
В поле «» вводится тип и имя функции модели блока без списка параметров – этот список будет добавлен автоматически. Вместо имени функции следует использовать символическое обозначение «$FUNC$» (см. §3.8.4), которое при формировании текста будет автоматически заменено на жестко встроенное в модуль автокомпиляции имя «rdsbcppBlockEntryPoint». Перед этим именем должны находиться все описания, необходимые для того, чтобы эта функция была экспортирована из библиотеки, то есть чтобы указатель на нее можно было бы получить функцией Windows API GetProcAddress. Кроме того, эта функция должна иметь тип вызова RDSCALL.
На рис. 503 приведен пример описания, подходящего для Borland С++. На рисунке видно только начало этого описания (весь текст на рисунке не уместился), но, на самом деле, в поле ввода введен следующий текст:
extern "C" __declspec(dllexport) int RDSCALL $FUNC$
Когда модуль автокомпиляции будет формировать функцию модели блока по этому описанию, она будет выглядеть следующим образом (фрагмент, сформированный по тексту из поля ввода, выделен цветом):
extern "C" __declspec(dllexport) int RDSCALL rdsbcppBlockEntryPoint( int CallMode, // Call mode (message RDS_BFM_*) RDS_PBLOCKDATA BlockData, // Block data structure LPVOID ExtParam) // Additional data (depends on CallMode) { … }
Во введенном в поле тексте «$FUNC$» заменено на «rdsbcppBlockEntryPoint» и получившийся текст добавлен перед открывающей скобкой списка параметров функции, который, как и ее тело, формируется модулем автоматически согласно требованиям RDS.
В поле «» вводится экспортированное имя функции модели, которое может не совпадать с именем функции в программе (изменение имен экспортированных функций называется «name mangling» и указывается в описании каждого компилятора). Именно экспортированное имя будет аргументом вызова GetProcAddress, при помощи которого RDS подключает функцию модели к блоку. В поле ввода этого имени можно использовать символические обозначения «$FUNC$», «$FUNCLC$» и «$FUNCUC$» (см. §3.8.4). На рис. 503 в примере для Borland С++ экспортированное имя совпадает с именем самой функции, поэтому в поле введено обозначение «$FUNC$». Но, например, компилятор Digital Mars C++ по умолчанию добавляет к экспортированному имени дополнительные символы. Функция блока с именем «rdsbcppBlockEntryPoint» и требуемым списком параметров получает в этом компиляторе имя «_rdsbcppBlockEntryPoint@12» – это можно определить опытным путем, скомпилировав модель блока с таким именем вручную и посмотрев список экспортированных функций получившегося файла DLL (как правило, в состав компилятора входят специализированные программы для такого просмотра). Таким образом, при настройке модуля на работу с Digital Mars C++ в поле «» нужно вводить текст «_$FUNC$@12».
В нижней части вкладки размещаются флажки, управляющие дополнительными описаниями и разрешающие использование некоторых директив препроцессора.
Флажок «» вставляет в формируемый текст программы следующие команды:
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#ifndef M_E
#define M_E 2.71828182845904523536
#endif
При этом в программе для числа π можно использовать константу M_PI, а для основания натурального логарифма – константу M_E. Эти имена констант общеприняты, и многие компиляторы уже содержат их описания в своих стандартных файлах заголовков, поэтому, в большинстве случаев, этот флажок можно не устанавливать.
Флажок «» разрешает модулю автокомпиляции вставлять в формируемый текст директиву «#line», изменяющую номера строк в сообщениях об ошибках. При использовании этой директивы снижается вероятность того, что модуль неправильно соотнесет номер строки в общем сформированном файле с номером строки в одном из фрагментов текста, введенных пользователем, что приведет к тому, что при двойном щелчке на сообщении об ошибке в редакторе модели курсор будет установлен не в ту строку, где эта ошибка возникла. Если компилятор поддерживает директиву «#line», этот флажок лучше установить.
Флажок «» разрешает модулю вставлять указанную директиву перед каждой автоматически сформированной функцией с параметрами. Эта директива отключает для следующей за ней функции предупреждение компилятора о том, что параметры функции не используются в ее теле. Это предупреждение, хотя и может быть полезно для пользовательских функций (с его помощью можно обнаружить и исключить ненужные параметры), бессмысленно для автоматически формируемых функций реакции на события: модуль всегда передает в них все возможные параметры, а уже пользователь решает, нужны ли они ему. Например, функция, формируемая для реакции на нажатие кнопки мыши (см. §3.6.11), всегда получает в качестве параметра указатель на структуру, содержащую координаты курсора, нажатую кнопку и т.п. Пользователь пишет только тело этой функции, и, для самых простых реакций, ему не нужна вся эта информация – достаточно самого факта нажатия кнопки. Но компилятор, обнаружив, что указатель на структуру описания события в теле функции нигде не использован, выдаст предупреждение, которое увидит пользователь, и оно может сбить его с толку. По этой причине, если компилятор поддерживает директиву «#pragma argsused», рекомендуется включать этот флажок. Если директива не поддерживается, рекомендуется запретить вывод предупреждения о неиспользованных параметрах функций в настройках запуска компилятора (как правило, в командной строке компиляторов предусмотрены параметры, позволяющие включать и выключать вывод отдельных предупреждений). В настройках некоторых стандартных модулей автокомпиляции это предупреждение по умолчанию выключено, хотя компиляторы, для которых предназначены модули, и поддерживают «#pragma argsused».
В правой нижней части вкладки находится кнопка «», заполняющая все поля ввода значениями, предлагаемыми разработчиками модуля (для поддерживаемых компиляторов это будут значения, совместимые с ними).