Описание пользователя
Глава 3. Использование стандартных модулей автокомпиляции
§3.6. Принципы создания автокомпилируемых моделей блоков
Рассматривается задание поведения блока при помощи включения различных реакций на события в его автокомпилируемую модель. Приводятся примеры моделей, выполняющих различные действия.
§3.6.1. Устройство формируемой модулем программы
Описывается общая структура автоматически формируемого текста программы модели блока с точки зрения программиста C++. Пользователям, мало знакомым с программированием, можно бегло просмотреть этот параграф для лучшего понимания устройства создаваемых ими моделей.
При каждом изменении модели модуль автоматической компиляции на основе введенных пользователем фрагментов программ, описаний и настроечных параметров формирует общий текст программы, который передается компилятору для сборки файла динамической библиотеки (DLL). Затем функция модели из созданной компилятором библиотеки подключается к обслуживаемым блокам. Разработчикам моделей имеет смысл представлять себе, как устроен формируемый текст программы – это позволит лучше понять, что можно и что нельзя использовать в фрагментах программы, вводимых в редакторе модели. При создании простейших моделей это не так важно – достаточно просто понимать, что реакция блока на любое событие оформляется модулем как функция, поэтому внутри вводимого фрагмента программы можно использовать любые конструкции языка C, оператором return можно немедленно прервать выполнение реакции, а все переменные, объявленные внутри фрагмента программы, будут локальными для этой автоматически сформированной функции. Свои функции внутри реакций на события описывать нельзя (в языке C запрещено описание функции внутри другой функции) – для этого служат глобальные описания. Описания (глобальные, внутри и после класса блока, см. §3.5.4) как функции не оформляются, поэтому все переменные, объявленные в них, становятся глобальными для данной модели, и, естественно, оператор return, как и другие исполняемые операторы C, в этих описаниях использовать невозможно (зато можно описывать свои функции). Разработчики моделей, не собирающиеся пользоваться расширенными возможностями, предоставляемыми сервисными функциями RDS, могут пропустить этот параграф – все сведения, необходимые для добавления в модель реакций на стандартные события, приведены начиная с §3.6.2.1. Список всех реакций и их параметров приводится в §3.7.
В §3.2 рассмотрена одна из простейших моделей: модель сумматора, выдающего на вещественный выход «y» сумму вещественных входов «x1» и «x2». Для ее создания достаточно было задать структуру переменных блока и ввести единственный оператор присваивания в реакцию на такт расчета. Теперь внимательно рассмотрим текст программы, которую модуль автокомпиляции сформировал для этой модели (его можно просмотреть в отдельной вкладке редактора модели, выбрав в меню редактора пункт «»). Предполагается, что настройки модели не изменялись пользователем и остались в состоянии по умолчанию. В приведенном тексте подсветка синтаксиса добавлена для большей наглядности: на самом деле, редактор модели такой подсветки не имеет.
//-------------------------------------------------------------- // Autogenerated source file for the model "new_model.mdl" // Model file: C:\Rds\Models\new_model.mdl //-------------------------------------------------------------- #include <windows.h> #include <stdlib.h> #include <math.h> #include <float.h> // Model name for indication #define RDSBCPP_MODELNAME "new_model.mdl" // No variable access message #define RDSBCPP_VARSDISABLEDMSG "\xD0\x9F\xD1\x80 … \xD0\xBC" #define RDSCALL CALLBACK #include <RdsDef.h> #define RDS_SERV_FUNC_BODY rdsbcppGetService #include <RdsFunc.h> #include <CommonBl.h> #include <CommonAC.hpp> //-------------------------------------------------------------- // Exception handling //-------------------------------------------------------------- #define RDSBCPP_EXCEPTIONS #define RDSBCPP_TRY __try #define RDSBCPP_CATCHALL __except(EXCEPTION_EXECUTE_HANDLER) //-------------------------------------------------------------- // Math error value double rdsbcppHugeDouble; //-------------------------------------------------------------- // DLL entry point //-------------------------------------------------------------- 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); } } return 1; } //-------------------------------------------------------------- //-------------------------------------------------------------- // Classes for block variables //-------------------------------------------------------------- // Type double ("D") RDSBCPP_STATICPLAINCLASS(rdsbcstDouble,double); // Type char ("S") RDSBCPP_STATICPLAINCLASS(rdsbcstSignal,char); //-------------------------------------------------------------- //-------------------------------------------------------------- // Block class //-------------------------------------------------------------- class rdsbcppBlockClass { public: // RDS internal block data structure RDS_PBLOCKDATA rdsbcppBlockData; // Static variables rdsbcstSignal Start; rdsbcstSignal Ready; rdsbcstDouble x1; rdsbcstDouble x2; rdsbcstDouble y; // Vars initialization void rdsbcppInitVars(void *base) { // Static variables Start.Init(base,0); Ready.Init(base,1); x1.Init(base,2); x2.Init(base,10); y.Init(base,18); }; // Check for dynamic vars existance BOOL rdsbcppDynVarsOk(void) { return TRUE; }; // Event reaction functions void rdsbcppModel(RDS_PINITIALCALCDATA InitialCalc); // Constructor rdsbcppBlockClass(RDS_PBLOCKDATA data); // Destructor ~rdsbcppBlockClass(); }; // class rdsbcppBlockClass //-------------------------------------------------------------- //-------------------------------------------------------------- // Block function //-------------------------------------------------------------- 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) ) { int result=RDS_BFR_DONE; // Return code // Block object (pointer is stored in the block private data) rdsbcppBlockClass *data= (rdsbcppBlockClass*)(BlockData->BlockData); switch(CallMode) { case RDS_BFM_INIT: // Block init BlockData->BlockData= (data=new rdsbcppBlockClass(BlockData)); break; case RDS_BFM_CLEANUP: // Cleanup (before deletion) delete data; break; case RDS_BFM_VARCHECK: // Static vars structure check if(strcmp((char*)ExtParam,"{SSDDD}")!=0) return RDS_BFR_BADVARSMSG; break; case RDS_BFM_MODEL: // One simulation step data->rdsbcppInitVars(BlockData->VarTreeData); data->rdsbcppModel((RDS_PINITIALCALCDATA)ExtParam); BlockData->BlockData=data; // Guards from accidental change break; } // switch(CallMode) return result; } //-------------------------------------------------------------- // Block class constructor rdsbcppBlockClass::rdsbcppBlockClass(RDS_PBLOCKDATA data) { // Block data structure pointer rdsbcppBlockData=data; } //-------------------------------------------------------------- // Block class destructor rdsbcppBlockClass::~rdsbcppBlockClass() { } //-------------------------------------------------------------- //-------------------------------------------------------------- // Event reaction functions //-------------------------------------------------------------- // One simulation step void rdsbcppBlockClass::rdsbcppModel(RDS_PINITIALCALCDATA InitialCalc) { y=x1+x2; /* Service remark */ } // rdsbcppBlockClass::rdsbcppModel //--------------------------------------------------------------
Можно видеть, что, несмотря на простоту модели, текст получился довольно объемным. В него включены все необходимые для компиляции описания, для данных блока сформирован класс с названием rdsbcppBlockClass, добавлена главная функция (точка входа) DLL и т.п. Начинается текст программы с директив «#include» для включения в текст программы стандартных файлов заголовков, в которых содержатся описания, необходимые для программ Windows и для использования функций стандартных библиотек языка C. Далее идут директивы «#define», описывающие некоторые специфичные для RDS константы, и директивы включения файлов заголовков RDS, в которых описаны необходимые для работы модели структуры и сервисные функции. Мы не будем подробно останавливаться на этих описаниях, желающие могут изучить руководство программиста. За ними следуют макросы обработки исключений, взятые из параметров модуля автокомпиляции – они будут использоваться в функции модели, если в параметрах этой модели будет установлен флажок «». Все перечисленные выше описания и директивы добавляются в каждую модель, независимо от ее параметров и состава ее реакций, и пользователь не может на них повлиять, не меняя настроек всего модуля: они находятся там, где и должны быть, и пользователь, чаще всего, не использует их напрямую, хотя и может пользоваться всеми типами и функциями из включенных файлов заголовков.
Далее в тексте программы находится объявление вещественной глобальной переменной следующего вида:
double rdsbcppHugeDouble;
Это весьма важная переменная, которую разработчикам моделей приходится использовать достаточно часто. В ней всегда хранится специальная константа HUGE_VAL из стандартных описаний языка C, обозначающая ошибку выполнения математической операции. Это значение может поступить на вход модели вместо вещественного числа, если блок, соединенный с этим входом, не смог выполнить какое-либо действие (например, в нем возникло переполнение или деление на ноль). Как правило, перед выполнением заложенных в них вычислений, модели проверяют значения на своих входах, сравнивая их со значением переменной rdsbcppHugeDouble. Если, например, вход модели – вещественная переменная x, то, обычно, реакция на такт расчета в модели выглядит следующим образом:
if(x==rdsbcppHugeDouble)
{ // Действия при ошибке
}
else
{ // Обычные действия
}
Можно, конечно, сравнивать входы не с глобальной переменной, а с самой константой HUGE_VAL, но, поскольку переменная rdsbcppHugeDouble получает свое значение непосредственно из RDS, сравнение с ней гарантирует, что во всех моделях блоков в качестве признака ошибки будет использоваться одно и то же значение, независимо от того, какие версии описаний в них использовались и на каких языках программирования они написаны. В рассматриваемой нами модели нет такой проверки – в простейших моделях она не обязательна. Тем не менее, для иллюстрации обработки ошибок входов проверка будет добавлена в эту модель в §3.6.2.1.
Следует обратить внимание на то, что имя описанной выше глобальной переменной имеет префикс «rds». Этот префикс имеют все формируемые модулем автокомпиляции имена переменных, функций и типов, поэтому разработчику модели не следует начинать с этих символов свои идентификаторы, чтобы не пересечься со служебными именами. Лучше всего вообще не начинать никакие идентификаторы с символов «rds» и «RDS» – это гарантирует отсутствие конфликтов не только с именами, созданными модулем автокомпиляции, но и со всеми именами типов, констант и функций, описанных в файлах заголовков RDS.
После описания переменной rdsbcppHugeDouble располагается автоматически формируемая функция DllEntryPoint – главная функция DLL, которую должна иметь каждая динамическая библиотека Windows. Имя и описание этой функции задается в параметрах модуля автокомпиляции. Структура и особенности главной функции DLL подробно описаны в §2.2 руководства программиста, здесь мы не будем рассматривать ее в деталях. Отметим только, что эта функция, в частности, вызывается при загрузке DLL в память RDS (при этом в ее параметре reason передается значение DLL_PROCESS_ATTACH). Текст функции сформирован модулем автокомпиляции так, чтобы в этот момент вызывалась другая, тоже автоматически сформированная, функция, служащая для получения доступа к сервисным функциям RDS (имя этой функции присвоено константе RDS_SERV_FUNC_BODY). Если доступ получен (он будет успешно получен в том случае, если DLL загружается в память RDS, и версия RDS соответствует описаниям из файлов заголовков, автоматически включенных в модель), будет вызвана сервисная функция rdsGetHugeDouble, которая запишет в уже описанную глобальную переменную rdsbcppHugeDouble значение HUGE_VAL, используемое блоками для сигнализации об ошибке математики. Если бы в параметрах модели была включена информация о версии, здесь же была бы вызвана функция, передающая в RDS номер версии модели.
Сразу за главной функцией DLL следуют макросы вида RDSBCPP_*CLASS, разворачивающиеся в описания классов для доступа к статическим и динамическим переменным, использованным в модели блока. Эти макросы описываются в файле «CommonAC.hpp», в котором, для желающих разобраться в них, содержатся подробные комментарии. Структура переменных нашего блока включает только статические переменные типов «double» и «сигнал», поэтому модуль включит в текст программы только два вызова макроса RDSBCPP_STATICPLAINCLASS: один для вещественного типа «double», и один для сигнала. Если проанализировать эти макросы, то можно увидеть, что, например, макрос для вещественного типа
RDSBCPP_STATICPLAINCLASS(rdsbcstDouble,double);
разворачивается в описание класса примерно следующего вида:
class rdsbcstDouble : public rdsbcppVarAncestor { protected: double *Ptr; public: inline void Init(void *base,int offset) {Ptr=(double*)(((unsigned char*)base)+offset);}; inline operator double() const {return *Ptr;}; inline double * GetPtr(void) const {return Ptr;}; inline double operator=(rdsbcstDouble &v) {*Ptr=(double)v; return *Ptr; }; inline double operator=(double v){*Ptr=v; return *Ptr;}; inline double operator+=(double v){(*Ptr)+=v; return *Ptr;}; inline double operator-=(double v){(*Ptr)-=v; return *Ptr;}; inline double operator*=(double v){(*Ptr)*=v; return *Ptr;}; inline double operator/=(double v){(*Ptr)/=v; return *Ptr;}; inline double operator++(){++(*Ptr); return *Ptr;}; inline double operator++(int) {double tmp=*Ptr; (*Ptr)++; return tmp;}; inline double operator--(){--(*Ptr); return *Ptr;}; inline double operator--(int) {double tmp=*Ptr; (*Ptr)--; return tmp; }; rdsbcstDouble(void):rdsbcppVarAncestor(){}; };
Можно заметить, что в этом классе все математические операторы и оператор преобразования к типу double переопределены таким образом, чтобы работать с областью памяти, на которую указывает скрытое поле класса Ptr. Таким образом, если при помощи вызова функции-члена Init записать в поле Ptr указатель на какую-либо вещественную переменную в структуре переменных блока, объект этого класса можно будет использовать во всех математических выражениях и вызовах функций, в которых требуется число типа double. При этом объект будет автоматически выполнять все операции, в которых он участвует, с числом, на которое указывает Ptr. На этом принципе основан доступ к переменным блока из фрагментов программ, вводимых пользователем: в классе блока создаются объекты специальных классов с именами, соответствующими переменным блока, а пользователь использует эти объекты в математических выражениях, как будто это сами переменные привычных ему типов. В большинстве случаев пользователь может не задумываться о том, что переменная «x1», которую он использует в модели, на самом деле имеет не тип double, а тип rdsbcstDouble. Это будет работать до тех пор, пока ему для какой-либо цели не понадобится указатель на переменную блока: запись «&x1» будет иметь тип не «указатель на double», как может ожидать пользователь, а «указатель на rdsbcstDouble». Чтобы получить настоящий указатель на переменную блока, скрытую за объектом класса (например, для использования в функциях типа sscanf), необходимо вызвать функцию-член класса GetPtr: вместо «&x1» нужно использовать «x1.GetPtr()».
Следует учитывать, что, в некоторых, достаточно редких, случаях, компилятор не может сам выполнить операцию преобразования типов. Пусть, например, «x1» – вещественная переменная блока, для которой автоматически создан объект x1 типа rdsbcstDouble. Если необходимо сформировать строку с текстовым представлением вещественного числа (например, для вывода сообщения), может показаться логичным включить в программу модели следующий фрагмент:
char buf[100]; // Вспомогательный буфер sprintf(buf,"x1=%.2lf",x1);
Здесь вызывается функция sprintf из стандартной библиотеки C, выполняющая форматированный вывод во вспомогательный массив buf. Этот фрагмент программы будет скомпилирован без ошибок, однако, работать он не будет (значение переменной блока не запишется в массив). Дело в том, что в функции sprintf может быть произвольное число параметров разных типов, поэтому компилятор не поймет, что, в данном случае, объект x1, указанный в третьем параметре функции, необходимо привести к типу double. В таких случаях необходимо указать приведение типа явно:
char buf[100]; // Вспомогательный буфер sprintf(buf,"x1=%.2lf",(double)x1);
В этом фрагменте перед x1 стоит оператор приведения типа «(double)», и разночтений в типе параметра уже не возникнет.
За исключением получения указателя на переменную и явного приведения типа, пользователю практически никогда не потребуется вызов функций-членов классов простых статических переменных, вроде приведенного выше класса rdsbcstDouble. В динамических переменных и в сложных статических (массивах, матрицах и т.п.) функции-члены, напротив, используются довольно часто: в динамических переменных с их помощью производится проверка существования переменной и уведомление других блоков о ее изменении (см. §3.6.3), в массивах и матрицах – изменение размерности (см. §3.6.2.2 и §3.6.2.3).
Если бы в нашей модели были глобальные описания пользователя, они разместились бы сразу за макросами классов переменных блока. Поскольку таких описаний в модели нет, за ними располагается описание самого класса блока с именем rdsbcppBlockClass (это имя жестко встроено в модуль автокомпиляции, его используют все модели). Объект этого класса будет создаваться для каждого блока с данной моделью в момент подключения модели (при загрузке схемы в память, загрузке блока из библиотеки или при первом подключении модели вручную) и будет существовать до тех пор, пока модель не будет отключена. Фактически, объект этого класса будет представлять собой личную область данных блока, рассматриваемую в §2.4 руководства программиста. Все реакции блока на события будут оформляться как функции-члены этого класса, все статические и динамические переменные блока (точнее, объекты для работы с этими переменными) и настроечные параметры будут полями этого класса. Команды создания и уничтожения объекта класса блока добавляются в модели автоматически, пользователю не нужно об этом заботиться.
Первое же поле в секции public этого класса (других секций у него нет) – это указатель на структуру данных, создаваемую в RDS для каждого блока:
RDS_PBLOCKDATA rdsbcppBlockData;
RDS_PBLOCKDATA – это указатель на структуру RDS_BLOCKDATA, описанную в файле «RdsDef.h». Эта структура и ее поля подробно рассматриваются в §2.3 руководства программиста и А.2.3 приложений. Мы не будем разбирать ее подробно, приведем только ее описание и рассмотрим некоторые, самые важные поля.
typedef struct { LPVOID VarTreeData; // Адрес дерева переменных блока LPVOID BlockData; // Адрес личной области данных блока RDS_BHANDLE Block; // Идентификатор блока RDSCSTR BlockNameA; // Имя блока (UTF8) RDSWCSTR BlockNameW; // Имя блока (UTF16) //RDSXCSTR BlockName; // Имя блока (поле-псевдоним) RDS_BHANDLE Parent; // Идентификатор подсистемы DWORD Flags; // Флаги RDSINT32 Width,Height; // Размеры прямоугольника блока RDSINT32 Tag; // Пользовательское поле } RDS_BLOCKDATA; typedef RDS_BLOCKDATA *RDS_PBLOCKDATA;
Чаще всего в этой структуре используется поле Block – это уникальный идентификатор данного блока, который нужен для вызова некоторых функций RDS. Например, чтобы перебрать все блоки, соединенные связями с данным блоком (пример задачи, где это может потребоваться, приведен в §3.6.13.3), необходимо вызвать функцию rdsEnumConnectedBlocks, в которую первым параметром передается идентификатор данного блока. Внутри какой-либо реакции на событие, вводимой в редакторе модели, это можно сделать следующим образом:
rdsEnumConnectedBlocks(rdsbcppBlockData->Block,…);
Поскольку указатель на структуру данных блока записан в поле rdsbcppBlockData, а все реакции на события оформляются модулем автокомпиляции как функции-члены того же класса, во всех них поле класса rdsbcppBlockData доступно просто по имени, и идентификатор данного блока можно получить при помощи выражения rdsbcppBlockData->Block.
В поле Parent структуры данных блока хранится идентификатор родительской подсистемы данного блока. Он может понадобиться в функциях, выполняющих какие-либо операции с подсистемой. Например, чтобы программно открыть окно подсистемы, в которой находится данный блок, в его модели следует сделать вызов
rdsOpenSystemWindow(rdsbcppBlockData->Parent);
Поля BlockNameA, BlockNameW и поле-псевдоним BlockName содержат указатели на строки с именем блока в разных кодировках, эти строки можно использовать, например, в сообщениях об ошибках. Поле Flags – одно из немногих полей в структуре данных блока, которое можно не только считывать, но и изменять внутри модели. Оно содержит набор битовых флагов, определяющих поведение блока. Например, в реакции на нажатие кнопки мыши блок может «захватить» мышь, взведя в этом поле флаг RDS_MOUSECAPTURE – после этого при любом перемещении курсора мыши будет вызываться реакция этого блока, даже если курсор покинет его изображение (см. пример блока-рукоятки в §3.6.11). Остальные поля структуры используются реже. В большинстве случаев, при создании простых моделей структура RDS_BLOCKDATA вообще не используется, но, если она вдруг понадобится, следует иметь в виду, что доступ к ней можно получить через поле rdsbcppBlockData.
За полем rdsbcppBlockData в классе блока следуют автоматически добавляемые модулем автокомпиляции поля для статических и динамических переменных блока и его настроечных параметров. В нашей модели всего пять статических переменных: два обязательных сигнала «Start» и «Ready» и вещественные переменные «x1», «x2» и «y». Для них будут созданы объекты двух классов, описания которых сформированы уже рассмотренными выше макросами: два объекта класса rdsbcstSignal (сигнальные переменные) и три объекта типа rdsbcstDouble (вещественные). Затем в класс блока автоматически вставляется функция, инициализирующая объекты доступа к переменным, в которой эти объекты настраиваются на конкретные переменные блока. Для нашей модели она выглядит так:
void rdsbcppInitVars(void *base) { // Static variables Start.Init(base,0); Ready.Init(base,1); x1.Init(base,2); x2.Init(base,10); y.Init(base,18); };
Вызов этой функции автоматически добавляется модулем перед вызовом функций реакции блока на любое событие, поэтому внутри реакций на события (за исключением событий инициализации и очистки данных блока, в которых к статическим переменным обращаться запрещено) все объекты уже настроены на работу с переменными блока. Внутри функции для каждой статической переменной блока (динамических в нашей модели нет) у соответствующего объекта вызывается функция-член Init, в которую передается базовый адрес всех переменных блока и смещение к конкретной переменной (эти же смещения можно увидеть и в окне редактора переменных, см. колонку «» на рис. 326). После функции инициализации внутри класса обычно располагается функция проверки наличия динамических переменных rdsbcppDynVarsOk – в нашей модели динамических переменных нет, поэтому эта функция всегда возвращает TRUE. И rdsbcppInitVars, и rdsbcppDynVarsOk создаются модулем автокомпиляции без всякого участия пользователя, и их вызовы тоже вставляются в нужные места формируемой функции модели автоматически, поэтому пользователю можно не задумываться об их существовании – вызывать их вручную ему не придется. Есть единственный случай, в котором пользователю может понадобиться функция проверки динамических переменных rdsbcppDynVarsOk: если в параметрах модели отключен флажок «», перед использованием таких переменных необходимо проверить их наличие, а для этого можно вызвать rdsbcppDynVarsOk и, если она вернет FALSE, как-то среагировать на отсутствие динамических переменных (например, взять данные из другого источника).
Далее в класс блока добавляются описания всех функций-членов для введенных пользователем реакций на события. Наша модель реагирует на единственное событие – такт расчета, поэтому такая функция будет единственной (ее параметр сейчас не важен):
void rdsbcppModel(RDS_PINITIALCALCDATA InitialCalc);
Внутрь класса вставляется только описание функции, ее тело будет добавлено в конец формируемого текста. За функциями реакции следуют автоматически формируемые описания конструктора и деструктора класса блока (сами эти функции располагаются в тексте дальше), а также описания пользователя внутри класса, если он их ввел (см. также §3.7.1). В нашей модели таких описаний нет, поэтому класс на этом завершается. После его закрывающей фигурной скобки добавляются описания пользователя после класса блока (в нашей модели таких тоже нет).
Далее в тексте программы размещается экспортированная функция модели с именем rdsbcppBlockEntryPoint (общая структура любой функции модели блока описана в §2.3 руководства программиста), заголовок которой, необходимый для ее экспорта, задается в параметрах модуля автокомпиляции. Эта функция формируется полностью автоматически и, фактически, представляет собой один большой оператор switch, в метки case которого модуль автокомпиляции вставляет вызовы функций реакций на события, сформированных для каждого введенного пользователем фрагмента программы. Кроме того, реакции на некоторые события добавляются автоматически – например, реакция на событие проверки структуры переменных RDS_BFM_VARCHECK, в которой модель сравнивает структуру статических переменных блока, к которому ее подключают, с введенной пользователем, и выдает сообщение об ошибке, если они не совпадают. Команды создания и уничтожения объекта класса блока rdsbcppBlockClass тоже вставляются в функцию автоматически – в реакции на события инициализации RDS_BFM_INIT и очистки RDS_BFM_CLEANUP соответственно. Если в параметрах модели включен перехват ошибок математических функций, в начало и в конец функции rdsbcppBlockEntryPoint будут добавлены фрагменты, заданные в настройках модуля автокомпиляции. В тексте, приведенном выше, они присутствуют – их легко узнать по комментариям.
Следует обратить особое внимание на то, что указатель на создаваемый в реакции на инициализацию блока (событие RDS_BFM_INIT) объект класса rdsbcppBlockClass записывается не только во вспомогательную переменную data, но и в поле BlockData структуры RDS_BLOCKDATA:
case RDS_BFM_INIT: // Block init BlockData->BlockData=(data=new rdsbcppBlockClass(BlockData)); break;
В этом поле указатель на созданный объект будет храниться на всем протяжении жизни блока, поэтому использовать поле BlockData->BlockData для других целей нельзя. Даже если внутри реакции на какое-либо событие присвоить ему другое значение, прежнее значение будет восстановлено при завершении функции модели, как будет показано ниже. В начале функции модели значение этого поля приводится к нужному типу и переписывается во вспомогательную переменную data, поэтому везде внутри этой функции обращения к классу блока выглядят как «data->…».
Наша модель имеет единственную введенную пользователем реакцию на событие – выполнение такта расчета, которой внутри оператора switch будет соответствовать следующий, автоматически вставленный, case:
case RDS_BFM_MODEL: // One simulation step data->rdsbcppInitVars(BlockData->VarTreeData); data->rdsbcppModel((RDS_PINITIALCALCDATA)ExtParam); BlockData->BlockData=data; // Guards from accidental change break;
Здесь сначала у созданного для данного конкретного блока объекта класса rdsbcppBlockClass (указатель на него записан в переменную data в самом начале функции модели) вызывается описанная выше функция инициализации переменных rdsbcppInitVars – теперь объекты Start, Ready, x1, x2 и y ссылаются на настоящие статические переменные блока и их можно использовать в математических выражениях. Если бы в нашей модели были динамические переменные и в ее параметрах был включен флажок «», далее была бы вызвана функция rdsbcppDynVarsOk, и, в случае возврата ей FALSE, выполнение реакции на событие было бы немедленно прервано оператором break. В нашей модели есть только статические переменные, поэтому после их инициализации сразу вызывается автоматически сформированная функция rdsbcppModel, внутрь которой будет вставлена введенная пользователем реакция на такт расчета (сама эта функция находится далее по тексту). В параметре функции передается указатель на структуру, используемую при инициализационном (предварительном) расчете, который в данной модели не используется. При реакции на такт расчета возвращенное моделью значение не анализируется RDS, поэтому функция rdsbcppModel ничего не возвращает. После ее вызова полю BlockData структуры BlockData присваивается указатель на объект класса блока из переменной data. Казалось бы, этот указатель и так должен находиться в этом поле: во-первых, он был записан туда при создании объекта в реакции на событие RDS_BFM_INIT, и, во-вторых, в переменную data он попал из этого самого поля. Однако, во введенном пользователем тексте программы, который находится внутри вызванной функции rdsbcppModel, может оказаться оператор, присваивающий полю BlockData какое-либо другое значение (структура данных блока, содержащая это поле, доступна во всех функциях-членах класса, включая пользовательские реакции на события, через поле rdsbcppBlockData). В этом операторе не может быть никакого смысла, поскольку в автокомпилируемых моделях нельзя использовать поле BlockData структуры данных блока, однако, нельзя гарантировать, что пользователь по ошибке не попытается его изменить. От такого изменения и предохраняет оператор присваивания, находящийся непосредственно перед оператором break.
В конце функции модели располагается оператор «return result;», возвращающий в RDS код завершения из переменной result. В самом начале функции модели этой переменной была присвоена константа RDS_BFR_DONE, означающая нормальное завершение модели. Единственная реакция в нашей модели ничего не возвращает, поэтому значение переменной result останется неизменным.
После функции модели записаны автоматически сформированные конструктор и деструктор класса блока, и, в самом конце, функция rdsbcppModel, внутрь которой вставлена пользовательская реакция на событие:
// One simulation step void rdsbcppBlockClass::rdsbcppModel(RDS_PINITIALCALCDATA InitialCalc) { y=x1+x2; /* Service remark */ } // rdsbcppBlockClass::rdsbcppModel
Если не обращать внимания на директиву «#line», необходимую модулю автокомпиляции для обработки сообщений компилятора об ошибках, и на служебные комментарии, можно увидеть, что все тело функции представляет собой введенный пользователем фрагмент, а именно оператор «y=x1+x2;». Так же устроены и любые другие реакции на события – весь введенный пользователем фрагмент программы вставляется внутрь некоторой функции-члена класса rdsbcppBlockClass, поэтому все описанные в этом фрагменте переменные будут локальными для данной функции и уничтожатся при ее завершении (если, конечно, они описаны без модификатора static). Многие функции реакций имеют параметры, через которые внутрь них передается информация о событии, на которое нужно среагировать (например, координаты курсора мыши или код нажатой клавиши), а наружу – результат обработки (например, среагировал ли блок на нажатие кнопки мыши). У функции rdsbcppModel тоже есть параметр, указывающий на проведение предварительного расчета, но в данном примере он не используется. Независимо от наличия параметров, все функции реакции имеют тип возврата void, поэтому их выполнение в любой момент можно прервать оператором return без параметра. Если функция что-то возвращает, возвращаемое значение будет передаваться через один из ее параметров, имеющий тип «ссылка на значение». Параметры различных реакций на события будут описаны далее в примерах. Заголовок конкретной функции, в которую будет вставлен вводимый пользователем текст, всегда можно увидеть в верхней части вкладки редактора, на которой этот текст вводится (см. рис. 332 и рис. 340).
Пользователю, создающему автокомпилируемые модели, можно и не разбираться в том, как из вводимых им фрагментов собирается полноценный текст программы. Однако, знание структуры этого текста может пригодиться при поиске ошибок в модели. Хотя модуль автокомпиляции, в большинстве случаев, может сам определить, к какому введенному пользователем фрагменту программы относится обнаруженная компилятором ошибка (при этом по двойному щелчку на сообщении об ошибке он автоматически откроет вкладку с нужным фрагментом), иногда ошибка может проявиться далеко от места своего возникновения. В этом случае модуль покажет весь автоматически сформированный текст, и пользователю желательно быть готовым к поиску своей ошибки внутри этого текста.