ЧАСТЬ 1 Перейти к Части 2 >> Программирование на языке С для пакета 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, которая будет выворачивать содержимое входной строки наоборот (последний символ станет первым, предпоследний - вторым и так далее). Большой разницы в коде МЕХ-функций, работающих с текстовыми строками, по сравнению с МЕХ-функциями, обрабат |