Interfejs języka C++ ze środowiskiem obliczeniowym Matlab.
Transkrypt
Interfejs języka C++ ze środowiskiem obliczeniowym Matlab.
Katedra Elektrotechniki Teoretycznej i Informatyki Przedmiot: Zintegrowane Pakiety Obliczeniowe W Zastosowaniach InŜynierskich Numer ćwiczenia: 8 Temat: Interfejs języka C++ ze środowiskiem obliczeniowym Matlab. 1. Wprowadzenie MATLAB, pomimo, iŜ jest kompletnym, samodzielnym środowiskiem do programowania i manipulacji danych, często jego działanie jest wspomagane przez zewnętrzne programy spoza środowiska MATLAB. MATLAB umoŜliwia zastosowanie, jako podprocedur, programów napisanych w C lub Fortranie. Podprocedury napisane w języku C lub Fortran moŜna wywoływać w MATLABie tak jak by to były jego wbudowanymi funkcjami. Funkcje napisane w C lub Fortranie wywoływane w środowisku MATLAB nazywane są MEX-plikami. MEX-plik (skrót od Matlab EXecutable) to dynamicznie ładowana biblioteka (w Microsoft Windows są to pliki *.dll) którą moŜna uruchomić z wiersza poleceń w oknie MATLABa tak jak zwykły m-plik. MoŜliwość skorzystania z napisanych w C lub Fortranie podprocedur to niewątpliwie zaleta, dzięki której moŜna nie tylko w prostszy sposób obsłuŜyć wiele urządzeń (np. generatory, przetworniki A/C i C/A, programowalne filtry, mierniki), ale równieŜ zwiększyć szybkość wykonywania wielu obliczeń (szczególnie jeśli oparte są one na instrukcjach sterujących takich jak for czy while). Trzeba jednak pamiętać, iŜ MATLAB jest efektywnym narzędziem, umoŜliwiającym oszczędzenie cennego czasu na pisanie programów w języku niŜszego-rzędu takich jak C i Fortran i z tego względu naleŜy unikać pisania zbyt wielu podprocedur w pastaci MEX-plików. MATLAB rozpoznaje MEX-pliki przez odpowiadające danej platformie rozszerzenie. W tabeli 1 podano moŜliwe rozszerzenia MEX-plików. Tabela 1: MEX-pliki Platforma HP-UX Linux Macintosh Solaris Windows Rozszerzenie MEX-pliku mexhpux mexglx mexmac mexsol dll 2. Struktura MEX-pliku KaŜdy MEX-plik składa się z: a) #include ”mex.h” b) Procedury mexFuction c) Macierz mxArray d) Wywołań funkcji API (interfejs programowania aplikacji, ang. Application Programming Interface) 1 Grzegorz Psuj, Katedra Elektrotechniki Teoretycznej i Informatyki Kontakt: e-mail: [email protected], tel: 91 449 47 27 #include ”mex.h” KaŜdy MEX-plik C/C++ powinien być opatrzony nagłówkiem ”mex.h”. W tym wypadku konieczne jest stosowanie w takim pliku procedur mx* oraz mex*. Procedura mexFunction KaŜdy MEX-plik musi zawierać zawierać procedure mxFuction (zwanna równieŜ procedura Gateway). W ten sposób MATLAB otrzymuje dostęp do pliku DLL. Składnia tej procedury jest następująca: mexFunction(int nlhs, mxArray*plhs[], int nrhs, const mxArray *prhs[]) { ... } gdzie: nlhs: liczba parametrów wyjściowych phls: macierz wskaźników do parametrów wyjściowych nrhs: liczba parametrów wejściowych prhs: macierz wskaźników do parametrów wejściowych (parametry wejściowe mągą być wyłącznie czytane przez mexFunction; nie powinny być modyfikowane wewnątrz procedury) Macierz mxArray mxArray jest specjalną strukturą zawierająca dane ze środowiska MATLAB. MATLAB rozpoznaję tylko pojedyncze obiekty, np. typu mxArray. Wszystkie zmienne (skalary, wektory, macierze, łańcuch, macierze komórkowe, struktury) są przechowywane jako mxArrary. W mxArray moŜna wyróŜnić: nazwę zmiennej w środowisku MATLAB jej rozmiar jej typ czy jest typu rzeczywistego czy zespolonego Procedura postaci: mxArray *array; umoŜliwia deklaracje zmiennej mxArray o nazwie array. Zmienna ta nie zawiera jeszcze Ŝadnych wartości. Musza one dopiero zostać zdefiniowane/zainicjowane przy wykorzystaniu procedur typu mx* Wywołania funkcji API Procedury typu mx* są wykorzystywane wewnątrz macierzy mxArray. SłuŜą do zarządzania pamięcią oraz do tworzenia i niszczenia macierzy mxArray. Do bardziej przydatnych procedur typu mx* zaliczyć moŜna: tworzenie macierzy: mxCreateNumericArray, mxCreateCellArray, mxCreateCharArray dostęp do macierzy: mxGetPr, mxGetPi, mxGetM, mxGetData, mxGetCell modyfikacja macierzy: mxSetPr, mxSetPi, mxSetData, mxSetField zarządzanie pamięcią: xMalloc, mxCalloc, mxFree, mexMakeMemoryPersistent, mexAtExit, mxDestroyArray, memcpy Inne przydatne procedury: mexFunction: przekazuje wskaźnik MEX-pliku C mexErrMsgTxt: Wyświetla komunikat błędu i zwraca kontrolę do MATALBa mexCallMATLAB: wzywa funkcję MATLABa lub teŜ zdefiniowany przez uŜytkownika dowolny M-plik lub MEX-plik mexGetArray: kopuje zmienne z innej przestrzeni wykonawczej 2 Grzegorz Psuj, Katedra Elektrotechniki Teoretycznej i Informatyki Kontakt: e-mail: [email protected], tel: 91 449 47 27 mexPrintf: procedura druku ANSI C mexWarnMsgTxt: wyświetla komunikat ostrzeŜenia Kompletną lista procedur mx* oraz mex* moŜna znaleźć na stronach MATLABa (http://www.mathworks.com) lub w plikach pomocy pod hasłem External/API Refrence. 3. Tworzenie MEX-pliku Tworzenia MEX-pliku zostanie omówione na podstawie przykładu dostępnego równieŜ w plikach pomocy MATLABa. #include "mex.h" void timestwo(double y[], double x[]) { y[0] = 2.0*x[0]; } Na początku naleŜy dodać nagłówek ”mex.h” Następnie pojawia się procedura obliczeniowa; w tym wypadku dotyczy ona mnoŜenia jednej liczby przez 2 i wyrzucania policzonej wartości na zewnątrz jej void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { double *x, *y; int mrows, ncols; Następnie pojawia się procedura mexFunction wraz z procedurami wywołującymi funkcje API /* SPrawdzenie poprawnej ilości parametrow. */ if (nrhs != 1) { mexErrMsgTxt("Wymagany jest jeden parametr wejsciowy."); } else if (nlhs > 1) { mexErrMsgTxt("Podano za duzo parametrow wyjsciowych"); } /* The input must be a noncomplex scalar double. */ mrows = mxGetM(prhs[0]); ncols = mxGetN(prhs[0]); if (!mxIsDouble(prhs[0]) || mxIsComplex(prhs[0]) || !(mrows == 1 && ncols == 1)) { mexErrMsgTxt("Input must be a noncomplex scalar double."); } /* Create matrix for the return argument. */ plhs[0] = mxCreateDoubleMatrix(mrows,ncols, mxREAL); /* Assign pointers to each input and output. */ x = mxGetPr(prhs[0]); y = mxGetPr(plhs[0]); /* Call the timestwo subroutine. */ timestwo(y,x); } Napisany MEX-plik nazwijmy timestwo.c 4. Kompilacja MEX-pliku Przed wykonaniem kompilacji MEX-pliku naleŜy określić rodzaj kompilatora. MATLAB posiada swój wewnętrzny interpreter języka C++, ale moŜna wykorzystać równieŜ kompilator 3 Grzegorz Psuj, Katedra Elektrotechniki Teoretycznej i Informatyki Kontakt: e-mail: [email protected], tel: 91 449 47 27 Microsoft Visual C/C++. Definicja aktywnego kompilatora odbywa się z wykorzystaniem składni mex –setup. Wybór kompilatora z pozycji MATLABa: >> mex -setup Please choose your compiler for building external interface (MEX) files: Would you like mex to locate installed compilers [y]/n? y Select a compiler: [1] Digital Visual Fortran version 6.0 in C:\Program Files\Microsoft Visual Studio [2] Lcc C version 2.4 in D:\MATLAB701\sys\lcc [3] Microsoft Visual C/C++ version 6.0 in C:\Program Files\Microsoft Visual Studio [0] None Compiler: 3 Please verify your choices: Compiler: Microsoft Visual C/C++ 6.0 Location: C:\Program Files\Microsoft Visual Studio Are these correct?([y]/n): y Po dokonaniu rodzaju kompilatora wykonuje się kompilację napisanego MEX-pliku poleceniem: >> mex timestwo.c Po udanej kompilacji moŜna wywołać utworzony MEX-plik poleceniem: >> timestwo(5,2) ans = 10 5. Ćwiczenie Zdalne sterowanie generatorem funkcyjnym wymaga podania do generatora łańcucha składającego się z liter i wartości numerycznych. Ciąg wartości moŜe np. odpowiadać jednemu okresowi sygnału wyjściowego na generatorze. Niniejsze ćwiczenie będzie polegało na stworzeniu takiego łańcucha w środowisku obliczeniowym MATLAB z wykorzystaniem wbudowanych funkcji oraz z wykorzystaniem MEX-pliku C oraz porównaniu czasu trwania obu operacji. Przykładowy łańcuch moŜe mieć poniŜszą postać: ‘GENVAL,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30’ W podanym łańcuchu GENVAL dotycz określenia rodzaju danych wysyłanych do generatora. W tym przypadku następujące po GENVAL wartości numeryczne to kolejne wartości sygnału generowanego. Na wyjściu generatora dostaniemy sygnał liniowo rosnący. Listing MEX-pliku słuŜący do utworzenia takiego łańcucha ma następująca postać: #include "mex.h" #include "stdio.h" #include "stdlib.h" 4 Grzegorz Psuj, Katedra Elektrotechniki Teoretycznej i Informatyki Kontakt: e-mail: [email protected], tel: 91 449 47 27 void mexFunction( int nlhs, // mxArray *plhs[], // int nrhs, // const mxArray *prhs[] // ) { int a, mrows, ncols; long j,n; double *tab; char *input_buf; int buflen; double ttmp; char *ptr; char tmp[5]; j=0; n = 0; tab = mxGetPr(prhs[1]); Number of left hand side (output) arguments Array of left hand side arguments Number of right hand side (input) arguments Array of right hand side arguments /* Check for proper number of arguments. */ if (nrhs != 2) { mexErrMsgTxt("Two inputs required"); } else if (nlhs > 2) { mexErrMsgTxt("Too many output arguments"); } if (mxIsChar(prhs[0]) != 1) mexErrMsgTxt("Input must be a string."); if (mxGetM(prhs[0]) != 1) mexErrMsgTxt("Input must be a row vector."); mrows = mxGetM(prhs[1]); ncols = mxGetN(prhs[1]); if (!mxIsDouble(prhs[1]) || mxIsComplex(prhs[1]) || (mrows != 1)) { mexErrMsgTxt("Input must be a noncomplex vector double."); } buflen = (mxGetM(prhs[0]) * mxGetN(prhs[0])) + 1; // for (a=0;a<ncols;a++) { ttmp = abs((int)tab[a]); if(ttmp<10) n++; else if(ttmp<100) n+=2; else if(ttmp<1000) n+=3; else if(ttmp<10000) n+=4; else n+=5; if(tab[a]<0) n++; } mexPrintf("%d",n); input_buf = mxCalloc(buflen, sizeof(char)); mxGetString(prhs[0], input_buf, buflen); 5 Grzegorz Psuj, Katedra Elektrotechniki Teoretycznej i Informatyki Kontakt: e-mail: [email protected], tel: 91 449 47 27 ptr = mxCalloc(n+ncols+buflen-1, sizeof(char)); j+=sprintf(ptr+j,"%s",input_buf); for (a=0;a<ncols;a++) { j+=sprintf(ptr+j,"%s",_itoa((int)tab[a],tmp,10)); if (a!=ncols-1) j+=sprintf(ptr+j,","); } plhs[0] = mxCreateString(ptr); } Proszę wpisać powyŜszą funkcję do edytora a następnie zapisać jako faststring.c. Kolejnym krokiem będzie określenie kompilatora. W tym celu proszę uŜyć polecenia: >> mex –setup i wybrać kompilator Microsoft Visual C/C++ [3]. Po wybraniu kompilatora naleŜy przeprowadzić kompilację stworzonego MEX-pliku. W tym celu naleŜy uŜyć polecenia: >> mex faststring.c Po skompilowaniu MEX-pliku naleŜy napisać skrypt pozwalający porównać czasy generacji łańcuch przy uŜyciu wbudowanych w MATLABa funkcji i stworzonego MEX-pliku. Zakładamy Ŝe poŜądany ciąg ma zawierać 4096 wartości rosnących od 1 do 4096. Przydatne funkcje: − tic, toc: podaje czas pomiędzy komendą tic a toc tic operacje time=toc − s = sprintf(format, A, ...): zapisuje sformatowane dane do łańcucha. format %c – pojedynczy znak %d notyfikacja dziesiętna %s ciąg znaków Przykłady skryptu: 1) % ilosc probek sygnalu podawanego na wyjscie generatora N=4096; %generacja lancuch przy uzyciu wbudowanych w srodowisko MATLAB funkcji tic str = sprintf('GENVAL') for a = 1:N str = sprintf('%s,%d',str,a); % wywołanie rekurencyjne end m_time=toc %generacja lancuch przy uzyciu stowrzonego MEX-pliku tic faststring('GENVAL',[1:N]); c_time=toc coef=m_time/c_time 6 Grzegorz Psuj, Katedra Elektrotechniki Teoretycznej i Informatyki Kontakt: e-mail: [email protected], tel: 91 449 47 27 2) % ilosc probek sygnalu podawanego na wyjscie generatora N=65536; %generacja lancuch przy uzyciu wbudowanych w srodowisko MATLAB funkcji tic s=sprintf('%d,',[1:N]); str=['GENVAL,',s] ; m_time=toc %generacja lancuch przy uzyciu stowrzonego MEX-pliku tic faststring('GENVAL',[1:N]); c_time=toc coef=m_time/c_time 7 Grzegorz Psuj, Katedra Elektrotechniki Teoretycznej i Informatyki Kontakt: e-mail: [email protected], tel: 91 449 47 27