Рассматривается технология использования программ, написанных на языке математического пакета MATLAB, в средах визуального программирования C/C++ с целью создания Windows-приложений, требующих привлечения богатого арсенала средств, алгоритмов, процедур и функций современной математики и обладающих развитым интерфейсом, на примере сред программирования MATLAB 6.5 и Borland C++Builder 6.0. В основу технологии положено использование встроенного в систему MATLAB компилятора m-кода в C-код MATLAB Compiler, предназначенного для создания исполнимых модулей программ, написанных на языке MATLAB. Обсуждаются особенности написания m-кода программ на языке MATLAB, пригодного для компиляции в C-код, и применения C-кода, создаваемого компилятором MATLAB Compiler, в визуальных средах программирования Microsoft Visual C/C++ и Borland C++Builder. Изложение иллюстрируется примером.
Technology of the use matlab-programs in ambience of the visual programming C/C++.pdf ВВЕДЕНИЕ И ПОСТАНОВКА ЗАДАЧИВ практике программирования во многих случаяхвозникает необходимость создать программное обес-печение, решающее сложную математическую задачу,требующую использования математических проце-дур, не входящих в набор стандартных средств системпрограммирования. При решении этой задачи можнопойти разными путями. Либо воспользоваться средст-вами, предоставляемыми каким-нибудь математиче-ским пакетом, например пакетом MATLAB, и решитьматематическую задачу, не создавая никакого специ-ального программного обеспечения, написав код про-граммы на языке пакета (MATLAB решает задачи врежиме интерпретации m-кода программы). Либо са-мому писать весь программный код, включая соответ-ствующие математические процедуры, на каком-либоязыке программирования (например, на C/C++). Либоискать (скажем, в Интернете) соответствующую ма-тематическую библиотеку, совместимую с даннойсредой программирования (при этом часто неизвест-но, корректно ли она работает, каковы правила ее ис-пользования и обладает ли она достаточными воз-можностями).Для случая, когда основным требованиеммодифицированных нелинейных дифференциальныхуравнений Лотки-Вольтерры, учитывающих наличиевнутривидовой борьбы в популяции жертв, связаннойс нехваткой пищи при превышении численностьюэтой популяции некоторого критического уровня,имеет вид( )( )12 1 121 2( )( ) ( ) ( ),( )( ) ( ),dy t a by t ry t y tdtdy t c dy t y tdt⎧ = − − ⎪⎪⎨⎪= − +⎪⎩(1)где t[t0,t1] - текущее время, изменяющееся на ука-занном интервале решения задачи; y1(t), y2(t) - теку-щие численности популяций жертв и хищников соот-ветственно; y(t0) = y0 - заданное начальное условиедля вектора-столбца 1 [ ]1 22( ) (( )) ( ), ( )y t yy tt y t y t T=⎡⎢⎣ ⎤⎥⎦=численности популяций; T - знак транспонирования.Параметры a, b, c, d, r предполагаются известнымипостоянными. Смысл их следующий. Параметр a вы-ражает скорость естественного прироста популяциижертв в единицу времени в расчете на одну жертву вотсутствие хищников. Параметр c - скорость естест-венной гибели (от голода) популяции хищников вединицу времени в расчете на одного хищника в от-сутствие жертв. Коэффициенты b и d выражают соот-ветственно влияние на скорости роста - гибели каж-дой популяции наличия другой популяции. Коэффи-циент r выражает дополнительное уменьшение скоро-сти роста популяции жертв из-за размножения самихжертв. Величина, обратная этому коэффициенту, вы-ражает количество жертв, которое может прокормитьприрода в отсутствие хищников.Рис. 1. Общий вид формы примера приложенияПрограммная реализация рассматриваемого при-мера приложения должна давать пользователю воз-можность в интерактивном режиме менять все исход-ные данные задачи (параметры a, b, c, d, r, начальныйt0 =T0 и финальный t1 =Tfin моменты времени ис-следования, начальное значение численности популя-ции жертв y1(t0) = y0(1) и хищников y2(t0) = y0(2), до-пустимую относительную погрешность RelTol чис-ленного интегрирования системы дифференциальныхуравнений). Вид возможной формы интерфейса этогоприложения приведен на рис.1.На рис.2 и 3 приведены графические результатыработы приложения при значениях параметров, за-данных на форме рис.1.Рис. 2. Интегральные кривые решенияРис. 3. Фазовая траекторияДля численного интегрирования системы диффе-ренциальных уравнений (1) использован метод Рунге-Кутты 4 - 5 порядка точности. На графиках видно,как численности взаимодействующих популяцийжертв и хищников приближаются со временем к рав-новесным значениям *y1 =c/d и * ( )y2 = a−rcd b(к точке покоя на фазовой траектории).ЭТАПЫ СОЗДАНИЯ ИСПОЛНИМОГО ПРИ-ЛОЖЕНИЯРассмотрим основные этапы создания исполнимо-го модуля рассматриваемого приложения, иллюстри-руя тем самым технологию создания приложений, ис-пользующих богатые математические возможностипакета MATLAB и средства визуальной среды про-граммирования C/C++.Этап 1. Создание исходного m-кодаНаличие данного этапа может показаться необяза-тельным, так как исходный код можно писать сразуна языке Си, используя функции математическойбиблиотеки MATLAB [1, 2]. Стоит сразу оговорить,что данный путь не оптимален по нескольким причи-нам. Во-первых, создавая приложение таким образом,пользователь теряет возможность контекстной отлад-ки кода. Во-вторых, построенный код может полу-читься неустойчивым в плане исполнения. В-третьих,даже если создается чисто Си-приложение, содержа-щее небольшие участки сложных математических вы-числений, изначально проще их предварительно на-писать в MATLAB в виде функций, затем перевестиполученный m-код в С-код с помощью компилятораMATLAB, полученный код впоследствии легко под-ключить в виде модуля к приложению на C\C++.Итак, создадим m-файл функционального типа:% Файл "lve.m"function main,% Программа решения системы% дифференциальных уравнений% Лотки-Вольтеррыglobal a b c d r; % Глобальные переменныеtic, % Фиксация времени начала решенияa = 1; b = 0.01; c = 1; d = 0.02; r = 0.005;% Задание параметров системыT0 = 0; Tfin = 100; % Границы интервалаy0 = [20; 30]; % Начальная численность% популяцийyp = [c/d; (a-r*c/d)/b]; % Точка покояopt = odeset('RelTol', 1e-6); % Установка% относительной точности решенияtt = []; yy = []; % Объявление массивов[tt, yy] = ode45(@lv, [T0, Tfin], y0, opt);tt = tt'; yy = yy'; % Интегрирование системы% дифференциальных уравнений Лотки-% Вольтерры методом Рунге-Кутты 4-5% порядка точности с заданной точностьюtime = toc; disp(time) % Фиксация времени% конца решения задачи и вывод времени счета% на экранfigure(1) % Открытие 1-го окна графиковplot(tt, yy(1, :), 'g', tt, yy(2, :), 'r'), grid on,% Изображение графиков и координатной сеткиtitle('Solution of Modified Lotka-Volterra Equation'),% Заголовок графикаlegend('Preys', 'Predators'), % Легенды графиковxlabel('Time'), ylabel('Populations'),% Обозначение осей координатfigure(2) % Открытие 2-го окна графиковplot(yy(1,:), yy(2,:), 'b', yp(1), yp(2), 'r.'), grid on,% Изображение графиков и координатной сеткиtitle('Phase Trajectory'),% Заголовок графикаlegend('Phase Trajectory', 'Rest Point'),% Легенды графиковxlabel('Preys'), ylabel('Predators')% Обозначение осей координат% ----------------------------------------------------------function z = lv(t, y)% Вектор-функция, выражающая правую часть% системы дифференциальных уравнений% Лотки-Вольтеррыglobal a b c d r; % Глобальные переменныеz=[(a-b*y(2)-r*y(1))*y(1); (-c+d*y(1))*y(2)];% Возвращаемый функцией вектор-столбецЭта программа с помощью функции ode45 (методаРунге - Кутты 4 - 5 порядка) численно решает моди-фицированную систему уравнений Лотки-Вольтерры(1), которая описывает поведение популяций жертв(Preys) и хищников (Predators). Затем с помощьюфункции plot строятся графики решения этих уравне-ний в зависимости от времени (интегральные кривыерешения) и фазовая траектория решения (зависимостьчисленности популяции хищников от численностипопуляции жертв). На графики наносятся координат-ные сетки командой grid, выводятся заголовки с по-мощью функции title, а также метки кривых при по-мощи функции legend. Оси графиков подписываютсяс помощью функций xlabel и ylabel.После создания исходного m-кода можно перейтик следующему этапу.Этап 2. Перевод m-кода в C-кодс помощью компилятора MATLAB CompilerТехнология перевода программ из средыMATLAB в C-код с созданием исполняемого модуляконсольного приложения полностью изложена в [3].При этом m-код программы должен быть определен-ным образом подготовлен (модифицирован).В первую очередь необходимо преобразовать m-файлы сценарного типа (script-файлы) в m-файлыфункционального типа (function-файлы). Как это де-лается, описано в [3].Далее следует преобразовать циклические опера-ции с массивами из формы, представленной слева нарис.4, в форму, представленную справа:for i = 1 : N, for i = 1 : N,M = [M, ]; M(i) = ;end endРис.4. Преобразование операций с массивамиТакую же операцию необходимо применить и кмногомерным массивам. Размер кода во втором слу-чае, возможно, немного и увеличится, но скоростьвыполнения цикла может увеличиться во много раз.Для проверки эффективности такой замены былсконструирован простейший тест. Было создано двафайла-функции, содержащие вышеописанные циклы, втеле которых находилась процедура вывода прогрессасчета, и на выходе строился график зависимости вре-мени выполнения программ от числа итераций цикла.Оказалось, что цикл второго («правого») типа гораздоболее эффективен, чем цикл первого («левого»). Роствремени выполнения операций первого цикла имеетэкспоненциальный характер в силу постоянного дина-мического увеличения памяти, отводимой под массив,тогда как время работы второго цикла растет линейнои очень медленно (например, для числа отсчетов 10000имеем соотношение по времени тип1/тип2 как0,02сек/6,72сек в тестируемой конфигурации).На следующем шаге рекомендуется удалить всекомментарии MATLAB на русском языке, так как не-которые из них вызывают ошибку компиляции. Этопроблема русификации системы MATLAB, котораяпроявляется в некоторых версиях системы при неко-торых версиях операционной системы. Например,русская буква "я", встречающаяся как в комментари-ях, так и в функциях текстового вывода, может вос-приниматься в качестве символа конца строки. «Разо-рванная» строка, в свою очередь, вызывает ошибкукомпиляции. Данную проблему можно разрешить ужепосле перевода m-кода на язык Си (см. этап 4).Рекомендуется также, во избежание ошибок, при-вести к единому регистру имена файлов, названияфункций и наименования функций-параметров.Не следует использовать переменную с именем "j"в алгоритмах, которые переводятся на C++, так как укласса MathWorks есть метод MathWorks::j(), и этовызывает ошибку компиляции.Еще одно важное замечание. Команды title, xlabel,ylabel не являются библиотечными функциямиMATLAB и хранятся лишь в виде m-файлов. Автома-тически компилятор MATLAB не транслирует функ-ции и процедуры, записанные во вспомогательныефайлы, поэтому их тексты придется самим добавлятьв файл "lve.m". Найти их можно в каталоге"$MATLAB\toolbox\matlab\graph2d", либо вызвать наэкран, просто набрав командуtype title.mв командном окне MATLAB.После проделанных манипуляций m-файл "lve.m"готов к компиляции. Перед компиляцией командойmbuild -setupиз командной строки устанавливается требуемыйкомпилятор с языка C/C++. Для компиляции исполь-зовался следующий синтаксис командыmcc -m -B sgl lve.mиз командной строки.Получающийся в результате исполняемый модульрасполагается в той же директории, где находитсяm-файл программы lve.m, и имеет имя lve.exe, т.е. имяm-файла, использованного для компиляции.Этап 3. Присоединение C-кода,созданного компилятором MATLAB, к проектув визуальной среде программированияВ качестве визуальной среды программированиябыл выбран Borland C++ Builder версии 6.0, которыйпомимо удобного пользовательского интерфейса пре-доставляет мощный компилятор кода на C/C++.Создадим проект в этой среде, содержащий форму,изображеную на рис.1, на которую поместим компо-нент StringGrid для ввода и редактирования исходныхданных, кнопки пуска, остановки и выхода, а такжечек-бокс вывода графики и строку статуса, выводя-щую процент выполнения задачи. Наша задача состо-ит в том, чтобы написать обработчики событий, свя-занных с этими органами управления, и использоватьполученный на предыдущем этапе C-код. Но преждечем присоединять к проекту этот C-код, необходимонастроить дю среду на совместное использованиес библиотеками MATLAB C Math Library и MATLABC Graphics Library. Исчерпывающую информацию поэтому вопросу можно получить из файлаMATLAB$\extern\examples\cppmath\borland\readme.txt.Следует заметить, что после указания путей кстандартным математическим библиотекам и под-ключаемым дополнительно c/cpp-файлам в опцияхпроекта, а также добавления к проекту соответст-вующих математических библиотек может оказаться,что проект не компонуется. Это означает, что не всенеобходимые библиотеки были подключены к проек-ту. Чтобы узнать, в каком из библиотечных файловхранится «сбойная» функция, надо ознакомиться ссодержимым файлов с расширением ".def", которыеразмещены в папке "MATLAB$\extern\include\".Добавление C-кода в проектНа данном этапе постараемся произвести мини-мальную модификацию созданного MATLAB CompilerC-кода, который состоит из трех файлов: "lve.c","lve.h", "lve_mainhg.c". Поскольку файл "lve_mainhg.c"отвечает за создание автономного консольного прило-жения, то он более не потребуется, однако содержа-щийся в нем код, за исключением функции main, нуж-но перенести в создаваемый проект, для удобства в".h"-файл, дополнительно подключаемый к ".cpp"-файлу, реализующему методы формы (см. несколькониже).Теперь подключим к проекту файл "lve.c", произ-ведя следующую небольшую модификацию кода.// Файл "lve.c#include "mhelper.h" /* Содержит (пока) лишь про-тотип функции WinFlush() */< .. >static void Mlve(void){ < .. >mlfDisp(mclVv(time, "time"));WinFlush(); /* Вручную добавленный вызов функ-ции */mclPrintAns(&ans, mlfNFigure(0, _mxarray0_,NULL));< .. > }< .. >Хорошо видно, что изменения кода минимальны.Был лишь подключен файл, содержащий объявлениефункции Winflush, использовавшейся после mlfDispдля одновременного вывода времени работы про-граммы на форму, полученное от Print Handler.Print Handler - это пользовательский обработчикпечати. Для используемого примера его можно скон-струировать таким образом, чтобы данный обработ-чик было удобно использовать при выводе матриц идругих составных объектов печати. Для полноценнойже работы ему потребуется функция, которая осуще-ствляла бы вывод накопленного текста на форму ивызывалась следом за выводящей библиотечнойфункцией (например, mlfPrintMatix, mlfDisp, mlfPrintfи т.д.). Способы создания, регистрация и использова-ние пользовательского обработчика печати описыва-ются в [3], дополнительную информацию по исполь-зованию можно найти в [2].Доработка исходного C-кодаПонятно, что функция main, содержащая, в своюочередь, функцию mclMainhg, удалена (проект дол-жен содержать либо main для консольных приложе-ний, либо WinMain для Windows-приложений). Новспомогательная функция компилятора MATLABmclMainhg несла большую смысловую нагрузку, ко-торую как раз можно компенсировать вызовом функ-ций компилятора MATLAB из модуля "Unit1.h" (см.ниже). Функция mclMainhg делает следующее:- Инициалиует рабочие таблицы: таблицу гло-бальных переменных, глобальную и локальные таб-лицы файлов-функций, рабочие переменные модулейи специализированные библиотеки. Также удаляет изпамяти рабочие переменные и завершает работу спе-циализированных библиотек. Данное действие ком-пенсируется "ручным" вызовом функцииmclLibInitCommon(&_main_info);при нажатии кнопки "Run", где &_main_info - адресструктуры, содержащей все вышеперечисленные таб-лицы.- Выполняет инициализацию графической библио-теки MATLAB. Данное действие можно осуществитьсамостоятельно, единожды вызвав библиотечнуюфункцию mlfHGInitialize, что и было сделано при соз-дании формы приложения.- Вызывает через feval-интерфейс рабочий код(через функцию mlxLve). Аналогичное действие мож-но выполнить при нажатии "Run" только через стан-дартный интерфейс (функцию mlfLve).- Завершает работу графической библиотекиMATLAB. Соответствующее действие с помощьюфункции mlfHGTerminate было осуществлено в файле"Unit1.cpp" в момент закрытия приложения.// Файл "cpphelper.c"< .. > /* Содержимое "Lve_mainhg.c", за исключе-нием функции main. *///----- Print Handler -----// Файл "Unit1.cpp"#include "cpphelper.h" /* Дополнительно подклю-чаемый файл (см. выше) */TForm1 *Form1;__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner){const char *argv = *_argv;mlfHGInitialize(&_argc, &argv); /* Инициализиру-ем MATLAB C Graphics Library */}void __fastcall TForm1::Button1Click(TObject*Sender){mlfSetPrintHandler(WinPrint); /* Регистрируем об-работчик печати */mclLibInitCommon(&_main_info ); /* Осуществ-ляем инициализацию таблиц компилятора */mlfLve(); //Имя исходной m-функции}void __fastcall TForm1::FormClose(TObject *Sender,TCloseAction &Action){mlfHGTerminate(); /* Прерываем использованиеграфической библиотеки */}Здесь по нажатию кнопки на форме производитсязапуск транслированного кода с помощью функций,полностью подменяющих функцию компилятораMATLAB mclMainhg. Следует заметить, что заменафункции mclMainhg - рекомендуемое, но не обяза-тельное действие. ВызовmclMainhg(&_argc, &argv, mlxLve, 0, &_main_info);произойдет вполне успешно, однако при повторномнажатии на кнопку приложение выдаст ошибку. Этосвязано с тем, что работа данной функции рассчитанана одиночный запуск из консольного приложения.При ее использовании также теряется удобная воз-можность отладки подключенного кода, что довольнонеприятно. Поэтому настоятельно рекомендуем ис-пользовать описанный нами способ запуска C-кода.Необходимо сделать еще одно важное замечание.В случае, если нет надобности использовать в прило-жении графику MATLAB, часть кода, касающуюсяинициализации графической библиотеки, можно уда-лить из проекта.Этап 4. Внесение в приложение новыхфункциональных возможностейНазвание данного этапа довольно неопределенно,так как включает большое число технических прие-мов, касающихся взаимодействия между рабочимисредаВ данный этап можно поместить непосретвенную разработку интерфейса пользователя, кото-рый уже естественно привязан к назначению созда-ваемого приложения. В контексте данного этапа мож-но произвести расширение и переработку рассматри-ваемого примера, чтобы «qросить мостик»междусозданием иллюстративного примера и разработкойреального приложения.Увеличим временной интервал вычислений в ис-ходном m-коде, а внутри функции lv предусмотримвывод на экран процента от общего времени вычис-лений в алгоритме. Расширив код подобным образоми проделав заново все вышеперечисленные техноло-гические этапы, получим следующее: пока будут про-изводиться вычисления, форма примера как бы «xа-снет» Она не станет отвечать ни на какие внешниеили внутренние воздействия и лишь по истечениивремени работы цикла опять «~ется» когда будетвыведено, что вычисления произведены на 100%.Причина данного явления состоит в том, что ма-тематические вычисления осуществляются в контек-сте главного потока VCL, а не независимо, как этодолжно быть. Данное явление не критично, если вы-числения не занимают много времени, в противномслучае необходимо знать прогресс вычислений и,возможно, досрочно их завершить.Обращаем особое внимание, что при созданиирассматриваемого примера использовалась следую-щая комбинация средств: MATLAB Compiler v3.0 иBorland C++Builder v6.0 (содержит компиляторBorland C++ v5.6).Добавим на форму еще одну кнопку "Stop" и па-нель статуса (Status Bar), на которую будем отобра-жать процент прогресса и общее время выполнениявычислений. По нажатию на кнопку "Run" будет соз-даваться новый поток класса TNewThread, реализую-щий в отдельном модуле абстрактный класс TThread,внутри которого уже будут производиться заданныевычисления. При использовании данного стандартно-го приема программирования возникает две особен- ности, связанные с использованием функций матема-тической библиотеки MATLAB:1. Инициализация графической библиотекиMATLAB и глобальной таблицы функций должнаосуществляться в модуле главной формы, в против-ном случае возникает ошибка. К сожалению, при ис-пользовании различных версий MATLAB Compiler иBorland С++Builder возникают существенные разли-чия при инициализации и использовании графическихфункций внутри пользовательского пото потокаVCL. В данной статье описывается такое использова-ние совместно лишь с последними версиями выше-упомянутых программных продуктов.2. Вызов методов созданного потока нельзя осу-ществлять напрямую из C-модуля, так как объектыможно использовать лишь в C++. Однако вызов мето-дов потока можно произвести с помощью дополни-тельных C-функций, которые объявляются с модифи-катором extern "C" в модуле, описывающем методыпотока, где также и реализуются. Данная дополни-тельная функция вызывает метод потока, который, всвою очередь, вызывает с помощью методаSynchronize еще один метод, содержащий защищае-мый код (пример см. ниже в файле "Unit2.cpp").3. Настоятельно рекомендуется использовать кон-цепцию «неизменяемого» m-кода при построенииприложения.«НЕИЗМЕНЯЕМЫЙ» m-КОД«Неизменяемым» можно назвать такой m-код, ко-торый после трансляции на язык C/C++ нет необхо-димости редактировать и дополнять вызовом каких-либо C-функций. Определенный, стандартизованныйнабор C-функций просто напрямую вызывается из m-кода. Использование данной концепции, на самом де-ле, должно превалировать при создании автономныхприложений в визуальной среде программирования.Это связано с тем, что исходный m-код зачастуюподвергается небольшой переработке на третьем тех-нологическом этапе (где производится доработка C-кода, полученного трансляцией из m-кода). И послеповторного прохождения этапов технологии необхо-димо заново осуществить доработку C-кода, что издовольно интересного процесса в первый момент пре-вращается в неприятную, рутинную и, возможно, длякого-то нетривиальную операцию.Разработчики MATLAB предусмотрели возмож-ность сопряжения m-кода с C/C++-кодом. Начиная сверсии 2.1, компилятор MATLAB поддерживает вы-зов произвольной C/C++-функции из m-кода. Доста-точно просто предоставить m-файлу функцию-заглушку, определяющую, каким образом данный кодбудет работать в m-коде, а затем реализовать телофунк в C/C++.С другой стороны, связь между вычислительнымкодом и кодом, реализующим пользовательскуючасть приложения, совершенно необходима, причемона выражается, как правило, определенным, в какой-то мере стандартным набором функциональных осо-бенностей:- присвоение значения конкретной C-переменной,конкретного поля объекта и т.п. переменной, исполь-зуемой функциями библиотеки MATLAB;- вывод текста (скорее всего «накопленного» обра-ботчиком печати) на рабочую форму;- досрочное прекращение вычислений.Это лишь неполный перечень действий, которыйможно взять за основу и осуществлять вызов соответ-ствующих этим действиям C-функций прямо изm-кода, первоначально не думая об их реализации,которая уже непосредственно привязана к разработкепользовательской части, хотя здесь возможно исполь-зование каких-либо шаблонов, облегчающих созданиеприложения для неискушенных пользователей даннойтехнологии.Чтобы не быть голословными, рассмотрим кон-кретный пример создания m-файла на основе описан-ной выше технологии. Рассмотрим ключевые фраг-менты кода, описывающего использованные техниче-ские приемы.% Файл "func.m"function mainglobal a b c d r mMyExit Tfin oldpers,tic;% Блок инициализацииmShowGraph = AssignValue(0);% Отображать ли графикmMyExit = 0; oldpers = 0;a = 1; b = 0.01; c = 1; d = 0.02; r = 0.005;T0 = 0; Tfin = 100;y0 = [20; 30]; yp = [c/d; (a-r*c/d)/b];opt = odeset('RelTol', 1e-6); tt = []; yy = [];% Блок вычислений% (сюда же можно отнести функцию lv)[tt, yy] = ode45(@lv, [T0, Tfin], y0, opt);tt = tt'; yy = yy';time = toc;% Блок выводаif (mShowGraph)% Если 1 - выводим график, 0 - пропускаем выводfigure(1)< .. >endfprintf(1, '%3.1f seconds', time);SyncVCL(0); % Осуществить вывод на форму% в то же место% Блок описания внешних C-функцийfunction CloseThread;% Завершить выполнение потока%#external%--------------------------------------------------------------function SyncVCL(Method);% Синхронизировать с VCL вывод текста% Здесь MethodNumber - определенный способ%вывода текста%#external%--------------------------------------------------------------function OutVar = AssignValue(VarNumber);% Присвоить конкретное значение% Здесь VarNumber - определенный способ% присвоения значения% Здесь OutNumber - выходное значение,% приведенное к библиотечному типу mxArray%#external%--------------------------------------------------------------function z = lv(t, y)global a b c d r mMyExit Tfin oldpers,mMyExit = AssignValue(1);% Осуществить ли досрочное завершение% вычисленийif (mMyExit)CloseThread; % Здесь работа потока прерываетсяend;pers = floor((t / Tfin) * 100);% Вычисляем процент прогресса вычисленийif (pers > oldpers)fprintf(1, '%4.0f%% Complete', pers);SyncVCL(0); % Осуществить вывод на формуoldpers = pers;endz = [(a - b*y(2) - r*y(1))*y(1); (-c + d*y(1))*y(2)];%--------------------------------------------------------------function hh = title(string, varargin)Приведенный код мы рекомендуем в качествешаблона, используемого для создания «быстро пере-водимых» m-файлов.Все m-файлы будущего приложения следует объе-динить в один единственный, который можно назвать,скажем, "func.m", а основную часть, из которой вызы-ваются вспомогательные функции, назвать, например,main.Затем выделить в программе «блок инициализа-ции», в котором необходимо присвоить первоначаль-ные значения используемым переменным. Перемен-ные, значения которых должен ввести пользователь,инициализировать с помощью функции AssignValue(),«заранее знающей», каким образом осуществить оз-наченное присвоение.В «блоке вычислений» следует произвести требуе-мые манипуляции, где, используя функцию SyncVCL(),можно выводить на форму текущие значения перемен-ных, либо при помощи функции CloseThread досрочнопрекратить процесс вычислений.Завершать программу должен «блок вывода» ре-зультатов проделанных вычислений в графическойили текстовой форме, где можно протестировать спе-циальные переменные на предмет необходимости вы-вода конкретных составляющих.И уже в самом конце нужно разместить описаниевнешних C-функций, внутри которых должна бытьобязательно указана директива%#external,предписывающая компилятору использовать внеш-нюю реализующую версию соответствующей m-функции.Далее применим те же технологические приемыдля создания демонстрационного приложения на базе«неизменяемого» m-кода.Уже на втором технологическом этапе, т.е. на эта-пе трансляции, возникают некоторые отличия, свя-занные с присутствием внешних нереализованных C-функций. Во-первых, компилятор создаст дополни-тельный заголовочный файл (будет называться"func_external.h"), где разместит прототипы реали-зующих версий внешних m-функций, а интерфейснаячасть будет содержаться в главном файле ("func.c").Во-вторых, компилятор не сможет создать исполняе-мый файл ввиду отсутствия реализующей части, что,в общем-то, и не нужно, так как «файл-оболочка»("func_mainhg.c") все-таки будет создан. Однако небудет создана директория "bin" с содержимым, но этотоже не помеха - ее можно взять у любого другогоприложения либо создать вручную, скопировав в неефайлы "FigureMenuBar.fig" и "FigureToolBar.fig", на-ходящиеся в директории "$(MATLAB)\extern\include".К тому же данная директория нужна, если вызывают-ся графические функции MATLAB, но даже и без неепрограмма будет нормально работать, просто не будетфункционировать панель и меню графического окнаMATLAB, а также библиотека будет выдавать преду-преждение об отсутствии файлов ".fig".К форме разбираемого примера добавим такжеCheckBox, регулирующий вывод графика после вычис-лений. Учитывая вышеописанные изменения, рассмот-рим соответствующие изменения в проектных файлах.Файл "func.c" просто подключаем к проекту, ни-коим образом не дополняя и не изменяя его. Затем,если вдруг понадобится внести какие-либо измененияв m-коде, достаточно лишь заново произвести транс-ляцию на язык Си и заменить старый "func.c" на егообновленную версию.// Файл "Unit1.cpp"#include "Unit1.h" /* Содержит описание классаTForm1 и внешнее объявление Form1 */#include "Unit2.h" /* Содержит описание классаTNewThread и внешнее объявление MyThread - объ-екта, инкапсулирующего поток WinAPI */#include "libmatlb.h" /* Содержит прототипыфункций инициализации графики *//* Глобальные переменные, управляющие выво-дом графика и досрочным завершением */double MyCheck = 1, MyExit; /* дополнительногопроцесса *///----------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner){const char *argv = *_argv;/* При создании формы инициализируем графиче-скую библиотеку MATLAB */mlfHGInitialize(&_argc, &argv);}void __fastcall TForm1::Button1Click(TObject*Sender){MyExit = 0;Button1->Enabled = false; CheckBox1->Enabled =false;// Создаем новый поток и сразу запускаем егоMyThread = new TNewThread(false);}void __fastcall TForm1::Button2Click(TObject*Sender){MyExit = 1;}void __fastcall TForm1::CheckBox1Click(TObject*Sender){MyCheck = CheckBox1->Checked;}void __fastcall TForm1::FormClose(TObject *Sender,TCloseAction &Action){ /* Во время завершения работы приложения вы-гружаем графическую библиотеку */mlfHGTerminate();}// Файл "Unit2.cpp"#include "cpph.h" /* Включает содержимое"func_mainhg.h" до функции main и реализациюфункции PrintHandler */#include "func_external.h" /* Включает прототипыспециальных C-функций */extern double MyCheck, MyExit;//--------------------------------------------------------------__fastcall TNewThread::TnewThread (boolCreateSuspended): TThread(CreateSuspended){ /* Устанавливаем, чтобы поток автоматическиуничтожился после завершения */FreeOnTerminate = true;}void __fastcall TNewThread::Execute() /* "Тело" по-тока */{mclLibInitCommon(&_main_info ); /* Инициализи-руем таблицы компилятора */mlfSetPrintHandler(WinPrint); /* Регистрируем об-работчик печати */mlfFunc(); /* Вызываем основную функцию, нахо-дящуюся в "func.c" *//* Ожидаем, пока пользователь не закроет все гра-фические окна, это является необходимым действием,а иначе завершающийся поток их закроет сам. Функ-ция pause не помогает в данной ситуации */mlfHGWaitForFiguresToDie();/* Разрешаем использование ранее отключенныхкнопки "Run" и CheckBox */UpdateMethod(1);}void __fastcall TNewThread::UpdateMethod(intmode){ /* Вызов методов потока, содержащих код, за-щищаемый от конфликтов доступа к компонентамVCL (thread-safe code) */switch (mode){case 0: Synchronize(UpdateForm); break;case 1: Synchronize(EnableButton);}}void __fastcall TNewThread::UpdateForm(){ /* Выводим содержимое буфера Print Handler встроку статуса */Form1->StatusBar1->SimpleText = OutputBuffer;FreeOutput();}void __fastcall TNewThread::EnableButton(){ // Разрешаем использование кнопки и CheckBoxForm1 -> Button1 -> Enabled = true;Form1 -> CheckBox1 -> Enabled = true;}void Mfunc_CloseThread(void){ /* "Тело" реализующей версии m-функцииCloseThread */MyThread -> UpdateMethod(1);EndThread(0);}void Mfunc_SyncVCL(mxArray * MethodNumber){ /* "Тело" реализующей версии m-функцииSyncVCL() */MyThread -> Update-Method(*mxGetPr(MethodNumber));}extern mxArray * Mfunc_AssignValue(int nargout_,mxArray * VarNumber){/* "Тело" реализующей версии m-функцииAssignValue */mxArray *ReturnNumber = NULL;int VarNum;VarNum = (int)*mxGetPr(VarNumber); /* Получаемномер способа присваивания */switch (VarNum){case 0: ReturnNumber = mlfScalar(MyCheck); break;case 1: ReturnNumber = mlfScalar(MyExit);} /* Возвращаем значение типа mxArray, "понят-ного" функциям MATLAB C Math Lib */return ReturnNumber;}Организацию ввода и редактирования исходныхданных можно реализовать следующим образом. Доба-вим массив Mass чисел с плавающей запятой, в кото-рый будем записывать введенные пользователем пара-метры динамической системы. Затем этот массив пере-дадим с помощью функции-заглушки AssignValue ваналогичный массив в m-коде. Фунция AssignValueреализуется таким образом, чтобы по селектору значе-ние переменной определенного типа данных Си преоб-разовывалось в соответствующее значение переменнойMATLAB, но было бы уже представлено типом, кото-рый понимает математическая библиотека MATLAB. ВMATLAB Math Library имеются необходимые для это-го функции: mlfScalar - для скалярных значений,mlfDoubleMatrix - для матриц, значениями которых яв-ляются числа с плавающей запятой.Завершение работы приложения можно произве-сти кнопкой "Exit".Соответствующие обработчики, созданные вручную,представлены в приводимом ниже файле Unit1.cpp.// Файл "Unit1.cpp"#include #pragma hdrstop#include "Unit1.h"#include "Unit2.h"#include "libmatlb.h"//---------------------------------------------------------------#pragma package(smart_init)#pragma resource "*.dfm"double MyCheck = 1, MyExit, Mass[10];TNewThread *MyThread;TForm1 *Form1;//---------------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner){const char *argv = *_argv;mlfHGInitialize(&_argc,&argv);}//---------------------------------------------------------------void __fastcall TForm1::Button1Click(TObject*Sender){for (int i = 0; i < 10; i++)Mass[i] = StringGrid1->Cells[1][i].ToDouble();//Переписываем значения параметров системы в//в дополнительный массивMyExit = 0;Button1->Enabled = false;CheckBox1->Enabled = false;MyThread = new TNewThread(false);}//---------------------------------------------------------------void __fastcall TForm1::Button2Click(TObject*Sender){MyExit = 1;}//---------------------------------------------------------------void __fastcall TForm1::CheckBox1Click(TObject*Sender){MyCheck = CheckBox1->Checked;}//---------------------------------------------------------------void __fastcall TForm1::FormClose(TObject *Sender,TCloseAction &Action){mlfHGTerminate();}//---------------------------------------------------------------void __fastcall TForm1::FormCreate(TObject*Sender){StringGrid1->Cells[0][0]="a";StringGrid1->Cells[0][1]="b";StringGrid1->Cells[0][2]="c";StringGrid1->Cells[0][3]="d";StringGrid1->Cells[0][4]="r";StringGrid1->Cells[0][5]="T0";StringGrid1->Cells[0][6]="Tfin";StringGrid1->Cells[0][7]="y0(1)";StringGrid1->Cells[0][8]="y0(2)";StringGrid1->Cells[0][9]="RelTol";StringGrid1->Cells[1][0]=1;StringGrid1->Cells[1][1]=0.01;StringGrid1->Cells[1][2]=1;StringGrid1->Cells[1][3]=0.02;StringGrid1->Cells[1][4]=0.005;StringGrid1->Cells[1][5]=0;StringGrid1->Cells[1][6]=100;StringGrid1->Cells[1][7]=20;StringGrid1->Cells[1][8]=30;StringGrid1->Cells[1][9]=1E-6;}//---------------------------------------------------------------void __fastcall TForm1::Button3Click(TObject*Sender){Close();}Небольшое дополнение в файле Unit2.cpp пред-ставлено ниже.// Файл "Unit2.cpp"< .. >extern double MyCheck, MyExit, Mass[10];< .. >extern mxArray * Mfunc_AssignValue(int nargout_,mxArray * VarNumber){mxArray *ReturnNumber = NULL;int VarNum;VarNum = (int)*mxGetPr(VarNumber);switch (VarNum){case 0: ReturnNumber = mlfDoubleMatrix(1, 10,Mass, NULL); break;case 1: ReturnNumber = mlfScalar(MyCheck);break;case 2: ReturnNumber = mlfScalar(MyExit);}return ReturnNumber;}MATLAB-программа принимает следующий вид:% Файл "func.m"function mainglobal a b c d r mMyExit Tfin oldpers,ticmMyExit = 0;oldpers = 0;mass = AssignValue(0);a = mass(1); b = mass(2); c = mass(3); d = mass(4);r = mass(5); T0 = mass(6); Tfin = mass(7);y0(1) = mass(8); y0(2) = mass(9);reltol = mass(10);mShowGraph = AssignValue(1);yp = [c/d; (a-r*c/d)/b];opt = odeset('RelTol', reltol);tt = []; yy = [];[tt, yy] = ode45(@lv, [T0, Tfin], y0, opt);tt = tt'; yy = yy';time = toc;if (mShowGraph),figure(1),plot(tt, yy(1, :), 'g', tt, yy(2, :), 'r'), grid on,title('Solution of Modified Lotka-Volterra Equation'),legend('Preys','Predators'),xlabel('Time'), ylabel('Populations'),figure(2),plot(yy(1,:), yy(2,:), 'b', yp(1), yp(2), 'r.'), grid on,title('Phase Trajectory'),legend('Phase Trajectory', 'Rest Point'),xlabel('Preys'), ylabel('Predators')end,fprintf(1, '%3.1f seconds', time);SyncVCL(0);function CloseThread;%#external%--------------------------------------------------------------function SyncVCL(MethodNumber);%#external%--------------------------------------------------------------function OutVar = AssignValue(VarNumber);%#external%--------------------------------------------------------------function z = lv(t, y)global a b c d r mMyExit Tfin oldpers,mMyExit = AssignValue(2);if (mMyExit)CloseThread;end;pers = floor((t / Tfin) * 100);if (pers > oldpers)fprintf(1, '%4.0f%% Complete', pers);SyncVCL(0);oldpers = pers;endz = [(a-b*y(2)-r*y(1))*y(1); (-c+d*y(1))*y(2)];%--------------------------------------------------------------function hh = title(string, varargin)if nargin > 1 & (nargin-1)/2-fix((nargin-1)/2),error('Incorrect number of input arguments')endax = gca;if isappdata(ax,'MWBYPASS_title'),h = mwbypass(ax,'MWBYPASS_title',…string,varargin{:});elseh = get(ax,'title');set(h, 'FontAngle', get(ax, 'FontAngle'), ...'FontName', get(ax, 'FontName'), ...'FontSize', get(ax, 'FontSize'), ...'FontWeight', get(ax, 'FontWeight'), ...'Rotation', 0, ...'string', string, varargin{:});endif nargout > 0hh = h;end%--------------------------------------------------------------function hh = xlabel(string, varargin)if nargin > 1 & (nargin-1)/2-fix((nargin-1)/2),error('Incorrect number of input arguments')endax = gca;if isappdata(ax,'MWBYPASS_xlabel')h = mwbypass(ax,'MWBYPASS_xlabel',…string,varargin{:});elseh = get(ax,'xlabel');set(h, 'FontAngle', get(ax, 'FontAngle'), ...'FontName', get(ax, 'FontName'), ...'FontSize', get(ax, 'FontSize'), ...'FontWeight', get(ax, 'FontWeight'), ...'string', string,varargin{:});endif nargout > 0hh = h;end%--------------------------------------------------------------function hh = ylabel(string, varargin)if nargin > 1 & (nargin-1)/2-fix((nargin-1)/2),error('Incorrect number of input arguments')endax = gca;if isappdata(ax, 'MWBYPASS_ylabel')h = mwbypass(ax, 'MWBYPASS_ylabel',…string, varargin{:});elseh = get(ax, 'ylabel');set(h, 'FontAngle', get(ax, 'FontAngle'), ...'FontName', get(ax, 'FontName'), ...'FontSize', get(ax, 'FontSize'), ...'FontWeight', get(ax, 'FontWeight'), ...'string', string, varargin{:});endif nargout > 0hh = h;end%--------------------------------------------------------------function hh = mwbypass(h,id, varargin)fcn = getappdata(h, id);if nargout > 0if ~iscell(fcn)hh = feval(fcn, varargin{:});elsehh = feval(fcn{:}, varargin{:});endelseif ~iscell(fcn)feval(fcn, varargin{:});elsefeval(fcn{:}, varargin{:});endendВ заключение заметим, что несмотря на очевидныеудобства предлагаемой в данной работе технологиииспользования MATLAB-программ в средах визуаль-ного программирования C/C++, в ней имеется покрайней мере один отрицательный момент: вызов ин-терфейсной функции (например, присваивания) будетосуществляться гораздо медленнее, чем та же опера-ция в Си. Так, из-за постоянного внешнего тестирова-ния переменной MyExit цикл работает в полтора-двараза дольше в используемой конфигурации системы.При возникновении подобной ситуации можно наэтапе выпуска финального релиза программного про-дукта осуществить замену соответствующей опера-ции, если это возможно, на C-эквивалент.
MATLAB C Math Library User's Guide. Revised for Version 2.2 (Release 12.1). 2001. 432 с.
MATLAB C++ Math Library Reference. Revised for Version 2.2 (Release 12.1). 2001. 429 с.
MATLAB Compiler User's Guide. Sixth printing. Revised for Version 3.0 (Release 13). 2002. 274 с.
MATLAB C/C++ Graphics Library User's Guide. Fifth printing. Revised for Version 2.1 (Release 12). 2000. 52 с.
Эрроусмит Д., Плейс К. Обыкновенные дифференциальные уравнения. Качественная теория с приложениями. М.: Мир, 1986. 244 с.