Требуется обновление браузера.

Опыт использования стандартных интерфейсов MATLAB для организации разноязыкового программирования в задачах математического и полунатурного моделирования авиационной техники


Просмотров: 4056
Декабрь 2013 года
Набатчиков А. М., Бурлак Е. А. Опыт использования стандартных интерфейсов MATLAB для организации разноязыкового программирования в задачах математического и полунатурного моделирования авиационной техники // Электронный научно-технический журнал "Инженерный вестник". – 2013. – №12. – С. 501-516.
УДК: 629.7:004:331.101.1
Ключевые слова: matlab; mat; mex; C++; code::blocks; работа с dll

Статья на сайте журнала

Аннотация


В статье рассматриваются два внешних интерфейса популярного пакета прикладных программ MATLAB: MAT и MEX. Приведены примеры создания расширений в бесплатной кросплатформенной IDE (Integrated Development Environment) Code::Blocks с использованием языка программирования C++. Даны рекомендации по конфигурации систем и практически проверенные примеры описания интерфейсов.

В качестве практического применения MAT и MEX могут выступать такие задачи, как обработка экспериментальных данных, математический анализ моделей динамики авиационной техники, организация полунатурного моделирования в реальном масштабе времени. Приведённый исходный код ориентирован на читателя с базовыми познаниями языков C++ и M.

Введение


MATLAB - один из лидеров в области обработки данных и математического моделирования. Пакет обладает большим количеством штатных библиотек и алгоритмических решений. Цель данной статьи – рассмотреть некоторые интерфейсы системы MATLAB, позволяющие расширять функциональные возможности за счёт использования приложений, написанных на языке C++. Указанные средства применялись на практике авторами данной статьи для анализа моделей динамики летательного аппарата [1] и для послеполётной обработки экспериментальных стендовых данных [12]. Интерфейсы позволяют создать предельно быструю реализацию алгоритма (за счёт многопоточности [2, 3, 15], использования языка ассемблера или вычислительных мощностей видеокарты) и создавать более быстродействующий транслированный код вместо принятой в MATLAB интерпретации [4, 5, 6]. Кроме того, как известно [7], никакой другой язык, созданный до или после языка C++, не давал программисту более полного контроля над компьютером.

Несмотря на существование штатной справки, учебников и других материалов, разобраться в подобных пособиях программисту, не имеющему обширного опыта разработки и использования внешних динамически подключаемых библиотек, весьма сложно. Среди причин можно выделить:
  • отсутствие в описаниях процедур разработки ряда важных деталей;
  • наличие в инструкциях неточностей и неоднозначностей;
  • ориентация пособий на определённую среду разработки (как правило, Visual Studio) и отсутствие рекомендаций по использованию Code::Blocks;

В статье приводятся простые и ясные, полностью работоспособные примеры, прошедшие апробацию на исследовательском стенде. Указанный далее исходный код составлен таким образом, чтобы продемонстрировать максимум гибкости и возможностей того или иного способа обмена информацией. Тем не менее предполагается, что читатель обладает базовыми знаниями используемых языков программирования [9, 5], и при необходимости может осуществить дополнительную оптимизацию и контроль [8] в соответствии с требованиями конкретных задач.

Характеристики стенда


Рассматриваемые примеры собираются на ПЭВМ с установленной операционной системой Windows (версия системы должна позволять инсталлировать приведённые далее программы) и пакетом прикладных программ MATLAB версии 7.5.0.342 (R2007b) (32-битная версия). Сборка программ и библиотек производится с помощью бесплатной кросплатформенной IDE Code::Blocks 10.05 и компилятора GNU (GNUs Not UNIX) GCC (GNU Compiler Collection) (идущего в комплекте). Разработка ведётся на языках C++ и M. Корневая директория MATLAB в приведённых далее путях именуется
matlabroot
.

Работа с форматом MAT


MAT-файл - это формат файла данных системы MATLAB, предназначенный для сохранения данных в энергонезависимой памяти ПЭВМ. Таким образом, формат позволяет обмениваться данными между платформами или совершать операции импорта и экспорта данных отдельным самостоятельным приложениям. Для упрощения работы стороннего программиста, пакет MATLAB включает в себя файлы динамической линковки (DLL) и сопутствующие им библиотеки импорта (LIB), реализующие API (Application Programming Interface) работы с MAT-файлом.

Полный список функций, для работы с форматом MAT (функции имеют префикс
mat
в имени) можно найти в справке MATLAB [10] (в разделе «MAT-File Access»). Детально остановимся на процессе создания программы, способной работать с рассматриваемым файлом данных. В рамках демонстрации ограничимся консольным приложением, так как вопросы проектирования и взаимодействия с GUI (Graphical User Interface) не относятся к теме настоящей статьи. Для удобства, разобьём процесс на последовательность шагов.

Шаг 1. Создаём консольное приложение (
Console application
). Указываем язык C++.

Шаг 2. В меню
«Project»
выбираем пункт
«Build options…»
, вкладка
«Linker settings»
. Добавляем в список
«Link libraries»
(нажав кнопку
«Add»
) следующие библиотеки импорта:
libmat.lib
и
libmx.lib
(оба из директории
matlabroot\extern\lib\win32\microsoft
). Отметим, что это менее удобный способ добавления большого числа библиотек для линковки, но альтернативный метод добавления (путём указания директории поиска) в некоторых версиях IDE не работает.

Шаг 3. В меню
«Project»
выбираем пункт
«Build options…»
, вкладка
«Search directories»
, вкладка
«Compiler»
. Добавляем в список (нажав кнопку
«Add»
) директорию с заголовочными файлами:
matlabroot\extern\include
.

Шаг 4. Для использования API подключаем в исходном коде разрабатываемой программы (директивой препроцессора
#include
) файлы
mat.h
и
matrix.h
. Эти файлы содержат объявления функций для работы с форматом MAT и массивами MATLAB соответственно. Отметим, что для работы разрабатываемого приложения в системе без предустановленного пакета MATLAB, понадобятся и сами динамически подключаемые файлы:
libmat.dll
и
libmx.dll
(находящиеся по адресу
matlabroot\bin\win32
). Так как при установке MATLAB прописывает свои директории в переменную
Path
(см. рис.1), то у разработчика, с установленным пакетом, может сложиться ложное представление о достаточности одних объектных файлов. Последнее легко опровергнуть, удалив из
Path
директории MATLAB и выполнив перезагрузку операционной системы (см. рис. 2 а). Дело в том, что при попытке приложения загрузить DLL, производится попытка найти требуемый файл в следующих местах: сначала в каталоге с программой, инициализировавшей создание процесса, в текущем каталоге, в путях, указанных в переменных среды, и так далее [11]. Кроме того, фактически, приложению понадобится ещё ряд библиотек, не упомянутых в справке MATLAB (см. рис. 2 б). Полный перечень библиотек для данного приложения можно посмотреть утилитой, аналогичной программе Dependency Walker (из установочного пакета Visual Studio), или руководствуясь диагностическими сообщениями системы (см. рис. 2).

env set.png
Рисунок 1. Переменные среды

wo dll run error.png
Рисунок 2 а. Ошибки загрузки DLL
another dll error.png
Рисунок 2 б. Ошибки загрузки DLL

На этом процесс подготовки проекта завершён, и можно переходить непосредственно к работе с API.

Для того чтобы начать экспериментировать с форматом MAT, необходимо подготовить тестовый файл, например, описанным далее способом. Выполним следующий код в командной строке MATLAB (см. листинг 1).

Листинг 1. Создание переменных в MATLAB
1
FOO=[1 2;3 4];BAR=[10 9+0.1i 8 7];QAZ='test';

Результатом выполнения команды станет создание матрицы
FOO
, вектора
BAR
и строки
QAZ
(см. рис. 3)

workspace.png
Рисунок 3. Просмотр переменных рабочей области

Для сохранения этих данных в виде MAT-файла необходимо выбрать меню
«File»
, опцию
«Save Workspace As…»
и указать желаемое имя и размещение файла (в примере рассматривается работа с файлом
matlab.mat
, находящимся в директории разрабатываемой программы).

Теперь перейдём к созданию программы взаимодействия с файлом. В листинге 2 приведён пример чтения данных из файла.

Листинг 2. Открытие и закрытие файла; получение переменных
12345678910111213141516171819202122232425262728
MATFile *hFILE=matOpen("matlab.mat","r");
//Попытка открыть MAT-файл в режиме чтения
if(hFILE==0){
	//Ошибка открытия
	cout<<"error: open";
	return 1;
}

int num;
//Попытка получить список переменных
char **names=matGetDir(hFILE,&num);
if(names==0){
	//Ошибка получения списка
	cout<<"error: GetDir";
}
else{
	for(int i=0;i<num;i++){
		//Здесь будет код вывода данных
	}
	//Освобождение памяти, занятой именами
	mxFree(names);	
}
int CCode=matClose(hFILE);
//Попытка закрыть файл
if(CCode==-1){
	//Ошибка закрытия
	cout<<"error: close";
}

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

Листинг 3. Вывод информации о переменных
12345678910111213141516171819202122232425262728293031323334353637383940
cout<<names[i]<<"; [";
//Попытка получить указатель на массив данных по имени
mxArray *Arr=matGetVariable(hFILE,names[i]);
if(Arr!=0){
	//Попытка удалась
	//Получаем количество измерений массива
	mwSize NoD=mxGetNumberOfDimensions(Arr);
	//Получаем указатель на массив размеров
	const mwSize *DimSz=mxGetDimensions(Arr);
	//В цикле выводим размеры массива
	//(например: [2x3])
	for(int j=0;j<NoD;j++){
		cout<<DimSz[j];
		if(j!=NoD-1){
			cout<<"x";
		}
	}
	cout<<"] total ";
	//Выводим общее количество элементов
	cout<<mxGetNumberOfElements(Arr);
	cout<<"; size 1 el = ";
	//Выводим объём памяти, занимаемый
	//одним элементов
	cout<<mxGetElementSize(Arr);
	cout<<" bytes [";
	//Выводим текстовое представление
	//класса данных массива
	cout<<mxGetClassName(Arr);
	cout<<"]"<<endl;
	//Проверяем: имеет ли массив
	//мнимую часть
	if(mxIsComplex(Arr)){
		cout<<"[complex]"<<endl;
	}
	//Здесь будет код вывода элементов массива

	//Очищаем память
	mxDestroyArray(Arr);
}
cout<<endl;

Следующий листинг последовательно выводит значения элементов массивов. Отметим, что многомерные массивы в MATLAB индексируются одним числом, причём «пробегающим» элементы столбец за столбцом, вместо более привычного построчного прохода [10]. Данное обстоятельство вынуждает использовать «нетрадиционную» формулу для сопоставления индекса с номерами строки и столбца, пересечение которых даёт требуемый элемент матрицы.

Листинг 4. Вывод значений элементов массивов
1234567891011121314151617181920212223242526272829
//Циклы перебора элементов
for(int y=0;y<mxGetM(Arr);y++){
	for(int x=0;x<mxGetN(Arr);x++){
		//Выбор наиболее подходящего метода вывода
		switch(mxGetClassID(Arr)){
			//числа
			case mxDOUBLE_CLASS:{
				//Получение указателей на
				//действительную…
				double *Re=mxGetPr(Arr);
				//… и мнимую части
				double *Im=mxGetPi(Arr);
				//вывод значения
				cout<<setw(3)<<Re[x*mxGetM(Arr)+y];
				if(Im!=0){
					cout<<"+"<<setw(3)<<Im[x*mxGetM(Arr)+y]<<"i;";
				}
				break;
			}
			//текст
			case mxCHAR_CLASS:{
				//вывод очередного символа
				cout<<char(mxGetChars(Arr)[x*mxGetM(Arr)+y]);
				break;
			}
		}
	}
	cout<<endl;
}

Результат работы программы представлен на рисунке 4.

output.png
Рисунок 4. Вывод содержимого MAT-файла

Рассмотрим теперь процесс записи данных в файл. Для открытия файла в режиме записи данных, необходимо использовать флаг
u
(update) или
w
(write). Второй режим удалит имеющиеся в файле данные перед началом записи.

Листинг 5. Изменение переменных и запись их в файл
1234567891011121314
//Получение переменной по имени
mxArray *Vec=matGetVariable(hFILE,"BAR");
//Получение указателя на массив действительных значений
double *RePart=mxGetPr(Vec);
//Изменение действительной части первого элемента
RePart[0]=99;
//Сохранение изменённого массива под именем BAR2
matPutVariable(hFILE,"BAR2",Vec);

Vec=matGetVariable(hFILE,"QAZ");
//Изменение второго символа в строке
mxGetChars(Vec)[1]='E';
//Сохранение под существующим именем QAZ
matPutVariable(hFILE,"QAZ",Vec);

Рисунок 5 показывает загруженный в MATLAB модифицированный MAT-файл.

workspace mod.png
Рисунок 5. Просмотр модифицированных переменных рабочей области

На этом рассмотрение данного интерфейса закончим. Полный список функций для работы со встроенными массивами MATLAB (функции имеют префикс
mx
в имени) можно найти в справке [10] (в разделе «MX Array Manipulation»).

Создание и эксплуатация MEX-файла


MATLAB предусматривает возможность использовать C-код, написанный сторонним разработчиком, как встроенную в систему функцию. Данная возможность реализуется с помощью DLL-файла, который MATLAB может автоматически загружать и выполнять. Подобные файлы, созданные с соблюдением ряда требований, реализуют собой MEX-функции (M – MATLAB, EX – External («внешний») [5]). Использование MEX-файлов имеет два основных преимущества: отсутствие необходимости переписывать давно существующее решение на язык M [12] и возможность повышения производительности в «узких местах» [10]. Вызов MEX-функции в среде MATLAB аналогичен вызову M-функции: в качестве имени выступает имя DLL-файла.

В справке [10] утверждается, что в системе Windows, MEX-файл должен иметь расширение
mexw32
или
mexw64
(узнать расширение для данной системы, можно выполнив команду
mexext
в MATLAB), однако, это скорее формальная рекомендация (упрощающая в дальнейшем различия между MEX и «обычной» DLL), чем техническая необходимость: MATLAB корректно работает и с расширением
dll
[5].

Рассмотрим последовательность действий по созданию MEX-файла.

Шаг 1. Создаём проект динамически подключаемой библиотеки (Dynamic Link Library).

Шаг 2. Создаём Module-Definition File (DEF-файл) и добавляем его к проекту. Для этого выбираем меню
«File»
, пункт
«New»
, пункт
«Empty file»
. Подтверждаем добавление файла, при сохранении указываем имя проекта и расширение
def
.

Шаг 3. Указываем в созданном DEF-файле содержимое листинга 6 (пример для проекта с именем
«MexTest»
, имя экспортируемой функцией фиксировано).

Листинг 6. Содержимое файла MexTest.def
12
LIBRARY MexTest
EXPORTS mexFunction

Шаг 4. В меню
«Project»
выбираем пункт
«Build options…»
, вкладка
«Linker settings»
. Добавляем в список
«Link libraries»
(нажав кнопку
«Add»
) следующие библиотеки импорта:
libmat.lib
,
libmx.lib
и
libmex.lib
(все из директории
matlabroot\extern\lib\win32\microsoft
).

Шаг 5. В меню
«Project»
выбираем пункт
«Build options…»
, вкладка
«Search directories»
, вкладка
«Compiler»
. Добавляем в список (нажав кнопку
«Add»
) директорию с заголовочными файлами:
matlabroot\extern\include
.

Шаг 6. Для использования API подключаем в исходном коде разрабатываемой программы (директивой препроцессора
#include
) файл
mex.h
.

Шаг 7. Удаляем созданные автоматически в проекте участки кода: объявление (из файла
main.h
) и реализацию (из файла
main.cpp
) функции
void DLL_EXPORT SomeFunction
.

Шаг 8. В меню
«Project»
выбираем пункт
«Properties…»
, вкладка
«Build targets»
. В поле
«Output filename»
изменяем расширение файла на
mexw32
. Снимаем выбор с пункта
«Auto-generate filename extension»
. При этом необходимо убедиться в правильности выбора режима сборки, для которого проводится конфигурация (
Debug
или
Release
), в списке
«Build targets»
.

Шаг 9. Основной код размещается в функции со следующим фиксированным именем и прототипом
void mexFunction(int nOut, mxArray* pOut[], int nIn, const mxArray* pIn[]).


Именно функция
mexFunction
играет роль интерфейса между кодом на языке C++ и средой MATLAB. Рассмотрим аргументы функции подробнее (см. таблицу 1).

Таблица 1. Аргументы функции
mexFunction
Аргумент
Описание
int nOut
Количество аргументов, переданных из MATLAB.
mxArray* pOut[]
Массив указателей на переданные аргументы.
int nIn
Количество выходных параметров, запрашиваемых MATLAB-ом.
const mxArray* pIn[]
Массив указателей на возвращаемые параметры.

Полный список функций для работы с mex интерфейсом (функции имеют префикс
mex
в имени) можно найти в справке [10] (в разделе «MEX-Files»). Собранный файл требуется скопировать в одну из директорий, перечисленных в списке путей доступа MATLAB. Далее приведём исходный код простой функции, возвращающей минимальное и максимальное значения из переданного действительного одномерного массива (см. листинг 7).

Листинг 7. Исходный код MEX-функции
123456789101112131415161718192021222324252627282930313233343536373839404142
//Проверка количества аргументов
if(nIn!=1){
	mexErrMsgTxt("nIn error");
}
//Проверка количества возвращаемых значений
if(nOut!=2){
	mexErrMsgTxt("nOut error");
}
//Указатель на первый аргумент
const mxArray *Arr=pIn[0];
//Проверка: входной массив комплексный
if(mxIsComplex(Arr)){
	mexErrMsgTxt("pIn[0] is complex");
}
//Проверка: входной массив – двумерная матрица
if(mxGetM(Arr)!=1 && mxGetN(Arr)!=1){
	mexErrMsgTxt("pIn[0] is 2D");
}
//все проверки пройдены.
//Выделяем память под возвращаемые значения:
//две матрицы [1x1] типа double без мнимой части
pOut[0]=mxCreateNumericMatrix(1,
				1,
				mxDOUBLE_CLASS,
				mxREAL);
pOut[1]=mxCreateNumericMatrix(1,
				1,
				mxDOUBLE_CLASS,
				mxREAL);
//указатель на массив действительных значений
//аргумента
double *f=mxGetPr(Arr);
//Ссылки на будущие min и max
double &min=mxGetPr(pOut[0])[0];
double &max=mxGetPr(pOut[1])[0];
//Первичное значение
min=max=f[0];
//Поиск экстремумов в оставшейся части массива
for(unsigned i=1;i<mxGetNumberOfElements(Arr);i++){
	if(f[i]<min)min=f[i];
	else if(f[i]>max)max=f[i];
}

Программист может завершить вызов функции с выдачей диагностического сообщения в окно MATLAB, с помощью функции
mexErrMsgTxt
. При этом вся выделенная память автоматически будет высвобождена. Аналогично поступит MATLAB и в случае нехватки места в хип-области [7, 10] для выделения памяти. Все созданные массивы (кроме выходных), не удаленные программистом, будут удалены после завершения работы функции, однако рекомендуется освобождать память по мере исчезновения необходимости в ней. Ещё раз подчеркнём: память под возвращаемые значения выделяется программистом внутри MEX-функции и не должна быть очищена.

Среди прочих отметим интересную возможность: вызывать M-функции и операторы MATLAB из MEX-функции посредством
mexCallMATLAB
. Приведём далее вычурный вариант использования данной возможности для решения рассмотренной выше задачи (см. листинг 8). Отметим, что для того, чтобы обновить MEX-файл без перезапуска MATLAB, необходимо выполнить команду
clear all
.

Листинг 8. Использование mexCallMATLAB
12345678910111213141516171819202122
//Выше происходят необходимые проверки

//Массивы указателей – аргументы mexCallMATLAB
mxArray *OutMTLB[1];
mxArray *InMTLB[1];
//На вход – копия массива,
//переданного в MEX
InMTLB[0]=mxDuplicateArray(Arr);
//Вызов m-функции “min”
mexCallMATLAB(1,OutMTLB,1,InMTLB,"min");
//Создание скаляра и сохранение в
//него возвращённого значения
pOut[0]=mxCreateDoubleScalar(mxGetPr(OutMTLB[0])[0]);
//Уничтожение ненужного массива
//(созданного MATLAB при вызове “min”)
mxDestroyArray(OutMTLB[0]);
//Аналогично…
mexCallMATLAB(1,OutMTLB,1,InMTLB,"max");
pOut[1]=mxCreateDoubleScalar(mxGetPr(OutMTLB[0])[0]);
mxDestroyArray(OutMTLB[0]);
//Уничтожение ненужного массива
mxDestroyArray(InMTLB[0]);

Листинг 9 демонстрирует пример использования функции в среде MATLAB.

Листинг 9. Вызов MEX-функции
12345678910
>> [min max]=MexTest([1 2 3 -1])

min =

	-1


max =

	3

К сожалению, MEX, в отличие от аналогичного интерфейса UserEFI, предоставляемого системой Mathcad, позволяет экспортировать только одну функцию в файле, но данный недостаток можно компенсировать, используя непосредственное взаимодействие с DLL-файлом (
loadlibrary
,
calllib
и т.п.). Авторы не ставили целью данной статьи рассмотрение вышеуказанного интерфейса в связи с большим количеством технических аспектов.

Дополнительную информацию по взаимодействию с MATLAB можно найти в [13].

Заключение


Таким образом, в работе предложено простое описание интерфейсов, снабжённое практическими примерами. Рассмотренные интерфейсы – одни из наиболее популярных в MATLAB, хоть и не единственные. Именно они широко используются для разноязыкового программирования, поскольку MAT совмещён с развитыми библиотеками по обмену и хранению информации, а MEX обеспечивает возможность исследовать модели, реализованные на C++, методами, предоставляемыми MATLAB. Данные средства применяются, например, при полунатурном стендовом моделировании: для обработки экспериментальных данных, математического анализа используемых моделей, организации полунатурного моделирования в реальном масштабе времени [14].

Список литературы


Комментарии

Инкогнито
  Загружаем captcha