Текущий раздел:

Описания RDS

Руководство программиста

Глава 3. Создание модулей автоматической компиляции

§3.3. Подключение моделей к блокам и вызов редактора

Полный исходный текст на языке C++ для библиотеки (DLL) с примером модуля автокомпиляции моделей блоков для языка C. В пример добавленв возможность подключения моделей к блокам и редактирование моделей. Компиляция пока не реализована.

Изменения относительно §3.2 выделены цветом.

      #include <windows.h>
      #include <RdsDef.h>
      // Подготовка описаний сервисных функций
      #define RDS_SERV_FUNC_BODY GetInterfaceFunctions
      #include <RdsFunc.h>

      //========== Главная функция DLL ==========
      int WINAPI DllMain(HINSTANCE /*hinst*/,
                               unsigned long reason,
                               void* /*lpReserved*/)
      { if(reason==DLL_PROCESS_ATTACH) // Загрузка DLL
          { // Получение доступа к функциям RDS
            if(!GetInterfaceFunctions())
              RDS_SERV_ERROR_MSGW // Сообщение: старая версия RDS
          }
        return 1;
      }
      //========= Конец главной функции =========

      // Имена секций файла модели
      // Начало файла
      #define TCTEXTSECTION_START  "$TESTCMODEL"
      // Секция переменных
      #define TCTEXTSECTION_VARS   "$VARS"
      // Секция исходного текста
      #define TCTEXTSECTION_PROG   "$PROG"
      //=========================================
      // Класс личной области данных модуля автокомпиляции
      class TCAutoCompData
      { private:
          // Пути
          char *CompPath;     // к компилятору
          char *LinkPath;     // к редактору связей (link)
          char *IncludePath;  // к файлам заголовков
          char *LibPath;      // к библиотекам
          // Нужно ли добавлять путь к exe-файлу компилятора и редактора связей
          BOOL CompSetPath,LinkSetPath;
          // Параметры командной строки
          char *CompParams;   // компилятора
          char *LinkParams;   // редактора связей
          // Параметры исходного текста
          char *DllMainName;  // имя главной функции DLL
          char *ModelFuncHdr; // заголовок функции модели
          char *Exported;     // экспортированное имя функции

          // Освободить все динамические строки
          void FreeAllStrings(void)
            { rdsFree(CompPath);
              rdsFree(LinkPath);
              rdsFree(IncludePath);
              rdsFree(LibPath);
              rdsFree(CompParams);
              rdsFree(LinkParams);
              rdsFree(DllMainName);
              rdsFree(ModelFuncHdr);
              rdsFree(Exported);
            };

          // Сообщение об ошибке в модели
          static void ModelErrorMsg(char *modelname,int errorcode);
        public:
          // Чтение параметров модуля из INI-файла
          void ReadFromIni(const char *IniFileName);
          // Запись параметров модуля в INI-файл
          void WriteToIni(const char *IniFileName);
          // Настройка параметров модуля
          void Setup(const char *IniFileName);

          // Создать новую пустую модель
          void CreateEmptyModel(void);
          // Выбрать существующий файл модели
          void ConnectExistingModel(const char *oldmodel);
          // Проверить возможность подключения модели к блоку
          int CanAttachBlock(RDS_COMPCANATTACHBLKDATA *param);
          // Открыть редактор модели
          void OpenEditor(RDS_OPENEDITORDATA *param);
          // Конструктор класса
          TCAutoCompData(void)
          { IncludePath=LibPath=NULL;
            CompSetPath=LinkSetPath=TRUE;
            #ifdef RDS_WIN64
              CompPath=rdsDynStrCopy(
                "$RDS$\\mingw-w64\\bin\\g++.exe");
              LinkPath=rdsDynStrCopy(CompPath);
              CompParams=rdsDynStrCopy(
                "-Wall -m64 -g -I\"$RDSINCLUDE$\" -c \"$TEMP$\\$NAME$.cpp\" -o \"$TEMP$\\$NAME$.o\""
                );
              LinkParams=rdsDynStrCopy(
                "-shared -static -static-libgcc -static-libstdc++ -Wl,--dll \"$TEMP$\\$NAME$.o\" -o \"$TEMP$\\$NAME$.dll\" -s -m64 -luser32"
                );
              DllMainName=rdsDynStrCopy("extern \"C\" __declspec(dllexport) BOOL APIENTRY DllMain");
              ModelFuncHdr=rdsDynStrCopy(
                "extern \"C\" __declspec(dllexport) int RDSCALL autocompModelFunc"
                );
              Exported=rdsDynStrCopy("autocompModelFunc");
            #else
              CompPath=rdsDynStrCopy(
                "$RDS$\\tdm-gcc\\bin\\g++.exe");
              LinkPath=rdsDynStrCopy(CompPath);
              CompParams=rdsDynStrCopy(
                "-shared -fpermissive -c -I\"$RDSINCLUDE$\" \"$TEMP$\\$NAME$.cpp\""
                );
              LinkParams=rdsDynStrCopy(
                "-shared -static -static-libgcc -static-libstdc++ -o \"$TEMP$\\$NAME$.dll\" \"$TEMP$\\$NAME$.o\""
                );
              DllMainName=rdsDynStrCopy("extern \"C\" BOOL WINAPI DllMain");
              ModelFuncHdr=rdsDynStrCopy(
                "extern \"C\" __declspec(dllexport) int RDSCALL autocompModelFunc");
              Exported=rdsDynStrCopy("autocompModelFunc@12");
            #endif
          };
          // Деструктор класса
          ~TCAutoCompData(){ FreeAllStrings(); };
      };
      //=========================================

      // Запись параметров модуля в INI-файл
      void TCAutoCompData::WriteToIni(const char *IniFileName)
      { RDS_HOBJECT ini=rdsINICreateTextHolder(TRUE);

        // Загружаем старый текст из файла во вспомогательный объект, если он есть
        if(rdsFileExists(IniFileName,NULL,NULL,NULL))
          rdsSetObjectStr(ini,RDS_HINI_LOADFILE,0,IniFileName);

        // Создаем секцию [Paths...]
        rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"Paths" RDS_PLATFORMNAME);
        // Записываем в нее пути
        rdsINIWriteString(ini,"Compiler",CompPath);
        rdsINIWriteString(ini,"Linker",LinkPath);
        rdsINIWriteString(ini,"Include",IncludePath);
        rdsINIWriteString(ini,"Lib",LibPath);
        rdsINIWriteBool(ini,"CompilerToPaths",CompSetPath);
        rdsINIWriteBool(ini,"LinkerToPaths",LinkSetPath);

        // Создаем секцию [Params...]
        rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"Params" RDS_PLATFORMNAME);
        // Записываем в нее параметры командной строки
        rdsINIWriteString(ini,"Compiler",CompParams);
        rdsINIWriteString(ini,"Linker",LinkParams);

        // Создаем секцию [Func...]
        rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"Func" RDS_PLATFORMNAME);
        // Записываем в нее остальные параметры
        rdsINIWriteString(ini,"DllMain",DllMainName);
        rdsINIWriteString(ini,"ModelFunc",ModelFuncHdr);
        rdsINIWriteString(ini,"Exported",Exported);

        // Сохраняем получившееся в файл
        rdsSetObjectStr(ini,RDS_HINI_SAVEFILE,0,TCAUTOCOMP_INI);

        // Уничтожение вспомогательного объекта
        rdsDeleteObject(ini);
      }
      //=========================================

      // Чтение параметров модуля из INI-файла
      void TCAutoCompData::ReadFromIni(const char *IniFileName)
      { RDS_HOBJECT ini=rdsINICreateTextHolder(TRUE);

        // Загружаем текст из файла во вспомогательный объект
        rdsSetObjectStr(ini,RDS_HINI_LOADFILE,0,IniFileName);
        // Проверяем, нет ли ошибок
        if(rdsCommandObject(ini,RDS_HINI_GETLASTERROR)) // Ошибка
          return;

        // Читаем строки из секции [Paths...]
        if(rdsINIOpenSection(ini,"Paths" RDS_PLATFORMNAME))
          { char *CompPath_old=CompPath, // Старые значения
                 *LinkPath_old=LinkPath,
                 *IncludePath_old=IncludePath,
                 *LibPath_old=LibPath;
            // Загружаем новые строки
            CompPath=rdsDynStrCopy(rdsINIReadString(ini,"Compiler",CompPath_old,NULL));
            LinkPath=rdsDynStrCopy(rdsINIReadString(ini,"Linker",LinkPath_old,NULL));
            IncludePath=rdsDynStrCopy(rdsINIReadString(ini,"Include",IncludePath_old,NULL));
            LibPath=rdsDynStrCopy(rdsINIReadString(ini,"Lib",LibPath_old,NULL));
            // Освобождаем старые строки
            rdsFree(CompPath_old);
            rdsFree(LinkPath_old);
            rdsFree(IncludePath_old);
            rdsFree(LibPath_old);
            // Добавление в пути
            CompSetPath=rdsINIReadBool(ini,"CompilerToPaths",CompSetPath);
            LinkSetPath=rdsINIReadBool(ini,"LinkerToPaths",LinkSetPath);
          }

        // Читаем строки из секции [Params...]
        if(rdsINIOpenSection(ini,"Params" RDS_PLATFORMNAME))
          { char *CompParams_old=CompParams, // Старые значения
                 *LinkParams_old=LinkParams;
            // Загружаем новые строки
            CompParams=rdsDynStrCopy(rdsINIReadString(ini,"Compiler",CompParams_old,NULL));
            LinkParams=rdsDynStrCopy(rdsINIReadString(ini,"Linker",LinkParams_old,NULL));
            // Освобождаем старые строки
            rdsFree(CompParams_old);
            rdsFree(LinkParams_old);
          }

        // Читаем строки из секции [Func...]
        if(rdsINIOpenSection(ini,"Func" RDS_PLATFORMNAME))
          { char *DllMainName_old=DllMainName, // Старые значения
                 *ModelFuncHdr_old=ModelFuncHdr,
                 *Exported_old=Exported;
            // Загружаем новые строки
            DllMainName=rdsDynStrCopy(rdsINIReadString(ini,"DllMain",DllMainName_old,NULL));
            ModelFuncHdr=rdsDynStrCopy(rdsINIReadString(ini,"ModelFunc",ModelFuncHdr_old,NULL));
            Exported=rdsDynStrCopy(rdsINIReadString(ini,"Exported",Exported_old,NULL));
            // Освобождаем старые строки
            rdsFree(DllMainName_old);
            rdsFree(ModelFuncHdr_old);
            rdsFree(Exported_old);
          }
        // Вспомогательный объект больше не нужен
        rdsDeleteObject(ini);
      }
      //=========================================

      // Настройка модуля автокомпиляции
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      void TCAutoCompData::Setup(const char *IniFileName)
      { RDS_HOBJECT window;	// Объект-окно
        const char exefilter[]=	// Фильтр для диалога выбора файла
               "Исполняемые файлы (*.exe)|*.exe\nВсе файлы|*.*";

        // Создание окна
        window=rdsFORMCreate(TRUE,-1,-1,"Параметры модуля");

        // Создание вкладки
        rdsFORMAddTab(window,0,"Пути");

        // Поле ввода выбора EXE-файла компилятора
        rdsFORMAddEdit(window,0,1,RDS_FORMCTRL_OPENDIALOG,
            "Компилятор:",300);
        rdsSetObjectStr(window,1,RDS_FORMVAL_LIST,exefilter);
        rdsSetObjectStr(window,1,RDS_FORMVAL_VALUE,CompPath);

        // Нужно устанавливать путь?
        rdsFORMAddEdit(window,0,100,RDS_FORMCTRL_CHECKBOX,
            "Добавлять папку в путь",300);
        rdsSetObjectInt(window,100,RDS_FORMVAL_VALUE,CompSetPath);

        // Поле ввода выбора EXE-файла редактора связей
        rdsFORMAddEdit(window,0,2,RDS_FORMCTRL_OPENDIALOG,
            "Редактор связей:",300);
        rdsSetObjectStr(window,2,RDS_FORMVAL_LIST,exefilter);
        rdsSetObjectStr(window,2,RDS_FORMVAL_VALUE,LinkPath);

        // Нужно устанавливать путь?
        rdsFORMAddEdit(window,0,200,RDS_FORMCTRL_CHECKBOX,
            "Добавлять папку в путь",300);
        rdsSetObjectInt(window,200,RDS_FORMVAL_VALUE,LinkSetPath);

        // Выбор папки файлов заголовков
        rdsFORMAddEdit(window,0,3,RDS_FORMCTRL_DIRDIALOG,
            "Папка заголовков:",300);
        rdsSetObjectStr(window,3,RDS_FORMVAL_VALUE,IncludePath);

        // Выбор папки файлов библиотек
        rdsFORMAddEdit(window,0,4,RDS_FORMCTRL_DIRDIALOG,
            "Папка библиотек:",300);
        rdsSetObjectStr(window,4,RDS_FORMVAL_VALUE,LibPath);

        // Создание вкладки
        rdsFORMAddTab(window,1,"Параметры");

        // Параметры командной строки компилятора
        rdsFORMAddEdit(window,1,5,RDS_FORMCTRL_MULTILINE,
            "Параметры компилятора:",300);
        rdsSetObjectInt(window,5,RDS_FORMVAL_MLHEIGHT,3*24);// Высота
        rdsSetObjectStr(window,5,RDS_FORMVAL_VALUE,CompParams);

        // Параметры командной строки редактора связей
        rdsFORMAddEdit(window,1,6,RDS_FORMCTRL_MULTILINE,
            "Параметры редактора связей:",300);
        rdsSetObjectInt(window,6,RDS_FORMVAL_MLHEIGHT,3*24);// Высота
        rdsSetObjectStr(window,6,RDS_FORMVAL_VALUE,LinkParams);

        // Создание вкладки
        rdsFORMAddTab(window,2,"Описания");

        // Имя главной функции
        rdsFORMAddEdit(window,2,7,RDS_FORMCTRL_EDIT,
            "Имя главной функции DLL:",300);
        rdsSetObjectStr(window,7,RDS_FORMVAL_VALUE,DllMainName);

        // Заголовок модели
        rdsFORMAddEdit(window,2,8,RDS_FORMCTRL_MULTILINE,
            "Заголовок функции модели:",300);
        rdsSetObjectInt(window,8,RDS_FORMVAL_MLHEIGHT,2*24);// Высота
        rdsSetObjectStr(window,8,RDS_FORMVAL_VALUE,ModelFuncHdr);

        // Экспортированное имя
        rdsFORMAddEdit(window,2,9,RDS_FORMCTRL_EDIT,
            "Экспортированное имя:",300);
        rdsSetObjectStr(window,9,RDS_FORMVAL_VALUE,Exported);

        // Открытие окна
        if(rdsFORMShowModalEx(window,NULL)) // Нажата OK
          { // Освобождаем старые строки параметров
            FreeAllStrings();

            // Делаем динамические копии всех строк из полей ввода
            // и присваиваем их полям класса
            #define GETDYNRELPATH(x) rdsGetRelFilePath(x,rdsGetSystemPath(RDS_GSPAPPPATH),NULL)
              CompPath=GETDYNRELPATH(rdsGetObjectStr(window,1,RDS_FORMVAL_VALUE));
              LinkPath=GETDYNRELPATH(rdsGetObjectStr(window,2,RDS_FORMVAL_VALUE));
              IncludePath=GETDYNRELPATH(rdsGetObjectStr(window,3,RDS_FORMVAL_VALUE));
              LibPath=GETDYNRELPATH(rdsGetObjectStr(window,4,RDS_FORMVAL_VALUE));
            #undef GETDYNRELPATH

            CompParams=rdsDynStrCopy(rdsGetObjectStr(window,5,RDS_FORMVAL_VALUE));
            LinkParams=rdsDynStrCopy(rdsGetObjectStr(window,6,RDS_FORMVAL_VALUE));
            DllMainName=rdsDynStrCopy(rdsGetObjectStr(window,7,RDS_FORMVAL_VALUE));
            ModelFuncHdr=rdsDynStrCopy(rdsGetObjectStr(window,8,RDS_FORMVAL_VALUE));
            Exported=rdsDynStrCopy(rdsGetObjectStr(window,9,RDS_FORMVAL_VALUE));

            CompSetPath=rdsGetObjectInt(window,100,RDS_FORMVAL_VALUE);
            LinkSetPath=rdsGetObjectInt(window,200,RDS_FORMVAL_VALUE);

            // Запись изменившихся параметров в INI-файл
            WriteToIni(IniFileName);
          }
        // Уничтожение окна
        rdsDeleteObject(window);
      }
      //=========================================

      // Коды ошибок
      #define MEC_SIMPLEBLOCK 1 // Только для простых блоков
      #define MEC_READERROR   2 // Ошибка чтения файла
      #define MEC_NOTAMODEL   3 // Файл - не модель
      #define MEC_NOSECTIONS  4 // Нет нужных разделов в модели
      #define MEC_MODELWRITEERROR 5 // Ошибка записи файла модели
      //=========================================

      // Сообщение об ошибке в модели
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      void TCAutoCompData::ModelErrorMsg(
            const char *modelname,int errorcode)
      { const char *errortext;
        // Сообщение по коду ошибки
        switch(errorcode)
          { case MEC_SIMPLEBLOCK:
              errortext="Модель может подключаться только к простому блоку";
              break;
            case MEC_READERROR:
              errortext="Ошибка чтения файла";
              break;
            case MEC_NOTAMODEL:
              errortext="Файл не является моделью блока";
              break;
            case MEC_NOSECTIONS:
              errortext="В файле нет необходимых разделов";
              break;
            case MEC_MODELWRITEERROR:
              errortext="Ошибка записи файла модели";
              break;
            default:
              errortext="Неизвестная ошибка";
          }
        // Название модели, если есть
        if(modelname)
          { char *msgtext; // Здесь формируется динамический текст
            msgtext=rdsDynStrCat("Модель: ",modelname,FALSE);
            rdsAddToDynStr(&msgtext,"\n",FALSE);
            // Описание ошибки
            rdsAddToDynStr(&msgtext,errortext,FALSE);
            // Показываем сообщение
            rdsMessageBox(msgtext,"Автокомпиляция",MB_OK | MB_ICONWARNING);
            // Освобождаем память, занятую динамическим текстом
            rdsFree(msgtext);
          }
        else
          rdsMessageBox(errortext,"Автокомпиляция",MB_OK | MB_ICONWARNING);
      }
      //=========================================
      // Функция модуля автокомпиляции
      extern "C" __declspec(dllexport) int RDSCALL TestCAutoComp(
          int CallMode,                   // Событие
          RDS_PCOMPMODULEDATA ModuleData, // Данные модуля
          LPVOID ExtParam)                // Дополнительные параметры
      { // Приведение указателя на личную область данных
        // к правильному типу
        TCAutoCompData *data=(TCAutoCompData*)(ModuleData->ModuleData);
        // Вспомогательная переменная – указатель на структуру
        // функции, вызванной из окна параметров
        RDS_PCOMPEXECFUNCDATA funcdata;
        switch(CallMode)
          { // Инициализация модуля
            case RDS_COMPM_INIT:
              // Создание личной области данных модуля
              ModuleData->ModuleData=data=new TCAutoCompData();
              // Чтение параметров из INI-файла
              data->ReadFromIni(ModuleData->DataFile);
              break;

            // Очистка данных модуля
            case RDS_COMPM_CLEANUP:
              delete data;	// Удаление личной области модуля
              break;

            // Вызов окна настройки модуля
            case RDS_COMPM_SETUP:
              data->Setup(ModuleData->DataFile);
              break;

            // Получение поддерживаемых модулем функций
            // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
            //   в противном случае необходимо использовать версии функций
            //   с суффиксом "W" и символьные константы с префиксом "L"
            case RDS_COMPM_GETOPTIONS:
              // Название поля ввода имени модели
              rdscompReturnModelNameLabel("Файл исходного текста:");
              // Разрешенные кнопки
              return RDS_COMPFLAG_FUNCMODELBROWSE | // Обзор
                     RDS_COMPFLAG_FUNCMODELCREATE;  // Новый
            // Выполнить функцию
            case RDS_COMPM_EXECFUNCTION:
              // Что произошло в окне параметров
              funcdata=(RDS_PCOMPEXECFUNCDATA)ExtParam;
              switch(funcdata->Function)
                { // Нажата кнопка "Новый"
                  case RDS_COMPFLAG_FUNCMODELCREATE:
                    data->CreateEmptyModel();
                    break;
                  // Нажата кнопка "Обзор"
                  case RDS_COMPFLAG_FUNCMODELBROWSE:
                    data->ConnectExistingModel(funcdata->ModelName);
                    break;
                }
              break;
            // Проверка возможности присоединения модели к блоку
            case RDS_COMPM_CANATTACHBLK:
              return data->CanAttachBlock((RDS_PCOMPCANATTACHBLKDATA)ExtParam);
            // Открыть окно редактора
            case RDS_COMPM_OPENEDITOR:
              data->OpenEditor((RDS_POPENEDITORDATA)ExtParam);
              break;
          }
        return RDS_COMPR_DONE;
      }
      //=========================================

      // Проверка возможности назначения модели блоку
      int TCAutoCompData::CanAttachBlock(RDS_COMPCANATTACHBLKDATA *param)
      { RDS_BLOCKDESCRIPTION blockdescr;
        BOOL ok;

        // Получаем описание блока, к которому подключается модель
        blockdescr.servSize=sizeof(blockdescr);
        rdsGetBlockDescription(param->Block,&blockdescr);
        // Блок должен быть простого типа, иначе наша модель со
        // статическими переменными не сможет сним работать
        ok=(blockdescr.BlockType==RDS_BTSIMPLEBLOCK);
        // Если модель подключается вручную, выводим сообщение
        if(param->AttachReason==RDS_COMP_AR_MANUALSET && (!ok))
          { ModelErrorMsg(param->ModelName,MEC_SIMPLEBLOCK);
            return RDS_COMPR_ERRORNOMSG; // Без сообщения пользователю
          }
        return ok?RDS_COMPR_DONE:RDS_COMPR_ERROR;
      }
      //=========================================
      // Записать строку текста в файл
      BOOL WriteString(HANDLE file,const char *text)
      { DWORD res,size;
        size=strlen(text);
        if(!WriteFile(file,text,size,&res,NULL))
          return FALSE;
        return (res==size);
      }
      //=========================================
      // Загрузка текстового файла в память
      // filename – имя файла, maxread – сколько читать или 0
      // для чтения всего файла
      char *ReadTextFile(const char *filename,DWORD maxread)
      { HANDLE f;
        DWORD size,actread;
        char *fullpath,*buffer;
        BOOL ok=TRUE;
        RDSWSTR fullpath_w;

        // Получаем полный путь к файлу
        fullpath=rdsGetFullFilePath(filename,NULL,NULL);
        if(fullpath==NULL) // Нет пути - ошибка
          return NULL;

        // Преобразуем путь в UTF16 для Windows
        fullpath_w=rdsUTF8toUTF16(fullpath,FALSE);

        // Открываем файл для чтения
        f=CreateFileW(fullpath_w,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
        rdsFree(fullpath); // Имя файла больше не нужно
        rdsFree(fullpath_w);
        if(f==INVALID_HANDLE_VALUE) // Ошибка открытия
          return NULL;

        // Определяем размер файла
        size=GetFileSize(f,NULL);
        if(size==0xFFFFFFFF) // Ошибка или слишком большой
          { CloseHandle(f);
            return NULL;
          }

        // Если есть ограничение, читаем только часть файла
        if(maxread!=0 && maxread<size)
          size=maxread;

        // Отводим память для загрузки файла
        buffer=(char*)rdsAllocate(size+1);
        if(buffer==NULL) // Не удалось отвести
          { CloseHandle(f);
            return NULL;
          }

        // Считываем файл в память, если он не пустой
        if(size)
          { if(ReadFile(f,buffer,size,&actread,NULL))
             ok=(actread==size);
            else
             ok=FALSE;
          }
        // Закрываем файл
        CloseHandle(f);
        if(!ok) // Ошибка чтения
          { rdsFree(buffer);
            return NULL;
          }
        // Дописываем после конца считанного текста нулевой байт
        buffer[size]=0;
        return buffer;
      }
      //=========================================
      // Создать новый пустой файл модели
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      void TCAutoCompData::CreateEmptyModel(void)
      { // Текст пустой модели
        const char *text=TCTEXTSECTION_START "\r\n"
                         TCTEXTSECTION_VARS  "\r\n"
                         "struct\r\nbegin\r\n"
                         " signal name \"Start\" in run default 1\r\n"
                         " signal name \"Ready\" out default 0\r\n"
                         "end\r\n"
                         TCTEXTSECTION_PROG  "\r\n";
        char *relpath,*fullpath;
        HANDLE file;
        BOOL ok;
        RDSWSTR fullpath_w;

        // Вызываем диалог сохранения
        relpath=rdsCallFileDialog("",
                    RDS_CFD_SAVE|RDS_CFD_OVERWRITEPROMPT,
                    "Текстовые файлы (*.txt)|*.txt\nВсе файлы|*.*",
                    "txt",
                    "Новая модель");
        if(relpath==NULL) // Пользователь нажал "Отмена"
          return;

        // Преобразуем относительный путь в полный
        fullpath=rdsGetFullFilePath(relpath,NULL,NULL);
        // Преобразуем путь в UTF16 для Windows
        fullpath_w=rdsUTF8toUTF16(fullpath,FALSE);

        // Открываем файл для записи
        file=CreateFileW(fullpath_w,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);
        rdsFree(fullpath); // Полный путь больше не нужен
        rdsFree(fullpath_w);
        if(file==INVALID_HANDLE_VALUE) // Ошибка открытия
          ok=FALSE;
        else
          { ok=WriteString(file,text);
            CloseHandle(file);
          }
        if(!ok) // Ошибка
          { ModelErrorMsg(relpath,MEC_MODELWRITEERROR);
            return;
          }

        // Пустой файл модели записан – устанавливаем его имя
        // в качестве имени модели блока
        rdscompReturnModelName(relpath);
        // Освобождаем динамическую строку
        rdsFree(relpath);
      }
      //=========================================
      // Выбрать файл модели
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      void TCAutoCompData::ConnectExistingModel(const char *oldmodel)
      { char *relpath,*buf;
        BOOL ok=FALSE;
        // Длина текста "$TESTCMODEL"
        int prefixlen=strlen(TCTEXTSECTION_START);

        // Вызываем диалог открытия файла
        relpath=rdsCallFileDialog(oldmodel,
                    RDS_CFD_OPEN|RDS_CFD_MUSTEXIST,
                    "Текстовые файлы (*.txt)|*.txt\nВсе файлы|*.*",
                    "txt",
                    "Файл модели");
        if(relpath==NULL) // Пользователь нажал "Отмена"
          return;

        // Читаем начало файла: является ли он нашей моделью?
        buf=ReadTextFile(relpath,prefixlen);
        if(buf==NULL) // Ошибка чтения
          ModelErrorMsg(relpath,MEC_READERROR);
        else if(strcmp(buf,TCTEXTSECTION_START)) // Плохой префикс
          ModelErrorMsg(relpath,MEC_NOTAMODEL);
        else
          ok=TRUE;
        rdsFree(buf); // Считанный текст больше не нужен

        if(ok) // Делаем выбранный файл именем модели блока
          rdscompReturnModelName(relpath);
        rdsFree(relpath);
      }
      //=========================================
      // Найти ключевое слово в начале строки текста
      char *FindKeywordAtLineStart(char *text,const char *word)
      { char *s=text; // Устанавливаем s на начало текста
        if(text==NULL) return NULL;
        // Ищем в цикле
        for(;;)
          { s=strstr(s,word); // Ищем word начиная с s
            if(s==NULL) // Не найдено
              return NULL;
            if(s==text) // Найдено в начале текста - годится
              return s;
            // Найдено в середине текста - перед s должен находиться
            // перевод строки
            if(s[-1]=='\r' || s[-1]=='\n') // Есть перевод строки
              return s;
            // Перед s - другой символ. Продолжаем поиск
          }
      }
      //=========================================
      // Разбить текст модели на описание переменных и текст программы
      BOOL ProcessModelText(
               char *text,   // текст модели в памяти
               char **pVars, // возвращаемое начало переменных
               char **pProg) // возвращаемое начало программы
      { char *s,*s1;

        if(text==NULL || pVars==NULL || pProg==NULL)
          return FALSE;

        // Ищем "$VARS" в начале строки
        s=FindKeywordAtLineStart(text,TCTEXTSECTION_VARS);
        if(s==NULL) // Нет такой строки
          return FALSE;
        // Найдено - записываем в pVars начало текста после этого
        // слова с пропуском всех пустых строк
        s+=strlen(TCTEXTSECTION_VARS); // Пропускаем название секции
        s+=strspn(s,"\r\n"); // Пропускаем пустые строки после названия
        *pVars=s;

        // Ищем "$PROG" в начале строки оставшегося текста
        s1=FindKeywordAtLineStart(s,TCTEXTSECTION_PROG);
        if(s1==NULL) // Нет такой строки
          return FALSE;
        // Записываем в pProg начало текста после этого слова
        // с пропуском всех пустых строк
        s=s1+strlen(TCTEXTSECTION_PROG); // Пропускаем название
        s+=strspn(s,"\r\n"); // Пропускаем пустые строки после названия
        *pProg=s;
        // Для завершения предыдущей секции записываем нулевой байт
        // вместо '$' в "$PROG"
        *s1=0;
        return TRUE;
      }
      //=========================================
      // Прототип функции обратного вызова окна редактора
      void RDSCALL TCAutoCompData_EditorCallback(
          RDS_HOBJECT window,          // Объект-окно
          RDS_PFORMSERVFUNCDATA data); // Данные
      //=========================================
      // Открыть редактор модели
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      void TCAutoCompData::OpenEditor(RDS_OPENEDITORDATA *param)
      { char *modelpath,*modeltext;
        char *vars,*prog;
        RDS_HOBJECT win;
        BOOL ok;

        // Полный путь к файлу модели
        modelpath=rdsGetFullFilePath(param->Model->ModelName,NULL,NULL);

        // Читаем весь файл модели в память
        modeltext=ReadTextFile(modelpath,0);
        if(modeltext==NULL) // Ошибка чтения
          { ModelErrorMsg(param->Model->ModelName,MEC_READERROR);
            return;
          }

        // Разбиваем загруженный текст на описание переменных и программу
        if(!ProcessModelText(modeltext,&vars,&prog))
          { rdsFree(modeltext);
            ModelErrorMsg(param->Model->ModelName,MEC_NOSECTIONS);
            return;
          }

        // Создаем объект-окно редактора
        win=rdsFORMCreate(FALSE,-1,-1,param->Model->ModelName);

        // Создаем в нем невизуальное поле и записываем в
        // него описание переменных
        rdsFORMAddEdit(win,0,1000,RDS_FORMCTRL_NONVISUAL,NULL,0);
        rdsSetObjectStr(win,1000,RDS_FORMVAL_VALUE,vars);

        // Создаем кнопку для вызова редактора переменных
        rdsFORMAddEdit(win,0,1,RDS_FORMCTRL_BUTTON|RDS_FORMFLAG_LINE,
            "Переменные:",150);
        rdsSetObjectStr(win,1,RDS_FORMVAL_VALUE,"Изменить...");

        // Создаем многострочное поле ввода для текста программы
        rdsFORMAddEdit(win,0,2,RDS_FORMCTRL_MULTILINE,
            "Такт расчета:",600);
        rdsSetObjectStr(win,2,RDS_FORMVAL_VALUE,prog);
        rdsSetObjectInt(win,2,RDS_FORMVAL_MLHEIGHT,5*24);// Высота
        rdsSetObjectInt(win,2,RDS_FORMVAL_MLRETURNS,1); // Enter

        // Загруженный текст модели больше не нужен – мы все переписали в поля окна
        rdsFree(modeltext);

        // Открываем окно (с функцией обратного вызова)
        ok=rdsFORMShowModalServ(win,TCAutoCompData_EditorCallback);
        if(ok)
          { // Нажата кнопка "OK" - записываем текст модели в файл
            HANDLE file;
            RDSWSTR modelpath_w=rdsUTF8toUTF16(modelpath,FALSE);
            file=CreateFileW(modelpath_w,GENERIC_WRITE,0,NULL,
                     CREATE_ALWAYS,0,NULL);
            rdsFree(modelpath_w);
            if(file==INVALID_HANDLE_VALUE) // Ошибка
              ok=FALSE;
            else // Записываем в файл
              { // Заголовок и секция переменных
                ok=WriteString(file,TCTEXTSECTION_START "\r\n"
                                    TCTEXTSECTION_VARS  "\r\n");
                // Описание переменных
                ok=ok && WriteString(file,rdsGetObjectStr(win,1000,RDS_FORMVAL_VALUE));
                // Секция текста программы
                ok=ok && WriteString(file,TCTEXTSECTION_PROG "\r\n");
                // Текст программы
                ok=ok && WriteString(file,rdsGetObjectStr(win,2,RDS_FORMVAL_VALUE));
                CloseHandle(file);
              }
            if(!ok)
              ModelErrorMsg(param->Model->ModelName,MEC_MODELWRITEERROR);
          }

        // Уничтожаем окно
        rdsDeleteObject(win);
        // Освобождаем строку с именем файла модели
        rdsFree(modelpath);
      }
      //=========================================
      // Функция обратного вызова окна редактора модели
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      void RDSCALL TCAutoCompData_EditorCallback(
                       RDS_HOBJECT window,
                       RDS_PFORMSERVFUNCDATA data)
      { RDS_HOBJECT dv=NULL;
        RDS_VARDESCRIPTION vdescr;
        char *varstr;

        switch(data->Event)
          { case RDS_FORMSERVEVENT_CLICK: // Нажата кнопка
              // Создаем объект для редактирования переменных
              dv=rdsVSCreateEditor();
              // Заполняем объект описанием переменных (поле 1000)
              if(!rdsVSCreateByDescr(dv,
                      rdsGetObjectStr(window,1000,RDS_FORMVAL_VALUE)))
                break;
              // Вызываем для объекта редактор переменных
              if(!rdsVSExecuteEditor(dv,TRUE,RDS_HVAR_FALLPLAIN,
                      0,"Переменные блока"))
                break;
              // Пользователь нажал "OK" – получаем текст описания
              // измененных переменных
              vdescr.servSize=sizeof(vdescr);
              if(!rdsVSGetVarDescription(dv,-1,&vdescr))
                break;
              varstr=rdsCreateVarDescriptionString(vdescr.Var,TRUE,0,NULL);
              if(varstr)
                { // Заносим обратно в поле 1000
                  rdsSetObjectStr(win,1000,RDS_FORMVAL_VALUE,vars);
                  rdsFree(varstr);
                }
              break;
          }
        // Уничтожаем объект-редактор, если он был создан
        rdsDeleteObject(dv);
      }
      //=========================================