決定寫 C library 之前,建議是先用 profile reporter 確認程式的效能瓶頸。針對造成瓶頸的函式再花時間去打造其 C library 比較有經濟效益。
供 Matlab 連結使用之 C DLL 程式碼可分為兩個部分:第一部分為介面程式,用來處理 Matlab 與 C 之間的變數型態轉換;另一部分為功能主體,用來做函數所需要的運算。該 DLL 必須遵循 mexFunction 架構,並適當轉換輸入及輸出變數型態。
mexFunction 架構
典型的 mexFunction 程式碼的架構為
#include "mex.h" void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { /* parse input arguments */ /* create output arguments */ /* base function */ }
首先必須要引入標頭檔 mex.h,這裡面有 Matlab 矩陣型態的定義以及各個輔助函式的 prototype。接著宣告進入點函數 mexFunction(...),差不多就是 main(...) 的意思,這函數的 prototype 是不可以改變的:
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]);
mexFunction 包含四個引數,分別為
nlhs:呼叫函式時,等號左邊的變數數目,即 plhs 之個數 plhs:呼叫函式時,等號左邊之變數群 nrhs:呼叫函式時,等號右邊的變數數目,即 prhs 之個數 prhs:呼叫函式時,等號右邊之變數群
由於 Matlab 的函數呼叫允許多個輸出參數,如:
[M, I] = larger(A, B);
呼叫時 nlhs 以及 plhs 即用來存放等號左邊的輸出參數 M 與 I,nrhs 與 prhs 即存放等號右邊的輸入參數 A 及 B。此時,nlhs 及 nrhs 均為 2 。但如果使用者以
M = larger(A, B);
的形式呼叫,這時候 nlhs 會為 1。DLL 裡面可以依照 nlhs 及 nrhs 的不同,判斷使用者呼叫的輸入及輸出型態,以便進行 function overloading。
Matlab 中所有的變數,無論是矩陣或純量型態,在 mexFunction 中都以 mxArray 型態表示,純量就視為一個「大小為1x1之矩陣」。plhs 及 prhs 之變數型態為 mxArray **,亦即一「指向 Matlab 矩陣之指標陣列」。以前述 [M, I]=larger(A, B);
的例子來看,plhs[0] 指向 M 而 plhs[1] 指向 I,prhs[0] 指向 A 而 prhs[1] 則指向 B。
Matlab 矩陣與 C 陣列轉換
mexFunction 中的 mxArray 只是一個指標,實際仍然是 Matlab 之資料型態,並無法直接被 C 程式碼使用。要用 Matlab 提供的一些輔助函式進行轉換,從 mxArray 中取得可供 C 使用的數值。詳細可以參考 Matlab 手冊中關於 MX-Functions 的部分。
要從 mxArray 裡面取得資料,我們至少必須知道兩件事:矩陣的大小和矩陣的內容。矩陣大小可由 mxGetM 和 mxGetN 取得,矩陣內容則由 mxGetPr 以及 mxGetPi 取得。
int mxGetM(const mxArray *array_ptr); int mxGetN(const mxArray *array_ptr);
mxGetM 及 mxGetN 分別用來取得矩陣之第一維以及第二維尺寸。函數傳回一個 int 整數,即為對應之尺寸大小。若矩陣超過二維,則必須使用 mxGetNumberOfDimensions 及 mxGetDimensions 函數。
double *mxGetPr(const mxArray *array_ptr); double *mxGetPi(const mxArray *array_ptr);
mxGetPr 及 mxGetPi 則分別用來取出矩陣的實數與虛數部分,並回傳一個 double * 指向所取出資料陣列的開頭。無論原始矩陣的維度(dimension)是多少,所取出之資料均為一個一維陣列,因此必須配合由 mxGetM 及 mxGetN 取得的矩陣大小並計算正確之索引位置。
配合上述輔助函式,可利用下列程式碼,把 [M, I] = larger(A, B);
中矩陣 A 的實數部分變成 C 的變數:
double *A; int mA, nA; A = mxGetPr(prhs[0]); mA = mxGetM(prhs[0]); nA = mxGetN(prhs[0]);
接著便可以使用 A[i] 來存取矩陣,如果是二維陣列就使用 A[i+j*mA] 之方式。
建立輸出矩陣
函式既然是給 Matlab 用的,輸出當然也必須是 Matlab 矩陣。輸出矩陣的大小及內容是由 C DLL 決定,所以輸出必須由 DLL 負責建立。利用 mxCreateDoubleMatrix 我們可以建立 double 型態的輸出矩陣。
mxArray *mxCreateDoubleMatrix(int m, int n, mxComplexity ComplexFlag);
這函數可創造一個任意大小、實數或複數型態的二維 Matlab 矩陣。使用時給定矩陣的大小(m-by-n)以及矩陣的型態(mxREAL或mxCOMPLEX擇一)。函式即會創造一個新的 Matlab 矩陣,配置足夠的空間,並回傳一個 mxArray 指標指向所創造的新矩陣。若要創造多維矩陣或非實數/虛數型態之矩陣,可參考 mxCreateNumericArray 以及 mxCreateNumericMatrix。
此處用 mxCreateDoubleMatrix 建立輸出矩陣 M 及 I,並利用前述 mxGetPr 取得輸出矩陣對應之 C 陣列:
double *M, *I; plhs[0] = mxCreateDoubleMatrix(mA, nA, mxREAL); M = mxGetPr(plhs[0]); plhs[1] = mxCreateDoubleMatrix(mA, nA, mxREAL); I = mxGetPr(plhs[1]);
上面程式碼將 M 與 I 設定為與 A 相同大小之實數矩陣,並取出其資料指標 M 及 I 以備寫入輸出資料用。做好輸入及輸出參數的設置,接下來就依照一般 C 語法撰寫函式的主體功能部分,寫完後存檔為 larger.c:
#include "mex.h" void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { double *A, *B, *M, *I; int m, n, s, i; /* parse input arguments */ A = mxGetPr(prhs[0]); B = mxGetPr(prhs[1]); m = mxGetM(prhs[0]); n = mxGetN(prhs[0]); /* create output arguments */ plhs[0] = mxCreateDoubleMatrix(m, n, mxREAL); M = mxGetPr(plhs[0]); if(nlhs>1) { plhs[1] = mxCreateDoubleMatrix(m, n, mxREAL); I = mxGetPr(plhs[1]); } /* base function */ s = m*n; if(nlhs==1) { for(i=0; i<s; i++) { if(A[i]>=B[i]) M[i] = A[i]; else M[i] = B[i]; } } else { for(i=0; i<s; i++) { if(A[i]>=B[i]) { M[i] = A[i]; I[i] = 0; } else { M[i] = B[i]; I[i] = 1; } } } }
設定編譯器
在 Matlab 下編譯 C DLL 的指令為 mex。使用 mex 之前必須設定編譯使用的 C 編譯器。在 Matlab 下輸入 mex -setup 指令進行設定:
>> mex -setup Select a compiler: [1] Lcc C version 2.4 in C:\\PROGRAM FILES\\MATLAB704\\syslcc [0] None Compiler:
mex 會偵測電腦中已安裝適合的C編譯器,Lcc 是 Matlab 內建的,可以用不用怕。
編譯
選好編譯器後就可以開始編譯了,假設剛剛寫好的檔案存檔為 larger.c,將 Matlab 工作目錄切換至剛剛存檔 larger.c 的目錄,執行:
mex larger.c
執行完畢後就可以在該目錄下看見編譯好的函式庫 larger.dll,如果失敗就要依照錯誤訊息檢查 DLL 程式碼以及 mex 設定。
測試
現在可以來測試看看編譯好的 DLL:
>> A=[1 2 3; 4 5 6; 7 8 9]; >> B=[9 8 7; 6 5 4; 3 2 1]; >> [M,I]=larger(A, B); >> M M = 9 8 7 6 5 6 7 8 9 >> I I = 1 1 1 1 0 0 0 0 0 >>
也可以測試一下 M=larger(A,B); 是否能正常工作。接著試試看
>> larger(A,B) ------------------------------------------------------------------------ Segmentation violation detected at Sat Dec 10 09:52:22 2005 ------------------------------------------------------------------------ (以下刪除)
出現 segmentation fault,這是因為程式碼並沒有考慮「未指定任何輸出」,即 nlhs 為 0 時的情形。一般函式在沒有指定輸出的情況下,應該要傳回一個輸出結果。把程式碼中,
if(nlhs==1) {
這一行,修正為
if(nlhs<=1)
就可以解決這個問題。