ЧАСТЬ 2 Перейти к Части 3 >> Программирование на языке С для пакета MATLAB На вход данной функции допускаются только двумерные массивы ячеек. С помощью 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 /////////////////////////////////////////////////////// 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 #include #include #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 { printf("%lf\n", pReal[i] ); } lf( pImage ) { printf("\nImage parts of eig vector\n"); for( i=0; 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; } Перейти к Части 3 >>
Другие материалы по теме
|