loading

Logout succeed

Logout succeed. See you again!

ebook img

Технология использования MATLAB-программ в средах визуального программирования C/C++ PDF

pages10 Pages
file size0.302 MB
languageRussian

Preview Технология использования MATLAB-программ в средах визуального программирования C/C++

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

See more

The list of books you might like