Вернуться на главную страницу

Часть 3. Программирование на языке С для пакета MATLAB

Глава 12. Программирование МЕХ-функций системы MATLAB на языке С

12.1. Интерфейс МЕХ-функций с системой MATLAB

Когда стоящую перед нами задачу не удается решить с помощью функций, встроенных в систему MATLAB, приходится разрабатывать собственные функции. Разработка ведется на некотором языке программирования. До сих пор нами разрабатывались функции на М-языке -внутреннем языке программирования пакета MATLAB. Код М-функций, как известно, сохраняется в текстовых файлах, имеющих расширением букву m. Именно в таком состоянии эти функции готовы к применению: система MATLAB загружает их в память, преобразует в некоторый промежуточный псевдокод (Р-код), который уже и выполняется далее в режиме интерпретации, когда каждая синтаксически законченная конструкция Р-кода заменяется на соответствующий набор машинных инструкций.

У М-языка имеется масса достоинств. Это наглядный и простой язык высокого уровня (очень далекий от машинного языка), похожий во многих отношениях на самый распространенный в мире язык программирования BASIC. Работа с М-функциями в интерпретируемом режиме (без промежуточной стадии полной компиляции в машинный код) создает быстро реализуемую цепочку "разработка-запуск-отладка (исправление ошибок) - новый запуск" и так далее. М-язык сверхкомпактен по отношению к массивам и операциям над ними: все математические функции работают с массивами так же легко, как и со скалярными величинами. И, наконец, богатейший набор встроенных математических и графических функций завершает перечисление чрезвычайно привлекательных особенностей внутреннего языка пакета MATLAB.

И тем не менее у любого языка программирования всегда имеются недостатки, являющиеся продолжением его достоинств. Высокоуровневый характер М-языка не позволяет реализовывать алгоритмы работы со сверхсложными структурами данных (деревья и ветвящиеся списки). Интерпретируемый характер этого языка, способствующий быстроте разработки, препятствует увеличению быстродействия во время выполнения и так далее. Кроме того, как быть, если некоторая функция уже разработана на другом языке программирования? Переработка на М-язык может оказаться весьма трудоемкой (если вообще возможной), особенно если нет поддержки со стороны оригинальных разработчиков.

Ясно, что любой математический пакет имел бы существенно большую область применимости, если бы он предоставлял возможность вести комплексные разработки, когда разные части прикладной пользовательской программы разрабатываются на разных языках программирования с помощью различных инструментальных средств. Именно таковым и является пакет MATLAB! Oн позволяет разрабатывать отдельные функции на популярнейших компилируемых языках программирования С, C++ и Фортран. Последняя версия пакета MATLAB б добавила к этому списку язык Java. Тем самым уже накопленный фонд программ на этих языках может быть легко подключен к текущему проекту, в котором лишь часть функций должна программироваться на М-языке.

В данном учебном пособии мы будем рассматривать разработку функций только на языке С, предоставив читателю возможность самостоятельно изучить по встроенной системе помощи аналогичные вопросы, касающиеся Фортрана. Ситуация же с языком C++ практически не отличается от рассматриваемой нами в связи с языком С.

Мы здесь выбрали язык С как наиболее яркий антипод М-языку. Язык программирования С является наиболее низкоуровневым (близким по смыслу к машинным инструкциям) и тем самым самым гибким в вариантах его применения и самым эффективным в смысле быстродействия и экономии использовании памяти компьютера. Безусловно, этот язык явно не тянет па чемпиона в легкости его изучения и применения. Однако являясь самым распространенным языком системного программирования и будучи хорошо переносимым между разными программными и аппаратными платформами, он является чемпионом в номинации "Программирование без ограничений" (Programming Unlimited)!

В рамках терминологии пакета MATLAB все функции, разработанные на внешнем по отношению к пакету MATLAB языке программирования, принято называть МЕХ-функциями (М - это Matlab, a ЕХ - это External, то есть "внешний").

Еще раз повторим, что мы будем разрабатывать МЕХ-функции только на языке программирования С, оставив аналогичные вопросы по языкам Фортран и C++ для самостоятельного изучения читателями по встроенной в пакет MATLAB системе помощи. Кроме того, предполагается, что читатель знаком с языком программирования С и средством разработки Microsoft Visual C++ (версий 5 ИЛИ 6).

На платформе Windows готовые к применению в рамках пакета MATLAB МЕХ-функции представляют из себя скомпилированный машинный код, погруженный в файлы динамических библиотек (Dynamic Link Libraries; эти файлы имеют расширение .dll). Одна МЕХ-функция соответствует одному файлу динамической библиотеки. При этом имя МЕХ-функции, используемое для ее вызова в выражениях системы MATLAB, совпадает с именем файла динамической библиотеки.

Например, если мы в командном окне системы MATLAB записываем выражение

res = А .* В + MyMexFunctionl( А + В );

где MyMexFunctionl - имя МЕХ-функции, то на диске компьютера в любом известном пакету MATLAB каталоге должен находиться бинарный файл с машинными инструкциями MyMexFunction1.dll, разработанный и скомпилированный в среде Microsoft Visual C++. Именно по имени МЕХ-функции пакет MATLAB ищет необходимый файл, загружает его в память компьютера в свое адресное пространство, после чего содержимое этого файла становится продолжением "родного машинного кода" пакета MATLAB. В этом смысле МЕХ-функции являются бинарными расширениями кода пакета MATLAB, в то время как М-функции являются его текстовыми расширениями.

Если в одном и том же каталоге встречаются как МЕХ- так и М-функции с одинаковым именем, то интерпретатор пакета MATLAB отдаст предпочтение МЕХ-функции, в то время как команда help выбирает М-функцию. Это позволяет документировать информацию по МЕХ-функции в содержимом одноименной М-функции.

Теперь начнем рассматривать вопросы, связанные с практическими аспектами разработки МЕХ-функции на языке программирования С. Как реально скомпилировать файл динамической библиотеки в среде компилятора Microsoft Visual C++ будет рассказано в следующем параграфе.

Сейчас же сосредоточимся на необходимых фрагментах исходного С-кода.

Для обеспечения возможности взаимодействия ядра системы MATLAB с МЕХ-функцией, расположенной в динамической библиотеке, последняя с точки зрения языка С должна представлять из себя совокупность С-функций, одна из которых имеет фиксированное имя mexFunction (здесь нельзя менять даже регистр букв), заданный набор входных параметров и заданное возвращаемое значение.

Итак, повторим еще раз. После компиляции МЕХ-функция является файлом с расширением .dll. Имя этого файла и определяет имя МЕХ-функции, которая вызывается по этому имени из командного окна или из М-функций пакета MATLAB. С точки зрения системы MATLAB такая МЕХ-функция полностью определяется одним своим именем и представляет из себя некоторую неделимую программную единицу. В то же время на этапе разработки МЕХ-функция представляет из себя С-код, состоящий из любого числа С-функций, из которых заданным прототипом обладает только одна функция - функция mexFunction.

Именно эту функцию и экспортирует динамическая библиотека, то есть имя функции mexFunction и се относительный адрес внутри библиотеки могут быть прочитаны загрузчиком операционной системы. Именно эту функцию и вызывает на самом деле пакет MATLAB в ответ на требование вычислить МЕХ-функцию. Фактическое имя МЕХ-функции требуется лишь для поиска и загрузки содержимого файла динамической библиотеки.

Окончательно подытоживаем сказанное: имя МЕХ-функции совпадает с именем файла динамической библиотеки. Это произвольное имя, выбираемое разработчиком по своему вкусу. Точка входа в динамической библиотеке имеет фиксированное имя - mexFunction. Это имя всегда одно и то же, и его менять нельзя.

Ясно, что при разработке МЕХ-функций особая роль принадлежит С-функции mexFunction. Это роль интерфейса между внутренним миром пакета MATLAB и внутренним устройством программ на языке С. Функция mexFunction принимает входные данные от пакета MATLAB. Она же и возвращает ему результаты работы.

Самый минимальный С-код проекта по созданию МЕХ-функции пакета MATLAB содержит единственную функцию mexFunction. Мы сейчас для простоты ограничимся этим случаем, причем в теле функции mexFunction не будем выполнять вообще никаких действий. Таким образом мы реализуем пустую (в смысле полезных действий) МЕХ-функцию, которую так и назовем - MyEmptyMexFunction, что в переводе значит "моя пустая функция".

Вот этот минимальный С-код:

#include "mex.h"
void mexFunction(	int		nOut,
			mxArray*	pOut[],
			int		nIn,
			const mxArray*	pIn[] )
{
}

из которого четко виден заданный прототип интерфейсной функции mexFunction. Здесь нужно дать пояснения по типу данных mxArray. Это С-структура, определенная в файле mex.h, поставляемом вместе с пакетом MATLAB и располагающемся в каталоге \matlabrl2\extern\include, где matlabr12 - это основной каталог системы MATLAB 6.

Любой массив системы MATLAB представим в виде структуры mxArray. Отдельные поля этой структуры хранят информацию о типе массива, его размерности и размерах. Есть и поле, указывающее на область памяти с собственно данными (элементами массива, которые хранятся линейно упорядоченными по столбцам).

Параметры функции mexFunction относятся как к входным параметрам вызываемой из пакета MATLAB МЕХ-функции (они помечены суффиксом In), так и к возвращаемым этой функцией значениям (эти параметры помечены суффиксом out). Так как наша "пустая" функция не будет принимать никаких входных параметров и не будет возвращать никаких результатов, то нам в теле функции ничего не надо с ними делать. Если мы вызовем эту нашу МЕХ-функцию из командного окна системы MATLAB (или из некоторой М-функции) с правильным синтаксисом, то есть без входных параметров и возвращаемых значений, то на входе mexFunction будут следующие значения ее входных параметров:

nOut = 0
pOut = NULL
nIn = 0
pIn = NULL

Разработанную таким образом пустую функцию мы будем использовать в качестве тренировочного материала для прослеживания всего процесса разработки и вызова МЕХ-функции. Такую МЕХ-функцию действительно можно будет вызвать из среды пакета MATLAB, и этот вызов отработает нормально без возникновения ошибочных результатов. Состоять этот вызов будет в передаче управления бинарному коду МЕХ-функции, после чего этот код тут же вернет управление (так как он не содержит никаких полезных действий) в точку вызова.

Теперь пора перейти к вопросам, связанным с компиляцией этой пустой МЕХ-функции (точнее с компиляцией соответствующего ей С-кода) и получением файла динамической библиотеки. Для этого мы воспользуемся компилятором Microsoft Visual C++.

12.2. Создание и компиляция DLL-проекта в среде компилятора Microsoft Visual C++

Работа в среде компилятора Microsoft Visual C++ начинается с создания проекта соответствующего типа. В нашем случае мы должны создать проект типа "Win32 Dynamic-Link Library" и поместить его в каталог с именем MyEmptyMexFunction. To есть имя каталога, в котором будут располагаться файлы проекта, по умолчанию совпадает с именем целевого файла динамической библиотеки.

Ниже на рисунке показано диалоговое окно New оболочки Microsoft Developer Studio (это графическая оболочка компилятора Microsoft Visual C++), в котором следует выбрать тип проекта и имя каталога (см. рис. 12.1).

Далее добавляем в проект файл main.с, содержащий пустую функцию mexFunction, описанную в предыдущем параграфе, а также файл MyEmptyMexFunction.def. Последний файл содержит всего лишь одну строку, сообщающую компилятору сведения об экспортируемых из динамической библиотеки функциях (см. рис. 12.2):

Теперь нужно вручную настроить некоторые параметры проекта, используя команду меню Project | Settings..., в результате чего появляется диалоговое окно (см. рис. 12.3) в котором при активной странице C/C++ в комбинированном списке Category нужно выбрать позицию Preprocessor, после чего в редактируемой строке Additional include directories нужно прописать путь к каталогу, в котором хранится файл mex.h. Этим каталогом является подкаталог \extern\include главного каталога пакета MATLAB.

Далее в том же самом диалоговом окне Project settings переходим на страницу Link и в конец редактируемой строки object/library modules добавляем (к имеющемуся уже содержимому) надпись matlab.lib. Завершаем работу с диалоговым окном нажатием кнопки ОК. Теперь проект настроен полностью. Не хватает только наличия указанного выше файла matlab.lib в корневом каталоге нашего проекта, то есть в каталоге MyEmptyMexFunction. Очевидно, что такой файл должен быть скопирован в указанный каталог. Незадача же в том, что файл matlab.lib не входит в штатную поставку пакета MATLAB (из соображений экономии дискового пространства). Нам придется изготовить этот файл самим, после чего мы будем его всегда копировать в главные каталоги всех наших будущих проектов по созданию МЕХ-функций.

Чтобы изготовить файл matlab.lib, вызовем утилиту командной строки (это MS-DOS Prompt на платформе Win9x или cmd.exe на платформах Windows NT/2000/XP) и перейдем в подкаталог \extern\include главного каталога пакета MATLAB, после чего введем команду, показанную на рисунке 12.4:

Этого достаточно, чтобы в подкаталоге \extern\include появился необходимый нам файл matlab.lib. Копируем его в каталог нашего проекта MyEmptyMexFunction и начинаем компилировать проект нажатием клавиши Р7 (или командой меню Build | Build MyEmptyMexFunction.dll). Если не было допущено никаких отклонений от представленных нами описаний всех действий (то есть не допущено ошибок), то проект успешно скомпилируется и в каталоге MyEmptyMexFunction\Debug появится файл MyEmptyMexFunction.dll. Теперь нужно этот файл скопировать в любой каталог, путь к которому прописан в списке путей доступа пакета MATLAB (или добавить каталог MyEmptyMexFunction\Debug в список доступа). После чего мы можем из командного окна вызвать разработанную нами МЕХ-функцию MyEmptyMexFunction (см. рис. 12.5):

Отсутствие сообщений об ошибках свидетельствует о том, что система MATLAB успешно нашла файл с указанной функцией и выполнила ее. Так как эта функция ничего не делает, то никаких результатов при этом не получается. Однако, на примере такой пустой МЕХ-функций мы смогли, не отвлекаясь на другие детали, проследить весь процесс изготовления МЕХ-функций.

12.3. Вызов функций MATLAB API

Сейчас сосредоточимся на содержательной стороне работы МЕХ-функций. Основной задачей интерфейсной функции mexFunction является прием данных, пришедших из системы MATLAB, а также формирование правильных структур данных, которые можно будет возвратить назад в систему MATLAB. Обмен данными происходит через параметры функции mexFunction.

Поэксплуатируем еще немного ранее созданную пустую МЕХ-функцию MyEmptyMexFunction. На ее примере покажем, какие параметры получает внутренняя интерфейсная mexFunction в случае разных вариантов вызова МЕХ-функцни из системы MATLAB. Мы ранее говорили, что все эти параметры нулевые при вызове MyEmptyMexFunction без входных параметров и без возвращаемых значений, то есть при том варианте вызова, который показан на рисунке 12.5.

Теперь рассмотрим такой вызов этой функции (см. рис. 12.6), при котором вызывающий передает функции MyEmptyMexFunction три входных параметра, а также надеется получить три возвращаемых значения (переменные r, s и t). Так как наша функция реально не возвращает никаких значений, то система MATLAB выдает в командное окно соответствующее предупреждение (оно предназначено для информирования пользователя о том, что он напрасно надеется получить от данной функции какие-то результаты).

В этом случае при входе в функцию mexFunction параметры nOut и nIn оба равны трем. Указатель pIn указывает на область памяти, содержащую три ненулевых указателя на структуры типа mxArray, то есть на массивы пакета MATLAB (напоминаем, что все типы данных системы MATLAB являются массивами того или иного рода). Конкретно, первый указатель из этой области - указатель pIn[0] указывает в нашем случае на массив А, следующий указатель - pIn[1] указывает на безымянный массив из одного элемента, равного 5. Наконец, последний указатель и этого блока памяти - pIn[2] указывает на массив в. Подытоживаем сказанное: все четыре указателя - pIn, pIn[0] (это то же самое, что и *pIn), pIn[i] и pIn[2] не равны NULL. Они все указывают на предварительно выделенные для дальнейшей работы блоки памяти. Мы теперь можем через эти указатели подбираться к входным массивам, пришедшим из пакета MATLAB.

Разобравшись со входными указателями, переходим к выходным указателям. Указатель pout теперь указывает на блок памяти, хранящий три указателя на mxArray. Но все три последних равны NULL, так как именно мы сами внутри mexFunction должны выделить необходимую память под выходные массивы.

Все сказанное про параметры функции mexFunction чрезвычайно важно, поэтому сразу же закрепим полученные сведения на конкретном практическом примере. Для этого разработаем МЕХ-функцию MyMexF2, которая работает с одним входным параметром (и игнорирует все остальные входные параметры, если пользователь функции удосужится их поставить). Эта функция возвращает единицу, если входной параметр не имеет тип double, и возвращает удвоенную величину первого параметра в противном случае. Ясно, что это не очень интересная с прикладной точки зрения функция, но нам она нужна в учебных целях для закрепления сведений о входных параметрах функции mexFunction.

Чтобы МЕХ-функция могла удобно работать с типами данных пакета MATLAB, а также выдавать сообщения в командное окно системы MATLAB (и выполнять ее команды и функции), в пакете MATLAB штатно присутствует набор специальных функций, которые с указанными целями можно вызывать из С-кода разрабатываемых МЕХ-функций. Такие функции в совокупности составляют функциональный набор, который Принято называть MATLAB API (Application Program Interface). Мы будем постепенно знакомиться с конкретными функциями MATLAB API. Для выполнения из МЕХ-функций любых действий, направленных в саму систему MATLAB, предназначены функции с префиксом (приставкой) тех, а для разбора структур данных пакета MATLAB предназначены функции с префиксом mx.

Вот С-текст для построения МЕХ-функций MyMexF2 (это же имя должен иметь проект Microsoft Developer Studio для получения файла MyMexF2.dll), в котором используется ряд функций MATLAB API:

#include "mex.h"
//Prototype:
void MyDouble(int,int,double*,double*);
//	 Main interface function:
void mexFunction(	int		nOut,
			mxArray*	pOut[],
			int		nIn,
			const mxArray* 	pIn[] )
{
	int m, n; double *pl, *pO;
	if( nIn == 0 ) mexErrMsgTxt("Input required"); if( nOut > 0 )
	{
		if( !mxIsDouble(pIn[0]) )
		{
			pOut[0]=mxCreateDoubleMatrix(1,1,mxREAL); *(mxGetPr(pOut[0])) - 1;
		}
		else
		{
			m = mxGetM( pIn[0] ); n = mxGetN( pIn[0] );
			pOut[0]=mxCreateDoubleMatrix{m,n,mxREAL);
			pI = mxGetPr( pIn[0] );
			pO = mxGetPr( pOut[0] );
			MyDouble( m, n, pI, p0 );
		}
	}
}
	//——- Our own auxiliary function:
void MyDouble( int m, int n, double* pI, double* pO )
{
	int i;
	for( i=0; i < n*m; ++i ) pO[i] = 2 * pI[i];
}

В представленном С-коде вызываются следующие функции MATLAB API: mexErrMsgTxt, mxIsDouble, mxCreateDoubleMatrix, mxGetPr, mxGetM И mxGetN. Первая из перечисленных функций имеет префикс mex и направлена на выполнение некоторого действия в самой системе MATLAB. Конкретно функция mexErrMsgTxt выдает в командное окно системы MATLAB сообщение об ошибке, заключающейся в отсутствии входных параметров (наша функция должна вызываться минимум с одним входным параметром), и корректно завершает выполнение нашей МЕХ-функции. Остальные функции имею префикс mx и направлены, как мы уже знаем, на работу со структурой mxArray - основным типом данных пакета MATLAB (это есть внутреннее представление любых массивов системы MATLAB).

У функции mxIsDouble один входной параметр - он является указателем на структуру mxArray, то есть указателем на массив системы MATLAB. Данная функция возвращает единицу (истину в смысле языка программирования С), если массив имеет тип double, и возвращает нуль (логическая ложь) в противном случае. Функции mxGetM и mxGetN принимают по одному такому же параметру (указателю на структуру mxArray). Первая из них возвращает число строк во входной матрице, а вторая функция возвращает число столбцов.

Функция mxCreateDoubieMatrix выделяет память под структуру mxArray, соответствующую числовой матрице с размерами, которые указываются через ее первый и второй параметры. Третий параметр является условным числовым флагом, сигнализирующим о создании чисто вещественной или комплексной числовой матрицы. Флаг mxREAL заставляет функцию mxCreateDoubleMatrix создавать вещественную числовую матрицу. При этом следует четко понимать, что функция mxCreateDoubleMatrix не заполняет созданную матрицу числовыми элементами. Это нужно выполнять в виде отдельной работы. В частности, сначала нужно получить доступ к собственно данным матрицы, то есть к ее элементам. Такую операцию получения доступа к элементам массивов системы MATLAB выполняет функция mxGetPr. Эта функция возвращает указатель на область памяти, в которой в линейно упорядоченной форме (по столбцам) хранятся все элементы массива. Конкретно функция mxGetPr возвращает указатель на действительные составляющие в общем случае комплексных элементов. Указатель на мнимые части элементов возвращается функцией mxGetPi. Действительные и мнимые части элементов хранятся порознь в виде отдельных блоков памяти. В каждом из этих блоков данные упорядочены линейно по столбцам. Так как мы создаем вещественные матрицы, то нам достаточно работать только с функцией mxGetPr.

Теперь уже можно объяснить работу нашей МЕХ-функции. Сначала в теле интерфейсной функции mexFunction мы проверяем количество входных параметров, с которыми вызвана из системы MATLAB наша МЕХ-функция. Если она вызвана вообще без параметров, то выдаем в командное окно системы MATLAB сообщение об ошибке (Input required - Требуются входные параметры) и завершаем работу. Все это делается с помощью MATLAB API функции mexErrMsgTxt. Дальнейшая работа выполняется только в случае наличия одного или большего числа входных параметров. Мы договорились, что наша функция все входные параметры, кроме первого, будет игнорировать (при этом не будет возникать никаких ошибочных ситуаций). Первый же входной параметр она будет тестировать на его тип. Если этот параметр не является массивом типа double, то наша МЕХ-функция просто возвращает единицу. Вот как достигается эта цель:

pOut[0]=mxCreateDoubleMatrix(1,1/mxREAL); *(mxGetPr(pOut[0])) = 1;

Мы создаем с помощью функции mxCreateDoubleMatrix чисто вещественную матрицу (флаг mxREAL) размером 1x1. Подбираемся к ее единственному элементу с помощью функции mxGetPr, которая возвращает указатель типа double* на этот элемент. По этому указателю мы и прописываем требуемую единицу. Ничего больше для возврата этого единственного значения делать не нужно. Интерфейсная функция mexFunction, заканчивая свою работу, всегда возвращает результаты системе MATLAB через указатели pOut[0], pOut[1] (если он был задействован) и так далее.

В случае же, когда первый входной параметр является вещественной числовой матрицей, мы создаем структуру mxArray такого же размера, после чего в отдельной С-функции MyDouble прописываем все ее элементы значениями, в два раза превосходящими соответствующие элементы входной матрицы. Чем и достигаем поставленной цели.

Окончательно создаем в Microsoft Developer Studio проект MyMexF2, помещаем в него представленный выше С-текст, настраиваем проект так, как было рассказано в предыдущем параграфе, компилируем его и получаем файл динамической библиотеки MyMexF2.dll. Помещаем этот файл в любой каталог, доступный пакету MATLAB, и начинаем вызывать МЕХ-функцию мумвхР2 в самых разных конфигурациях (задавая различное количество и тип входных переменных и выходных значений). Вот некоторые результаты (см. рис. 12.7):

Здесь мы передали созданной нами МЕХ-функции MyMexF2 два входных параметра, из которых первый является вещественной числовой матрицей 2x3. Наша функция отработала штатно и вернула массив такой же структуры, но с удвоенными элементами.

А вот вызов функции MyMexF2 со строковым входным аргументом:

str = 'Hello, MEX-function'; res = MyMexF2( str );
res =

1

Видно, что созданная нами функция распознала ситуацию, когда первый параметр не является числовым массивом, и вернула единицу в числовом массиве res, размер которого равен 1x1 (см. рис. 12.8):

Ситуация с вызовом функции MyMexF2, когда ее аргументом (параметром) будет массив ячеек или структур, абсолютно очевидна: функция вернет единицу. Но вот что будет, если вызвать эту функцию с числовым массивом размерности три? Попробуем это сделать (хотя про себя сразу отметим, что мы разрабатывали эту функцию и расчете на двумерные числовые массивы - матрицы):

W(:,:,1)=[1,2;3,4]; W(:,:,2)=[5,6;7,8])
res = МуМexF2( W ); res =
2 4 10 12
б 8 14 16

Видно, что функция "переварила" эту ситуацию и "не сломалась", но в любом случае тут не получается возврата той же самой структуры, что и входной параметр. Если бы мы сразу вспомнили при разработке функции MyMexF2 про многомерные числовые массивы, то их можно было бы обрабатывать штатно по некоторой схеме. А без этого остается надеяться, что по крайней мере не произойдет аварийного завершения работы функции.

Это одна из причин, которые могут вызывать ошибочные ситуации при выполнении кода МЕХ-функций. Существуют также многие другие причины, по которым разрабатываемые МЕХ-функции могут работать не так, как ожидается. В таком случае нужно проводить отладку этих функций.

12.4. Отладка МЕХ-функций

Отладка МЕХ-функций, понимаемая как процесс исправления ошибок, может заключаться в чем угодно, хоть в многократном визуальном изучении текста функции с целью обнаружить в нем логические и синтаксические ошибки. Однако в технике программирования под отладкой чаще всего понимают некоторые автоматизированные методики. Самым удобным приемом является прием приостановки выполнения кода функции с изучением значений переменных в этот момент времени.

Как это делается в случае М-функций, мы уже изучали. Там мы опирались на возможности Редактора/Отладчика, входящего в состав пакета MATLAB. МЕХ-функций являются чужеродным элементом для системы MATLAB. Эта система лишь взаимодействует с такими функциями, но не берет на себя управление их выполнением. Поэтому и отладка этих функций выполняется не средствами пакета MATLAB, а средствами оболочки Microsoft Developer Studio. Вот как это делается.

Нужно в Developer Studio открыть соответствующий проект и в тексте интерфейсной функции mexFunction поставить напротив некоторой строки С-кода так называемую метку точки останова (Breakpoint). Это делается при помощи клавиши F9, после чего напротив выбранной строки появляется красный кружок, индицирующий наличие точки останова именно в этой строке.

Далее нужно выполнить некоторые дополнительные настройки в Project | Settings, показанные на рисунке 12.9:

Нужно на странице Debug в редактируемом поле Executable for debug session прописать путь к исполняемому модулю системы MATLAB, коим является файл matlab.exe.

После этого нужно нажать клавишу F5, означающую работу с открытым проектом в режиме отладки. Но так как наш проект представляет собой библиотеку (а не самостоятельное приложение), то сначала загружается основной программный модуль, который и указывается как Executable for debug session. В нашем случае это сама система MATLAB, то есть исполняемый файл matlab.exe. После запуска системы MATLAB из его командного окна мы вызываем нашу МЕХ-функцию (см. рис. 12.10):

(показан пример, когда в качестве параметра передается трехмерный числовой массив), и тут-то и происходит останов на той строке исходного С-кода, где мы ранее поставили точку останова (Breakpoint). Целесообразно ставить ее на одну из первых строк интерфейсной функции mexFunction, так как в самом начале следует проверить, с какими входными параметрами она вызвана.

На рисунке 12.11 видна точка останова, а также видно, как происходит просмотр значений переменных при наведении на них курсора мыши (появляется маленькое всплывающее окно со значением переменной).

Теперь мы можем буквально собственными глазами увидеть, что же на самом деле происходит, когда нашей МЕХ-функции передается в качестве параметра трехмерный массив. Для этого ставим точку останова на строке, где вычисляются размеры "матрицы" тип. Выполняем эту строку нажатием клавиши F10 (пошаговое исполнение инструкций исходного С-кода) и смотрим значения переменных шип. Для матриц го является количеством строк, и в данном случае для этой переменной получается значение 2, что совпадает с количеством строк в обеих страницах входного трехмерного массива. Для переменной же n, которая у матриц равна количеству столбцов, получается значение, равное 4. Теперь ясно, что после этого создается новая матрица 2x4, которая и заполняется удвоенными элементами входного массива. Работа нашей МЕХ-функции в случае входного трехмерного массива полностью ясна.

Если мы хотим добавить к штатному поведению нашей МЕХ-фуикции корректную обработку многомерных числовых массивов, то вместо функций mxGetM и mxGetN нужно использовать MATLAB API функции mxGetNumberOfDimensions И mxGetDimensions. Первая ИЗ ЭТИХ функций возвращает количество измерений массива, а вторая функция возвращает массив размеров вдоль каждого из измерений. Например, следующий код

int N;
int* pd;
num = mxGetNumberOfDimensions( pIn[0] );
pd = mxGetDimeneione( pIn[0] );
pOut[0]=mxCreateNumericArray(N,pd,mxDOUBLE_CLASS,mxREAL);

в рассмотренном нами для примера конкретном случае трехмерного массива w приведет к созданию трехмерного выходного массива размером 2x2x2, который далее можно заполнить удвоенными значениями элементов входного массива.

Как бы тщательно не разрабатывались МЕХ-функции, этап отладки остается практически неизбежным. Поэтому овладение мастерством отладки необходимо для приобретения высокой квалификации. Такие приемы, как просмотр не только имеющихся в С-коде переменных, но и произвольных выражений с ними (с помощью диалогового окна, вызываемого клавишами Shift-F9), простановка условных точек останова и другие приемы, известные опытным программистам, работающим с компилятором Visual C++, пригодятся и при разработке МЕХ-функций для пакета MATLAB.

12.5. Примеры конкретных разработок МЕХ-функций

Освоив некоторое количество MATLAB API функций, можно перейти к рассмотрению конкретных примеров МЕХ-функций, работающих с разного рода массивами пакета MATLAB.

Начнем с простого случая, когда разрабатываемая МЕХ-функция. должна принять на входе два числовых скаляра, которые будут использоваться как размеры матрицы, создающейся внутри этой МЕХ-функций и отправляемой назад в систему MATLAB.

Этот пример очень простой, но он иллюстрирует новые для нас MATLAB API функции обработки скаляров. Кроме того, он демонстрирует некоторую защиту, связанную с проверкой входных параметров, а также еще раз показывает необязательный, но логически понятный стиль программирования, когда внутри интерфейсной функции mexFunction выполняется вся работа, связанная с приемом и переработкой специфических для системы MATLAB данных (тип mxArray), а все остальные С-функции проекта ничего такого не знают. Последние даже выглядят абсолютно традиционно для С-фуикций, не знакомых с пакетом MATLAB. Более того, эти функции могли быть разработаны ранее и по другому поводу, и только сейчас они подключаются для работы в составе пакета MATLAB.

Итак, в нашей МЕХ-функций мы будем создавать числовую матрицу заданного размера MxN и будем заполнять ее некоторыми числовыми значениями (в возрастающем порядке в смысле упорядочения по столбцам). Вот исходный С-код проекта, по-прежнему расположенный пределах единственного файла main.с (часто все функции, кроме интерфейсной функции mexPunction, выносятся в отдельные файлы проекта):

#include "mex.h"
//Prototypes:
void SetData( double*,int,int );
////////////////////////////////////////////////////////
void mexFunction(	int		nOut,
			mxArray*	pOut[],
			int		nIn,
			const mxArray*	pIn[] )
{
	// We ignore extra input parameters 1 int M, N;
	double* pElem;
	if( (mxGetN( pIn[0] )	!=1) ||
	(mxGetM( pIn[0]	) !=1) ||
	(mxGetN( pIn[l]	) != 1) ||
	(mxGetM( pIn[l]	) != 1) )

	mexErrMsgTxt("Only scalars must be !");
	M = (int)mxGetScalar( pIn[0] ); N = (int)mxGetScalar( pIn[l] );
	pOut[0] = mxCreateDoubleMatrix( M, N, mxREAL ); pElem = mxGetPr( pOut[0] );
	SetData( pElem, M, N );
}
///////////////////////////////////////////////////////
void SetData( double* p, int m, int n )
{
	int i;
	for( i=0; i < m*n; ++ i )
	{
	*p= (double)i;
	p++;
	}
} //////////////—the end of the code—////////////////

Мы знаем, что по существу скалярные числовые величины в системе MATLAB являются матрицами 1x1 и внутренне представимы структурами типа mxArray. Поэтому единственный числовой элемент этих массивов еще нужно извлечь из структуры mxArray, прежде чем можно будет его использовать. Это выполняется с помощью MATLAB API функции mxGetScalar. Именно эту функцию мы и применили в представленном выше коде.

После этого мы создаем с помощью функции mxCreateDoubleMatrix числовую матрицу нужного размера, а с помощью функции mxGetPr получаем указатель типа double* на выделенный при этом блок памяти, предназначенный для хранения числовых значений элементов матрицы. Заполняем же этот блок памяти некоторыми конкретными значениями элементов мы в отдельной функции - в функции SetData. Эта отдельная С-функция выглядит абсолютно традиционно для С-функций, не знакомых с системой MATLAB. Это объясняется тем, что в ней отсутствуют вызовы MATLAB API функций и нет никакой работы со структурой mxArray.

Ранее мы подробно рассказывали о том, что нужно сделать, чтобы превратить исходный С-код в целевой файл динамической библиотеки, представляющей собой МЕХ-функцию пакета MATLAB. Поэтому мы не будем это повторять, а только сообщим, что именуем проект (рабочий каталог) в Microsoft Developer Studio как SetMatrix. Это значит, что целевая МЕХ-функция будет иметь такое же имя. Ниже показан пример вызова этой функции из командного окна системы MATLAB:

A=SetMatrix( 3, 4 ); A=
0 3 6 9
1 4 7 10
2 5 8 11

Итак, МЕХ-функция SetMatrix создаст числовую матрицу заданного размера и заполняет ее элементы целыми значениями от нуля и выше.

Теперь рассмотрим другой проект, в котором реализуем более осмысленную задачу о вычислении средних арифметических значений строк числовых матриц. Назовем такую МЕХ-функцию AverVector, поскольку она возвращает вектор-столбец средних по строкам входной матрицы значений (Aver - сокращение от average, то есть средний).

Продемонстрируем сначала весь С-код этого проекта, после чего обсудим некоторые детали:

#include "mex.h"
// Prototype:
void Aver( double*,double*,int,int);
//////////////////////////////////////////////////////
void mexFunction(	int		nOut,
			mxArray*	pOut[],
			int		nIn,
			const mxArray*	pIn[] )
{
	double* pDataIn; double* pDataOut;
	int M, N;

	if( nIn !=1 ) mexErrMsgTxt( "One input required." );
	if( !mxIsDouble(pIn[0]) || mxIsComplex(pIn[0]) )
		mexErrMsgTxt( "Input must be a noncomplex double" "matrix only." );
	M = mxGetM( pIn[0] );
	N = mxGetN( pIn[0] );
	pOut[0] = mxCreateDoubleMatrix( M, 1, mxREAL );
	pDataln = mxGetPr( pIn[0] ); pDataOut = mxGetPr( pOut[0] );
	Aver( pDataln, pDataOut, M, N );
}
//////////////////////////////////////////////////////
void Aver( double* pIn, double* pout, int m, int n )
{
	int i, j; double sum;
	for( i = 0; i < m; i++ )
	{
		sum = 0;
		for( j=0; j < n; j++ )
		sum = sum + *( pin + i + j * m );
		*( pout + i ) = sum / ((double)n);
	}
} ////////////---the end of the code—/////////////////

Здесь функция mxIsDouble возвращает логическую истину только тогда, когда входная матрица имеет тип double, а функция mxIsCompiex дополнительно требует для этого, чтобы числовая матрица имела комплексные значения. Поэтому входную проверку проходит и далее поступает на обработку только входная числовая матрица с действительными элементами.

Сама по себе представленная обработка входной матрицы, когда вычисляются средние арифметические значения по ее строкам, а результаты записываются в элементы выходного вектор-столбца, весьма проста и не требует дополнительных комментариев за исключением оговорки, что язык С сам по себе достаточно сложен в таком компоненте, как активное использование указателей, что мы и делаем в С-функции Aver. Но с этим ничего нельзя поделать: сложности при изучении языка С оборачиваются преимуществами в работе получающихся с его помощью программ.

А вот и пример использования МЕХ-функции AverVector (рис. 12.12) из которого видно, что разработанная нами МЕХ-функция AverVector справляется с возложенными на нее обязанностями.

Перейдем теперь от разработок МЕХ-функций, работающих с массивами типа double, к функциям обработки символьных данных. Разработаем МЕХ-функцию RevString, которая будет выворачивать содержимое входной строки наоборот (последний символ станет первым, предпоследний - вторым и так далее).

Большой разницы в коде МЕХ-функций, работающих с текстовыми строками, по сравнению с МЕХ-функциями, обрабатывающими вещественные числа, нет. Тем не менее для данного проекта нам потребуется ряд дополнительных MATLAB API функций, которые мы еще не изучали. Приведем весь текст проекта, а потом обсудим использованные в нем новшества:

#include "mex.h"
//////////////////////////////////////////////////////
void Reverse( char* inStr, int len, char* outStr )
{
	int i;
	for( i=0; i < len-1; i++ )
	*( outStr + i ) = *( inStr + len - i - 2 );
}
///////////////////////////////////////////////////////
void mexFunction(	int		nOut,
			mxArray*	pOut[],
			int		nIn,
			const mxArray*	pIn[] )
{
	char* StringIn;
	char* StringOut; int len, status;
	//	 Input and output checking:
	if(nIn!=l) mexErrMsgTxt("One input required."); else if(nOut > 1)
	mexErrMsgTxt("Too many output arguments.");
	if( mxIsChar( pIn[0]) 1= 1 )
	mexErrMsgTxt("Input must be a string.");
	if(mxGetM( pln[0])!=1 )
	mexErrMsgTxt("Input must be a row vector.");
	//	 Allocate buffers for strings:
	len = mxGetN(pIn[0]) + 1;
	StringIn = mxCalloc( len, sizeof(char) );
	StringOut = mxCalloc( len, sizeof(char) );
	status = mxGetString( pIn[0], StringIn, len );
	if(status != 0) mexWarnMsgTxt("Not enough space for string.");

	Reverse( StringIn, len, StringOut );
	pOut[0] = mxCreateString( StringOut );
}
////////////	the end of the code—///////////

Ну, во-первых, в данном случае мы текст внутренней (неэкспортируемой из динамической библиотеки) функции Reverse поместили перед текстом интерфейсной функции mexFunction, что не представляет из себя ничего необычного, но тем не менее позволяет обойтись без прототипа этой функции (что всегда имело место в предыдущих примерах). В самой функции Reverse имеет место типичное для языка С тотальное применение указателей для работы с символьными массивами (в терминологии языка С это массивы типа char).

Входная текстовая строка извлекается из массива mxArray с помощью MATLAB API функции mxGetstring, которая помещает эту строку в заранее подготовленный буфер, память для которого выделяется другой MATLAB API функцией - функцией mxCalloc. В случае недостаточного размера буфера мы выдаем в командное окно системы MATLAB предупреждающее сообщение с помощью функции mexWarnMsgTxt.

Наконец, выходное значение данной МЕХ-функции, которое является массивом системы MATLAB типа char, создается с помощью MATLAB API функции mxCreateString, аргументом для которой служит массив языка С типа char, в котором располагаются выходные символы.

Для проверки работы полученной МЕХ-функции RevString подадим ей на вход строку 'Hello, world', и вот во что она ее превращает (см. рис. 12.13):

В завершении цепи примеров рассмотрим МЕХ-фуикцию, принимающую на входе массив ячеек. Мы знаем, что массив ячеек является "массивом массивов", так как его элементы сами являются массивами системы MATLAB. С точки зрения МЕХ-функций это означает, что MATLAB API функция mxGetCell возвращает указатель на тип mxArray, который требует дальнейшей обработки.

Разработаем МЕХ-функцию, которая получив на входе массив ячеек, изучает его структуру и передает полученную информацию в командное окно системы MATLAB с помощью MATLAB API функции mexPrintf.

Для простоты ограничимся случаем, когда входной массив ячеек имеет размерность два, то есть является матрицей. Исходя из этого ограничения следующий С-код достаточен для разбора содержимого (на первом уровне возможных многоуровневых вложений массивов ячеек в другие массивы ячеек) входного массива (матрицы) типа cell:

#include "mex.h"
// Prototype:
void MyPrint( int, int, mxArray* );
///////////////////////////////////////////////////////
void mexFunction(	int		nOut,
			mxArray*	pOut[],
			int		nIn,
			const mxArray*	pIn[] )
{
	int i, j, ndim, M, N;
	mxArray* pAr;
	//	 Input and output checking:
	if(nIn!=1) mexErrMsgTxt("One input required."); else if(nOut > 0)
	mexErrMsgTxt("No output required.");
	ndim = mxGetNumberOfDimensions( pIn[0] ); if( ndim != 2 )
	mexErrMsgTxt("Only matrices accepted.");
	if( !mxIsCell( pIn[0]) )
	mexErrMsgTxt("Input must be a cell only.");
	//--------:--------------
	M = mxGetM( pIn[0] ); N = mxGetN( pIn[0] );
	for( i = 0; i < M; i++ )
		for( j = 0; j < N; j++ )
		{
			pAr = mxGetCell( pIn[0], i+j*M ); MyPrint( i, j, pAr );
		}
}
////////////////////////////////////////////////////////
void MyPrint( int i, int j, mxArray* pAr )
{
	if( mxIsChar( pAr ) )
		mexPrintf("%d-%d element is Char\n",i+l,j+1); else if( mxIsDouble( pAr ) )
	mexPrintf("%d-%d element is Double\n",i+1,j+1); else if( mxIsComplex( pAr ) )
	mexPrintf("%d-%d element is Complex\n",i+l,j+1); else if( mxIsCell( pAr ) )
	mexPrintf("%d-%d element is Cell\n",i+l,j+1); else if( mxIsStruct( pAr ) )
	mexPrintf("%d-%d element is Struct\n",i+1,j+1); else if( mxIsUint8( pAr ) )
	mexPrintf("%d-%d is Uint8\n",i+l,j+1); else if( mxIsSparse( pAr ) )
	mexPrintf("%d-%d is Sparse\n",i+l,j+1); else
	mexPrintf("%d-%d element - Unknown\n",i+l,j+1);
}
//////////////-the end of the code—/////////////////

На вход данной функции допускаются только двумерные массивы ячеек. С помощью MATLAB API функции mxGetCell мы извлекаем отдельный элемент входного массива. Для массива ячеек элементы имеют тип mxArray. Обратим внимание на то, что в функции mxGetCell отдельные элементы индексируются единственным индексом, значение которого нужно назначать исходя из упорядочения элементов массивов системы MATLAB по столбцам. Именно поэтому элемент из i-й строки и j-ro столбца извлекается с помощью следующего выражения (индексы в языке С начинаются с нуля):

pAr= mxGetCell( pln[0], i+j*M );

Указатель pAr на структуру mxArray далее передается в функцию MyPrint, где С ПОМОЩЬЮ функций mxIsChar, mxIsDouble, mxIsComplex, mxIsCell, mxIsStruct, mxIsUint8 и mxIsSparse определяется, какие данные содержатся в этой структуре.

Так как в языке С индексы массивов начинаются с нуля, а в М-языке они начинаются с единицы, то, чтобы наблюдать в командном окне системы MATLAB привычные значения индексов, мы в функции MyPrint увеличиваем значения индексов на единицу при их выводе в командное окно функцией mexPrintf.

Протестируем работу созданной только что МЕХ-функции мехCell1 на примере простого массива типа cell (см. рис. 12.14):

Если же применить функцию мехСеll1 к массиву ячеек MyCellArray

MyStruct = struct('fieldl',[ 1 2 3],'field2','Hello');
MyCellArray( 1,1)={ 'Bonjour!' };
MyCellArray( 1,2)={[123;456;789]>;
MyCellArray( 2, 1 ) = { MyStruct };
MyCellArray( 2,2)={[975]};

который мы ранее создавали в главе 7 при изучении массивов типа cell, то результат работы этой функции покажет, что распознаются и ситуации, когда некоторые элементы входного массива ячеек сами имеют сложную структуру: например, являются массивами структур:

МехСell1 ( MyCellArray )
1-1 element is Char
1-2 element is Double
2-1 element is Struct
2-2 element is Double

Таким же образом распознаются ситуации, когда элементами массива ячеек, в свою очередь, являются массивы ячеек.

Заканчивая рассмотрение конкретных примеров программирования МЕХ-функций, отметим, что со всеми не рассмотренными нами MATLAB API функциями всегда можно ознакомиться по встроенной в систему MATLAB подсистеме справочной информации.

12.6. Вызов функций и команд системы MATLAB из МЕХ-функций

Мы уже говорили в предыдущем параграфе, что помимо MATLAB API функций с префиксом mx имеются также MATLAB API функции с префиксом тех. Последние предназначены для выполнения команд и функций среды MATLAB. В примерах предыдущего параграфа мы на практике использовали такого рода функции mexErrMsgTxt, mexWarnMsgTxt И mexPrintf.

Однако концептуально наиболее важной функцией такого типа (помимо интерфейсной функции mexFunction) является функция mexCallMATLAB. Эта функция позволяет изнутри С-кода разрабатываемых нами МЕХ-функций вызывать другие МЕХ-функции, любые М-функции и вообще все команды и функции среды MATLAB. Отсюда ясно, что важность функции mexCallMATLAB трудно переоценить. Именно эта функция позволяет самым тесным образом интегрировать МЕХ-фуикции в среду MATLAB, сделав их самым непосредственым расширением этой программной среды.

В качестве примера использования функции mexCallMATLAB рассмотрим сейчас пример МЕХ-функции, которая ничего не принимает на входе, но самостоятельно создает квадратную матрицу типа double, заполняет ее элементы конкретными значениями, после чего вызывает встроенную в среду MATLAB функцию det для вычисления определителя полученной матрицы. Далее наша МЕХ-функция выведет результат вычисления определителя в командное окно системы MATLAB опять-таки с помощью функции mexCallMATLAB.

Эта МЕХ-функция, которую мы назовем MyMexDeterminant, всю работу производит в своем собственном коде и не возвращает в среду MATLAB никаких значений. Поэтому мы проверяем параметры nIn и nOut, чтобы гарантировать правильный вызов этой МЕХ-функции из командного окна системы MATLAB (то есть без входных параметров и без возвращаемых значений).

Перед тем как писать код МЕХ-функции MyMexDeterminant, представим прототип функции mexCallMATLAB:

int mexCallMATLAB( int nOut, mxArray*pOut[],int nIn, mxArray* pIn[], const char* name );

У этой функции всего пять параметров. Первые четыре из них совпадают с параметрами функции mexFunction, что абсолютно очевидно, потому что это и есть интерфейсная функция среды MATLAB (как же иначе можно было бы вызвать из МЕХ-функции другую МЕХ-функцию). Пятый параметр представляет имя функции (команды), подлежащей выполнению.

Теперь мы готовы к написанию кода МЕХ-функции MyMexDeterminant (напомним еще раз, что имя МЕХ-функции совпадает с именем проекта в Microsoft Developer Studio и используется при вызове этой функции, по не встречается в ее исходном С-коде):

#include "mex.h"
#include <memory.h>
///////////////////////////////////////////////////////
void mexFunction( int nOut, mxArray* pOut[],
int nIn, const mxArray* pIn[] )
{
	int ret;
	double x;
	char str[128];
	mxArray* pAr[1];
	mxArray* pRes[1];
	double pS[] = {1,4,7,2,5,8,3,6,9};
	//	 Input and output checking:
	if(nIn!=0) mexErrMagTxt("None input required."); else if(nOut != 0)
	mexErrMsgTxt("None output required.");
	// 3-by-3 matrix creation and element setting:
	pAr[0] = mxCreateDoubleMatrix(3, 3, mxREAL);
	memcpy( mxGetPr( pAr[0] ), pS, sizeof( pS ) );
	// We ask MATLAB to calculate determinant.
	// The result will be in pRes.
	ret = mexCallMATLAB( 1, pRes, 1, pAr, "det" );
	if( ret != 0 ) mexErrMsgTxt("det failure");
	// Mow we show the result in Command window:
	// this is equivalent to "disp" command:
	x = *( mx6etPr( pRes[0] ) );
	sprintf( str, "determinant - %f", x );
	mxDestroyArray( pRes[0] );
	pRes[0] = mxCreateString( str );
	mexCallMATLAB( 0, NULL, 1, pRes, "disp" );
	// the last memory cleanup (good practicel):
	mxDestroyArray( pAr[0] );
	mxDestroyArray( pRes[0] );
}
/////////////—the end of the code	///////////////

По тексту функции можно дать несколько пояснений. Во-первых, заполнение созданного массива типа double значениями его элементов можно выполнить единственным вызовом функции memcpy, так как эта функция за один раз копирует содержимое блока памяти с подготовленными элементами в блок памяти, отведенный под элементы в структуре mxArray.

Вызов функции mexCallMATLAB для вычисления определителя (детерминанта) матрицы достаточно очевиден:

ret = mexCallMATLAB( 1, pRes, 1, pAr, "det" );

У нас тут имеются единственный входной параметр и единственное выходное значение. Входной параметр передается через массив pAr (этот массив состоит из одного элемента, являющегося указателем на структуру mxArray), а выходное значение будет прописано в массив pRes. Последний массив также состоит из единственного указателя на mxArray. Именно так эти переменные и были определены в нашем коде:

mxArray* pAr[1]; mxArray* pRes[1];

Важно проверять возврат функции mexCallMATLAB, так как операция вычисления определителя не всегда возможна (вдруг вы по ошибке передали для вычисления неквадратную матрицу, например).

Из результирующего массива pRes[0] мы извлекаем значение вычисленного определителя и помещаем его в переменную х типа double, после чего используем значение этой переменной для формирования строки текста, содержащего это значение. Делается это с помощью стандартной библиотечной С-функции sprintf.

Далее мы уничтожаем за ненадобностью ранее сформированный массив pRes[0] (фактически освобождаем выделенную под него память), а затем по этому же указателю (не пропадать же добру) создаем новый массив системы MATLAB, но этот массив уже имеет тип char. Он-то и будет служить входным параметром для функции mexCallMATLAB, с помощью которой подготовленное текстовое сообщение и выводится в командное окно системы MATLAB (см. рис. 12.15):

Вот этот фрагмент кода, который осуществляет показанный на рисунке вывод:

pRes[0] = mxCreateString( str );
mexCallMATLAB( 0, NULL, 1, pRes, "disp" );

Так как для исполняемой таким образом функции disp системы MATLAB возвращаемые значения не используются, то мы и прописываем первые два параметра функции mexCallMATLAB нулем и значением NULL соответственно.

В конце текста нашей МЕХ-функции хорошим решением будет самостоятельное (чтобы не перегружать пакет MATLAB дополнительной работой) освобождение памяти, выделенной под структуры mxArray (то есть под массивы системы MATLAB):

// the last memory cleanup (good practice!): mxDestroyArray( pAr[0] );
mxDestroyArray( pRes[0] );

Выполняется эта работа с помощью MATLAB API функции mxDestroyArray, синтаксис вызова которой абсолютно очевиден.

Глава 13. Взаимодействие Windows-приложений с системой MATLAB

13.1. Взаимодействие приложений Windows с MATLAB Engine

До сих пор мы работали целиком и полностью с единственным приложением - с пакетом MATLAB. Даже когда мы разрабатывали некоторые функции на языке С, мы компилировали их в динамические библиотеки, которые для работы загружаются в адресное пространство работающей системы MATLAB, после чего становятся неотделимы от нее. Можно сказать, что МЕХ-функции системы MATLAB (их-то мы и разрабатывали в предыдущей главе на языках C/C++) являются прямым расширением этой системы.

Как быть, если мы разрабатываем собственное изолированное приложение Windows, в котором требуется осуществлять серьезные математические вычисления?

Пакет MATLAB имеет убедительный ответ на этот вопрос, так как предоставляет прямой интерфейс для взаимодействия вашего приложения с этим пакетом. Способ взаимодействия отдельного приложения с системой MATLAB называется взаимодействием с подсистемой MATLAB Engine. В некотором смысле это просто принятая в рамках системы MATLAB терминология. Про приложение, которое взаимодействует с системой MATLAB по технологии MATLAB Engine, говорят, что это приложение MATLAB Engine. Всякая терминология хороша тем, что позволяет точно формулировать детали разработки и обстоятельства функционирования объектов обсуждения.

С точки зрения исходных текстов разрабатываемого на языке С изолированного приложения его взаимодействие с системой MATLAB осуществляется путем вызова специальных функций из библиотеки MATLAB Engine (это небольшая динамическая библиотека libeng.dll размером 32 KB), а функции из этой библиотеки и осуществляют непосредственное взаимодействие с приложением matlab.exe (размер этого файла равен 928 KB). Библиотека MATLAB Engine во время работы загружается в адресное пространство вашего приложения, не слишком утяжеляя его из-за своего небольшого размера. На этапе компиляции же требуется так называемая библиотека импорта libeng.lib. Как получить такую библиотеку, а также как реально осуществить компиляцию проекта для получения изолированного приложения MATLAB Engine, будет рассказано ниже в следующем параграфе настоящей главы.

Сейчас же просто перечислим основные функции из библиотеки MATLAB Engine:

  1. engClose( Engine* ep );
  2. engEvalString( Engine* ер, const char* string );
  3. engGetArray( Engine* ep, const char* name );
  4. engOpen( const char* startcmd );
  5. engOutputBuffer( Engine* ep, char* p, int n );
  6. engPutArray( Engine* ep, const mxArray* mp );
  7. engOpenSingleUse(const char*, void*, int*);

Итак, в этой библиотеке всего семь основных функций (есть еще некоторое количество устаревших функций, доставшихся в наследство от предыдущих версий пакета MATLAB - их не рекомендуется использовать в новых приложениях).

Глядя на представленные прототипы функций, замечаем как уже известные нам внутренние типы данных системы MATLAB, такие как mxArray, так и новый тип данных Engine. Чтобы воспользоваться этим типом данных, нужно включать (директивой препроцессора include) в исходный код разрабатываемого проекта заголовочный файл engine.h.

Взаимодействие с системой MATLAB начинается вызовом функции engOpen, которая и возвращает правильный указатель на тип Engine:

Engine* pEng;
pEng = engOpen( NULL );

причем всегда нужно проверять возврат на NULL. Он означает, что вызов функции engOpen прошел неудачно (например, по причине невозможности запустить на выполнение приложение matlab.exe) и следует закрыть приложение, так как взаимодействовать с системой MATLAB по технологии MATLAB Engine уже невозможно.

Если же вызов функции engOpen прошел удачно, то есть получен ненулевой указатель, то после этого можно вызывать другие функции с префиксом eng, а также функции с префиксом mx, которые мы раньше активно использовали при разработке МЕХ-функций (смотри предыдущую главу). Однако теперь нельзя использовать функции с префиксом mex.

Когда работа с системой MATLAB заканчивается, нужно вызвать функцию engClose, чтобы осуществить корректное отсоединение и освободить занятые ресурсы:

engClose ( pEng );

В промежутке между вызовами функций engOpen и engClose можно вызывать остальные функции библиотеки MATLAB Engine, то есть функции engPutArray, engGetArray, engEvalString и engOutputBuffer (упомянутая выше функция engOpenSingleuse используется вместо функции engOpen).

Функция engPutArray помещает массив mxArray в рабочее пространство системы MATLAB, после чего над этим массивом можно производить вычисления непосредственно в этом рабочем пространстве при помощи функции engEvalString. Чтобы получить из рабочего пространства копию массива (например, результата вычислений) и разместить ее в адресном пространстве нашего приложения, нужно вызвать функцию engGetArray.

Наконец, функция engOutputBuffer позволяет задать буфер в памяти нашего процесса, куда будет поступать при работе функции engEvalstring поток сообщений, обычно поступающий в командное окно системы MATLAB.

Итак, мы очень кратко описали работу всех функций библиотеки MATLAB Engine. Теперь проиллюстрируем их работу на конкретных примерах. Мы ограничимся компактными учебными примерами, так что весь С-код всегда будет помещаться в единственный файл с именем main.с. Мы также будем создавать только приложения Win32 Console Application, причем делается это лишь для простоты. На самом деле абсолютно приемлемы и даже желательны приложения с полноценным графическим интерфейсом Windows типа Win32 Application.

Всего мы рассмотрим три примера. В первом из них мы в своем приложении сформируем матрицу, затем функцией engPutArray передадим ее в рабочее пространство системы MATLAB. Затем с помощью функции engEvalString вычислим в рабочем пространстве собственные значения этой матрицы и возвратим результаты в наше адресное пространство с помощью функции engGetArray. Вот текст первого примера:

#include <stdio.h>
#include <memory.h>
#include <conio.h>
#include "engine.h"
double data[6] = {1,2,3,4,5,6};
int main( void )
{
	Engine* pEng = NULL; mxArray* pArr = NULL; mxArray* pV = NULL;
	double* pReal = NULL;

	double* plmage = NULL;
	int M, i;
	// Start Engine session:

	pEng = engOpen( NULL );
	if( pEng == NULL )
	{
		printf( "\nCan't start Engine session!" );
		return 1;
	}
	// Create real matrix
	pArr = mxCreateDoubleMatrix( 3, 2, mxREAL ); mxSetName( pArr, "A" );
	// Set its elements:
	memcpy( mxGetPr( pArr ), data, 6*sizeof(double) );
	// Put array into MATLAB environment // and calculate eigen values of it:
	engPutArray( pEng, pArr );
	engEvalString( pEng, "Vec = eig( A*A')" );
	pV = engGetArray( pEng, "Vec" );
	// Stop Engine session: engClose( pEng );
	// Get double values from
	// possibly complex mxArray v:
	pReal = mxGetPr( pV );
	pImage = mxGetPi( pV );
	M = mxGetM( pV );
	// Print results:
	printf("Real parts of eig vector\n");
	for( i=0; i<M; i++)
	{
		printf("%lf\n", pReal[i] );
	}
	lf( pImage )
	{
		printf("\nImage parts of eig vector\n");
		for( i=0; i<M; i++)
		{
			printf("%lf\n", plmage[i] );
		}
	}

	// Free mxArray memory buffers:
	mxDestroyArray( pArr );
	mxDestroyArray( pV ); // Screen delay:
	printf( "Press any key to exit");
	getch();
	return 0;
}

В следующем параграфе будет детально рассказано как скомпилировать реальное Win32 console-приложение на базе представленного текста, которое составляет содержимое файла main.с. Это единственный исходный файл, необходимый для компиляции. Там же будет рассказано, как следует отлаживать такое приложение.

Сейчас же покажем, как выглядит окно приложения после того, как все вычисления будут произведены (см. рис. 13.1):

Отсюда видно, что получены три собственных числа. После нажатия любой клавиши окно закроется.

Весь представленный выше код достаточно очевиден после того, как мы кратко описали предназначение всех функций библиотеки MATLAB Engine. Помимо них еще используются функции с префиксом mx, большинство из которых нам также хорошо известно, так как мы достаточно потренировались с ними на примерах из предыдущей главы.

Однако есть все-таки одна новая функция, на которой следует остановить свое внимание. Это функция mxSetName, которую мы только что использовали в представленном выше примере. Это очень важная функция. Можно сказать, что эта функция нивелирует некоторое принципиальное различие между миром программ на языке С и миром программ на М-языке. В первом из них (то есть в мире С-программ) объекты типа mxArray (массивы системы MATLAB) адресуются указателями, которых нет вообще в М-языке. А в мире программ на М-языке есть только обычные имена для массивов. Так вот функция mxSetName и призвана снивелировать это различие, точнее перебросить мостик между двумя способами обращения к массивам (матрицам) в этих двух мирах.

Фрагмент из вышеприведенной программы

// Create real matrix
pArr = mxCreateDoubleMatrix( 3, 2, mxREAL ); mxSetName( pArr, "A" );

показывает, что сначала создается массив 3x2 и он адресуется указателем pArr. После этого с помощью функции mxSetName для массива вводится "человеческое" (в отличие от использования "нечеловеческих" указателей в языке С) имя А. Когда позже этот массив будет внедрен в рабочее пространство системы MATLAB для дальнейшей его обработки, то сама эта обработка в рабочем пространстве системы MATLAB будет происходить над переменной с именем А. Вот соответствующий фрагмент кода:

engPutArray( pEng, pArr );
engEvalString( pEng, "Vec = eig( A*A')" );
pV = eng6etArray( pEng, "Vec" );

Здесь в первой строке массив, адресуемый в С-коде указателем pArr, внедряется в рабочее пространство системы MATLAB, где с ним уже работают как с переменной А, что прекрасно видно из второй строки фрагмента. Далее, поскольку результат вычислений поименован там как Vec, то именно под этим именем мы и извлекаем его назад из рабочего пространства функцией engGetArray. Результат такого извлечения, то есть некоторая область памяти, далее адресуется в С-коде указателем pV.

Надо полагать, что представленных подробнейших пояснений по поводу назначения функции mxSetName вполне достаточно.

Теперь перейдем ко второму примеру. Он будет небольшой модификацией первого примера, направленной на то, чтобы проиллюстрировать назначение и работу функции engOutputBuffer.

Отметим один более-менее очевидный недостаток в коде первого примера, для чего воспроизведем следующий фрагмент:

engEvalString( pEng, "Vec = eig( A*A')" );
pV = engGetArray( pEng, "Vec" );
// Stop Engine session: engClose( pEng );
// Get double values from
// possibly complex mxArray v: pReal = mxGetPr( pV );

Первой строкой здесь запрашивается выполнение некоторого действия в рабочем пространстве системы MATLAB. Результат этого действия извлекается с помощью функции engGetArray. Но что будет, если в процессе выполнения запрошенной операции произошла ошибка? Тогда невозможно получить правильное значение для указателя pV, адресующего результат вычислений. Из-за этого функция mxGetPr, использующая этот указатель, отработает с ошибкой.

С одной стороны, этот недостаток представленного кода легко исправить. Для этого надо лишь перед использованием указателя pV проверять его на равенство значению NULL:

if( pv != NULL )
pReal = mxGetPr( pV );

и тогда все будет работать безошибочно.

Однако остается проблема получения диагностической информации об ошибках, которая всегда выводится в командное окно системы MATLAB. Пусть, к примеру, для вычисления собственных значений выбрана неквадратная матрица. Вот что при этом мы наблюдаем в командном окне системы MATLAB (см. рис. 13.2):

Задачей второго примера будет передача этой диагностической информации в распоряжение С-кода приложения типа MATLAB Engine. Как мы уже рассказывали выше, для этой цели служит функция engCmtPutBuffer. Она задает буфер в памяти, куда и будут поступать сообщения из системы MATLAB.

Добавим в код первого примера несколько строк кода, которые позволят отслеживать ошибочные ситуации, возникающие в процессе вычислений в рабочем пространстве системы MATLAB, а также позволят получить всю диагностическую информацию оттуда. Чтобы не возникло недоразумений и сложностей в восприятии нового варианта текста из первого примера, приведем весь этот код в полном объеме:

#include <stdio.h>
#include <memory.h>
#include <conio.h>
#include "engine.h"
#define BUFSIZE 256
double data[6] = {1,2,3,4,5,6};
int main( void )
{
	Engine* pEng = NULL;
	mxArray* pArr = NULL;
	mxArray* pV = NULL;
	double* pReal = NULL;
	double* plmage = NULL;
	int M, i;
	char pBuf[BUFSIZE];
	// Start Engine session: pEng = engOpen( NULL ); if( pEng == NULL )
	{
		printf( "\nCan't start Engine session 1" );
		return 2;
	}
	// Create real matrix
	pArr = mxCreateDoubleMatrix( 3, 2, mxREAL ); if( pArr == NULL )
	{
		printf("\nCan't create matrix!");
		return 1;
	}
	mxSetName( pArr, "A" );
	// Set its elements:
	memcpy( mxGetPr( pArr ), data, 6*sizeof(double) );
	// Put array into MATLAB environment // and calculate eigen values of it:
	engPutArray( pEng, pArr );
	engOutputBuffer( pEng, pBuf, BUFSIZE );
	engEvalString( pEng, "Vec = eig( A*A')" );
	pV = engGetArray( pEng, "Vec" );

	// Stop Engine session:
	engClose( pEng ); // Let's see the OutputBuffen
	printf( "%e", pBuf );
	// Get double values from // possibly complex mxArray v: if( pV != NULL ) {
	pReal = mxGetPr( pV ); pImage = mxGetPi( pV ); M = mxGetM( pV );
	// Print results:
	printf("Real parts of eig vector\n");
	for( i=0; i<M; i++)
	{
		printf("%lf\n", pReal[i] ); }
		if( plmage )
		{
			printf("\nImage parts of eig vector\n");
			for( i=0; i< M; i++)
			{
				printf("%lf\n", pImage[i] );
			}
		}
	}
	// Free mxArray memory buffers: mxDestroyArray( pArr ); if( pV != NULL ) mxDestroyArray( pV );
	// Screen delay:
	printf("Press any key to exit");
	getch();
	return 0;
}

В этот текст включены все проверки указателей на равенство их значению NULL, но самое главное изменение заключается в применении функции engOutputBuffer.

Эту функцию нужно вызвать перед вызовом функции engEvalString, в результате чего весь вывод информации в командное окно системы MATLAB будет продублирован в заранее приготовленный буфер нашей С-программы:

engOutputBuffer( pEng, pBuf, SIZEBUF );

Буфер размером SIZEBUF адресуется указателем pBuf. Третий параметр этой функции задает число байт информации, которое будет направлено в буфер. Если размер диагностической информации, выводимой в командное окно системы MATLAB, будет больше, чем заданное нами число SIZEBUF, то ничего страшного не произойдет: в соответствии со сказанным только первые SIZEBUF байт будут скопированы в предоставленный буфер памяти. Также никаких проблем не возникает, если реально переданное число байт информации будет меньше, чем истинный размер буфера.

На рисунке 13.3 показано окно разработанного приложения с выведенной в него информацией из рабочего пространства системы MATLAB:

В случае штатной ситуации, когда вычисления в рабочем пространстве системы MATLAB осуществляются корректно, вывод информации, предоставленной функцией engOutputBuffer, бывает излишним, что и видно из последнего рисунка.

Чтобы не было избыточной информации, распечатку буфера лучше осуществлять после проверки наличия ошибочных ситуаций. Вот единственное изменение кода, которое потребуется для этого:

if( pV == NULL )
printf( "%s", pBuf );

После этого наша программа отработает без выдачи излишней информации. В то же время, если таковая возникнет, например, если мы попробуем в среде MATLAB вычислить следующее выражение

engEvalString( pEng, "Vec = eig( A )" );

(которое следует подставить в приведенный выше код второго примера вместо вызова функции engEvalstring с правильным выражением), то в окно нашего приложения поступит уже крайне полезная информация об ошибочной ситуации, так как других способов продиагностировать ее нет (см. рис. 13.4):

Теперь разработаем третий пример приложения типа MATLAB Engine, который будет демонстрировать выполнение системой MATLAB графических манипуляций по требованию из нашего приложения. В этом примере нет ничего особенного, кроме того, что он с очевидностью демонстрирует возможность получать графические изображения, выполненные системой MATLAB, по приказу из внешних приложений типа MATLAB Engine. Вот текст исходного кода третьего примера:

#include <stdio.h>
#include <memory.h>
#include <conio.h>
#include "engine.h"
double data[10] = { 0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0 };
int main( void )
{
	Engine* pEng = NULL;
	mxArray* pArr = NULL;

	// Start Engine session: pEng = engOpen( NULL );
	if( pEng == NULL )
	{
		printf( "\nCan't start Engine session!" ); return 2;
	}
	//
	// Create real vector
	pArr = mxCreateDoubleMatrix( 1, 10, mxREAL );
	if( pArr == NULL )
	{
		printf("\nCan't create vector !"); return 1;
	}
	mxSetName( pArr, "x" );
	// Set its elements:
	memcpy( mxGetPr( pArr ), data, 10*sizeof(double) );
	// Put vector into MATLAB environment
	// and calculate y-sin(x). Then plot graph:
	engPutArray( pEng, pArr );
	engEvalString( pEng, "y » sin( x );plot(xry);" );
	// Free mxArray memory buffers:
	mxDestroyArray( pArr );
	// Screen delay:
	printf("Press any key to exit"); getch();
	// Stop Engine session: engClose( pEng ); return 0;
}

После запуска на выполнение нашего приложения система MATLAB породит следующее графическое окно (см. рис. 13.5):

Обратите внимание на то, что закрытие сессии (сеанса) MATLAB Engine функцией engClose следует осуществлять после вызова функции getch, осуществляющей ожидание ввода с клавиатуры, тем самым обеспечивающей пользователя достаточным временем для наблюдения графических изображений. После же вызова функции engClose приложение MATLAB заканчивает работу и закрывает все свои графические окна.

13.2. Создание и компиляция ЕХЕ-проекта в среде компилятора Microsoft Visual C++

Недостаточно просто написать С-код для приложения типа MATLAB Engine. Нужно еще выбрать тип проекта Microsoft Visual C++, выполнить изменение свойств проекта и, наконец, скомпилировать его.

Допустимыми типами проектов для создания приложений MATLAB Engine являются проекты Win32 Console Application и Win32 Application. Последние позволяют компилировать приложения с развитым графическим интерфейсом пользователя, в то время как первые проекты ограничиваются работой с простыми окнами, ориентированными на текстовый вывод. Именно эти окна (в рамках терминологии системы Windows они и называются консольными) мы приводили выше в качестве иллюстрации работы приложений MATLAB Engine, получающихся из представленных там примеров исходных С-кодов. Мы и далее будем работать только с проектами типа Win32 console Application исключительно из соображений их меньшего объема, что важно в процессе изучения системы MATLAB.

Создав проект указанного типа, включаем в него единственный файл (например, под именем main.с, хотя выбор имени абсолютно произволен). Если тут же начать компилировать проект, то Developer Studio выдаст сообщения об ошибках, поскольку мы еще не выполнили должную настройку проекта. Она заключается в следующем.

Во-первых, нужно прописать путь к включаемому файлу engine.h. Это все тот же каталог \extern\include, который мы уже указывали в свойствах проекта по созданию МЕХ-функций (см. предыдущую главу).

Далее в списке библиотечных файлов проекта нужно вручную записать имена двух библиотек импорта, что показано на рисунке 13.6.

В конце строки ввода object/library modules: здесь присутствуют имена libeng.lib и libmx.lib этих библиотек. Их нужно ввести вручную с клавиатуры.

Сами эти библиотечные файлы, однако, не поставляются в готовом виде вместе с пакетом MATLAB. Их требуется изготовить самостоятельно. В предыдущей главе мы уже самостоятельно изготавливали аналогичный по смыслу и назначению файл matlab.lib. Мы сейчас точно таким же способом, который использовался в главе 12, изготовим недостающие библиотечные файлы libmx.lib И libeng.lib.

Содержимое следующего командного окна Windows показывает необходимые действия по изготовлению обоих библиотечных файлов (рис. 13.7):

По-прежнему применяется утилита lib с ключом /def:, но аргументами теперь являются файлы libeng.def и libmx.def. В результате получаются библиотечные (это библиотеки импорта) файлы libeng.lib И libmx.lib.

Затем полученные таким образом библиотечные файлы нужно скопировать в главный каталог нашего проекта в среде Developer Studio, после чего все готово к компиляции. Нажав клавишу F7, получаем в результате процесса компиляции целевой исполняемый файл с расширением .ехе (а имя его совпадает с выбранным именем каталога проекта). Этот целевой файл уже можно запускать на выполнение (если под отладчиком среды Developer Studio - то клавишей F5). Он сам в процессе работы запустит приложение MATLAB, запросит у него выполнение некоторой работы, а результаты возможных вариантов работы мы уже наблюдали в предыдущем параграфе.

Теперь несколько слов об отладке приложений MATLAB Engine. Для отладки эти приложения запускаются клавишей F5 (если, конечно, они собраны в Debug, а не Release варианте) с предварительно поставленными в их тексте точками останова (Breakpoints). После осуществления останова программы на одной из таких точек можно клавишей F10 осуществлять построчное продвижение. При этом очень легко наблюдать процесс запуска приложения MATLAB в фоновом режиме. Это, однако, не мешает щелчком мыши по иконке этого приложения развернуть его главное (командное) окно и начать тут же вводить команды и выполнять их.

Таким образом легко убедиться в роли такой библиотечной функции системы MATLAB, как функция mxSetName, которую мы детально обсуждали выше. Однако в процессе отладки в справедливости сказанного вы можете убедиться сами. Например, закомментируйте строку в исходном С-коде с вызовом функции mxSetName и перекомпилируйте весь проект. Затем запустите приложение под отладчиком и остановитесь на вызове функции engPutArray. По идее после работы этой функции в рабочем пространстве системы MATLAB должна появиться внедряемая туда с помощью этой функции переменная. Вот это и можно легко проверить. Разверните окно системы MATLAB и введите команду who, которая и покажет список всех переменных из рабочего пространства системы MATLAB. При закомментированной функции mxSetName вы внедряемой функции не обнаружите. Это естественно, так как функцию в рамках системы MATLAB можно показать только по ее имени, а мы не приписали в С-коде ей никакого имени, так как это делается закомментированной функцией mxSetName.

Если вернуться и раскомментировать вызов этой функции, перекомпилировать и запустить проект снова под отладчиком, остановиться где надо, то зайдя в командное окно системы MATLAB и выполнив команду who, мы теперь обнаружим внедряемую нами переменную в списке переменных из рабочего пространства системы MATLAB.

Остальные приемы отладки приложений типа MATLAB Engine не отличаются от приемов отладки любых других приложений Windows.

13.3. Математические библиотеки системы MATLAB

Разработав приложение типа MATLAB Engine, вы возможно захотите передать его для работы сторонним пользователям. Это вполне разумная идея, особенно если на языке С разработан удобный и самоочевидный графический интерфейс, а также присутствуют подробные HELP-файлы.

Тут, однако, следует иметь в виду, что приложение MATLAB Engine невозможно запустить на компьютере, на котором не установлено приложение MATLAB. В этом случае пользователь получит сообщение об ошибке, гласящее, что не удается найти библиотек libeng.dll и libmx.dll. Если просто скопировать эти библиотеки на другой компьютер (например, в каталог System32) и снова запустить на нем разработанное приложение MATLAB Engine, то теперь будут затребованы другие библиотеки, так что конца этому не будет и ничего не получится.

Как же быть, если требуется разработать на языке С приложение, которое использует всю математику, присущую системе MATLAB, но тем не менее, полностью независимо от самого приложения MATLAB? Ответ здесь заключается в том, что тогда нужно разрабатывать на языке С приложение Windows, которое обращается уже не к библиотекам MATLAB Engine (это файлы libeng.dll и libmx.dll), а к математическим библиотекам, поставляемым вместе с пакетом MATLAB. В данном параграфе рассмотрим функции из этих библиотек, а в следующем параграфе разработаем на их основе ряд учебных проектов.

Мощь математических библиотек, поставляемых с пакетом MATLAB и которые можно вызывать из программ, разработанных на языке С, весьма велика. Суммарно математические библиотеки системы MATLAB содержат буквально сотни функций. Все математические библиотеки являются являются библиотеками динамической компоновки и располагаются в каталоге \bin.

Полный набор индивидуальных библиотечных файлов, составляющих логически единую математическую С-библиотеку пакета MATLAB, представлен ниже:

libmmfile.dll
libmatlb.dll
libmatlbmx.dll
libmat.dll
libmx.dll
libut.dll

Эти библиотеки экспортируют основные математические (и некоторые вспомогательные) функции пакета MATLAB. Если изолированное от ядра пакета MATLAB (то есть от приложения matlab.exe) приложение хочет вызывать эти функции, то при его компиляции нужно предоставить в распоряжение компоновщика (часть компилятора в широком смысле этого термина) соответствующие библиотеки импорта. Эти библиотеки, то есть файлы libmmfile.lib, libmatlb.lib, libmatlbmx.lib, libmat.lib, libmx.lib и libut.lib, изготавливаются с помощью утилиты lib.exeи соответствующих файлов с расширением .def. Этот процесс аналогичен ранее рассмотренным примерам получения lib-файлов.

Библиотека libmat.dll содержат функции для работы с МАТ-файлами, то есть с файлами закрытого формата, в которых система MATLAB сохраняет свое рабочее пространство по команде save.

С помощью утилиты dumpbin.exe, входящей в состав пакета Microsoft Visual C++, можно получить список функций, экспортируемых той или иной библиотекой. Выходную информацию удобно сохранять в текстовом файле (см. рис. 13.8):

В частности, в представленном примере мы сохраняем выходную информацию в файле MyFilel2345. Просмотреть содержимое такого файла можно любым текстовым редактором и узнать список экспортируемых функций:

1 0 00001060 MATCLOSE94
2 1 00001110 MATDELETEMATRIX@12
3 2 00001080 MATGETDIR08
4 3 00001140 MAT6ETFULL@28
5 4 00001ОАО MATGETMATRIX@12
6 5 000010FO MATGETNEXTMATRIX@4
7 б 000011ЕО MATGETSTRING@24
8 7 00001000 MATOPEN@16
9 8 00001190 MATPUTFULL028
10 9 00001ODD MATPUTMATRIX08
11 A 00001290 MATPUTSTRING020
12 В 00001540 raatClose
13 С 00001300 raatCreateMATFile
14 D 000016AO matDeleteArray
15 E 00001790 matDeleteMatrix
16 F 00001610 matGetArray
17 10 00001650 matGetArrayHeader
18 11 00001670 matGetDir
19 12 00001580 matGetFp
20 13 000017BO matGetFull
21 14 00001970 matGetMatrix
22 15 000015FO matGetNextArray
23 16 00001630 matGetNextArrayHeader
24 17 000019BO matGetNextMatrix
25 18 000018CO raatGetString
26 19 000014BO matOpen
27 1A 000015BO roatPutArray
28 IB 000015DO matPutArrayAsGlobal
29 1C 00001830 matPutFull
30 ID 00001990 matPutMatrix
31 IE 00001590 matPutNextArray
32 IF 00001920 roatPutString

В списках экспортируемых функций помимо порядковых десятичных и шестнадцатеричных номеров функций даются также их байтовые смещения от начала файла.

Показанным способом всегда можно узнать список экспортируемых функций из любой библиотеки, входящей в группу математических С-библиотек пакета MATLAB.

Одной из наиболее важных библиотек этой группы является библиотека libmatlb.dll. Вот часть списка экспортируемых ею функций:

543 21Е 00013А20 mlfReshape
544 21F 000095DO mlfRestorePreviousContext
545 220 00028А40 mlfReturnValue
546 221 0001C110 mlfRmappdata
547 222 00013AEO mlfRound
548 223 00013B20 mlfRuntime
549 224 OOOOC930 mlfSave
550 225 OOOOCBBO mlfSave_v!2
551 226 OOOOBD40 rolfScalar
552 227 00013B40 mlfSchur
553 228 0001C1CO mlfSelectmoveresize
554 229 0001C200 mlfSemilogx
555 22A 0001C340 mlfSemilogy
556 22В 0001С480 mlfSet
557 22C 000099BO mlfSetErrorHandler
558 22D OOOODC40 mlfSetLibraryAllocFcns
559 22E OOOOC2FO mlfSetLibraryCalloc
560 22F OOOOC330 mlfSetLibraryFree
561 230 OOOOC310 mlfSetLibraryMalloc
562 231 OOOOBF50 mlfSetPi
563 232 OOOOBEFO mlfSetPr
564 233 00009940 mlfSetPrintHandler
565 234 00013BCO mlfSet_param
566 235 0001C5CO mlfSetappdata
567 236 00013BEO mlfSetstr
568 237 00013C20 mlfSign
569 238 00013C60 mlfSim
570 239 00013C80 mlfSimget
571 23A 00013CAO mlfSimset
572 23В 00013ССО mlfSin
573 23C 00013DOO mlfSing.le
574 23D 00013D40 mlfSinh
575 23E 00013D80 mlfSize
576 23F 00013E50 mlfSize2d
577 240 00013EEO mlfSldebug
578 241 00013FOO mlfSort
579 242 00013F90 mlfSparse
580 243 00014020 mlfSparsfun
581 244 000141AO mlfSprintf
582 245 00014290 mlfSqrt
583 246 000142DO mlfSscanf
584 247 000143DO mlfStr2func
585 248 00014470 mlfStrcmp

Из представленного списка хорошо видно, что функции из этой библиотеки имеют префикс mlf и коррелируют с сответствующими функциями, которые вызываются в М-языке без этого префикса. Например, для обычной функции М-языка, такой как sin, в этой библиотеке имеется функция аналогичного действия с именем mlfSin. Для функции sort в пару ставится библиотечная функция mlfSort и так далее. Вообще в библиотеку libmatlb.dll сгруппированы функции, используемые на практике наиболее часто, так что при создании многих приложений можно будет ограничиться лишь двумя библиотеками: libmatlb.dll и libmx.dll. Суммарный объем этих двух библиотек составляет 648 КВ.

Последняя из этих двух библиотек нужна всегда, так как математические функции системы MATLAB оперируют на массивах этой системы, а работу с такими массивами как раз и обеспечивает библиотека libmx.dll. Мы уже использовали эту библиотеку как при создании МЕХ-функций, так и при написании приложений типа MATLAB Engine, поскольку везде приходится работать с массивами системы MATLAB.

Как было сказано выше, суммарно математические библиотеки пакета MATLAB содержат многие сотни функций, поэтому их хотя бы краткое поименное обсуждение здесь невозможно. В то же время в этом нет практической необходимости, так как действие большинства функций вполне понятно из-за их хороших мнемонических имен. Кроме того, в случае необходимости по каждой функции можно получить справочную информацию, вызвав диалоговое окно Help (см. рис. 13.9):

Поэтому мы сразу перейдем к практическому примеру, иллюстриру-щему создание приложений, использующих математическую мощь пакета MATLAB, но являющихся вполне независимыми от ядра этого пакета, то есть от приложения matlab.exe. Будем для краткости называть такие приложения изолированными от matlab.exe.

13.4. Изолированные от matlab.exe приложения Windows

Такие приложения создаются на языке Си с помощью компилятора Microsoft Visual C++ компилируются в исполняемые файлы, для дальнейшей работы которых требуется наличие на компьютере (например, в том же каталоге, где находится исполняемый файл изолированного приложения) динамических математических библиотек пакета MATLAB, о которых было рассказано в предыдущем параграфе.

Последнее означает, что для переноса изолированных приложений на другие компьютеры, на которых пакет MATLAB отсутствует, их следует поставлять вместе с указанными математическими библиотеками.

Рассмотрим простой пример, в котором вычислим sin(x), опираясь на математические библиотеки системы MATLAB, а не на математическую библиотеку компилятора Visual C++. На таком простом примере проде- . монстрируем передачу параметров в функции математической библиотеки системы MATLAB, а также рассмотрим создание проекта и его настройки, необходимые для успешной компиляции.

Вот текст первого примера, в котором исключительно ради краткости убраны все проверки возвращаемых значений (разумеется их нужно применять в реально работающих приложениях):

#include <stdio.h>
#include <memory.h>
#include <conio.h>
#include "matlab.h"
double x[] = {1,3,5,7,2,4,6,8};

int main( void )
{
	mxArray* X = NULL; mxArray* Y = NULL; int i, M, N;
	// Create arguments matrix X:
	X = mxCreateDoubleMatrix( 4, 2, mxREAL );
	// Set its 8 elements:
	memcpy( mxGetPr( X ), data, 8*sizeof(double) );
	// Calculate sin for all values at one time:
	Y = mlfSin( X );
	// Get elements of mxArray Y:
	pEl = mxGetPr( Y ); M = mxGetM( Y );
	N = mxGetN( X );
	// Print results of calculations:
	for( i=0; i<M*N; i++)
	{
		printf("%lf\n", pEl[i] );
	}
	// Free all mxArray memory buffers: mxDestroyArray( X );
	mxDestroyArray( Y );
	// Screen delay:
	printf("Press any key to exit");
	getch();
	return 0;
}

Из этого примера видно, что математические функции из библиотеки системы MATLAB сохраняют важное свойство всех функций системы MATLAB - способность осуществлять массовые (групповые) вычисления. Здесь мы одним вызовом функции mlfSin вычислили сразу восемь скалярных значений функции sin для восьми различных скалярных значений аргумента.

В остальном представленный код нам должен быть абсолютно понятен, так как в нем нет ничего нового по сравнению с примерами, которые мы разрабатывали ранее в связи с приложениями MATLAB Engine. Обратим только внимание на необходимость удалять все буферы в памяти компьютера, выделенные под структуры mxArray. Причем это нужно делать как для массивов, созданных нами явно функцией mxCreateDoubieMatrix, так и для массивов, являющихся возвращаемыми значениями функций из математической библиотеки системы MATLAB. В нашем коде таким массивом является массив Y значений синуса. Память под этот массив была выделена в процессе работы библиотечной функции mlfSin, а освободить эту память должны мы сами:

mxDestroyArray( X ); mxDestroyArray( Y );

Иначе мы допустим ситуацию, когда после завершения работы нашего приложения память, выделенная в процессе ее работы, не будет освобождена, что перегружает операционную работу дополнительными обязанностями, а это нельзя признать хорошим стилем программирования.

Чтобы из представленного исходного С-кода получить исполняемый модуль, нужно создать проект Win32 console Application, прописать в свойствах проекта путь к включаемому файлу matlab.h, добавить в список библиотек проекта еще две библиотеки: libmx.lib и libmatlb.lib. Изготовить эти библиотеки нужно самостоятельно так, как мы всегда поступали в таких случаях (смотри выше, например, параграф про приложения типа MATLAB Engine), после чего скопировать их в главный каталог нашего проекта. После всего этого нужно запустить процесс компиляции нажатием клавиши F7.

Запустив скомпилированное приложение, получим результаты вычислений восьми значений синуса в консольном окне, показанном на рисунке 13.10:

Как мы заявляли ранее, полученное приложение абсолютно независимо от самого приложения matlab.exe. Его можно перенести вместе с библиотеками libmx.dll и libmatlb.dll , а также еще двумя вспомогательными библиотеками libut.dll и libmat.dll, на другой компьютер, на котором пакет MATLAB не установлен, а работоспособность нашего приложения от этого не пострадает. В этом смысле изолированные от matiab.exe приложения поддаются наиболее простому способу распространения среди различных групп пользователей. Естественно, сохраняется зависимость от операционной платформы (невозможно, например, запустить приложение, скомпилированное под Microsoft Windows, на платформе Macintosh).

Используются технологии uCoz