MEXファイルの作り方・使い方(C/C++ on Linux & Matlab 6R13 )

森 耕平 , 2005/06/10 - 2005/06/15
mori@cs.kobe-u.ac.jp

Intro.   スカラーの受渡し   行列の受渡し   ライブラリの利用   C++   おまけ

Intro.

MEXファイルとは,Matlabのスクリプトやコマンドラインから呼び出せるCやFortranのバイナリのことです. Matlabにはこの仕組みがあるので,CやFortranで書かれた過去の遺産(プログラム)の流用や,計算コストの高い部分をCで書くことによる計算の高速化や消費メモリの節約ができます. 特に高いスキルがなくても利用できます(といっても,関数やポインタを理解していないと無理ですが). 以下,Fortranは無視してCの場合に関してMEXファイルの作り方と使い方をちょっとだけ説明します. ちなみに,サイバネットシステムのウェブサイトには以下のように書かれています.
MATLABは,CやFortranのようなコンパイル言語において,時間のかかる低レベルのプログラミングを削減する特徴をもつ高生産性システムです. 一般に,ほとんどのプログラミングは,MATLABで行われるべきです. アプリケーションで必要としない限り,MEX機能は使わないでください.
Matlabの組み込み関数であろうと自分で書いたC言語のソースコードをコンパイルして作成されたバイナリであろうと,CPUの側から見ると違いはありません. 要は,Matlabが使用するデータ構造(行列とか)に関する記述をCのコードに加えてやればMatlabから呼び出せる関数(共有ライブラリ)を書けるので,MEXファイルの作り方も基本的には共有ライブラリの作り方と本質的な違いはありません.

サンプルプログラムその1(スカラーの受渡し)

以下のコードは,第2引数の正の平方根を第1引数に保存するC言語の関数です.
#include<math.h>
void mysqrt(double *y, double *x){
  *y = sqrt(*x);
}
これをMEXファイル用に書き直すと以下のようになります. ゴチャゴチャしていますが,本質的なポイントは次の二つだけです.
  1. 最初の「#include"mex.h"」によりMatlabが用いるデータ形式が読み込まれる.
  2. Matlab側の変数をC言語のポインタを用いて操作する.
/* mysqrt.c */
#include"mex.h"          /* Matlabのデータ構造の読み込み */
#include<math.h>

/* Matlabから呼び出したい関数   */
/* 「*x」の正の平方根を「*y」に格納する */
void mysqrt(double *y, double *x){
  *y = sqrt(*x);
}

/* operand[0][0]の正の平方根を returned[0][0]に格納する. */
/* ・mxArrayはMatlab中でのスカラデータ型に行と列の情報を加えたもの(たぶん)
     行列はmxArrayの一次元配列として扱われる.
   ・引数となる行列や戻り値となる行列がそれぞれ一つならば,使用されるのは
     returned[0],operand[0]だけであり,returned[1]などは使用されない.
   ・NreturnedとNoperandはreturnedなどの何番目までが使用されるかを表す.    */    
void mexFunction( int Nreturned, mxArray *returned[], int Noperand, const mxArray *operand[] ){
  double *x,*y;
  int rows,cols;
  
  /* Matlab側から受け取った行列の行数と列数を保存する  */
  /* この例では rows = cols = 1 としてもよい           */
  rows = mxGetM(*operand);
  cols = mxGetN(*operand);

  /* Matlab側に渡されるデータ(戻り値みたいなもの)を作る.C言語のmalloc()みたいなもの.        */
  /* Matlabの基本データ型は行列なので,行の数(rows),列の数(cols),要素が何か(mxREAL)を伝える */
  *returned = mxCreateDoubleMatrix(rows,cols, mxREAL);
  
  /* Matlab側の変数のアドレスをC側のポインタにコピーする */
  x = mxGetPr(*operand);
  y = mxGetPr(*returned);
  
  /* Matlabから呼び出したい関数 mysqrt() を呼び出す */
  mysqrt(y,x);
}
コンパイル方法と使用例は以下の通り. Matlabのプロンプトから行います.
>> mex mysqrt.c
>>i=0; while i<5, disp(mysqrt(i)), i=i+1; end
     0
     1
    1.4142
    1.7321
     2
コンパイルの結果,mysqrt.mexglx というファイルができています. 拡張子のmexglxがIA32(x86)互換CPU用のMEXファイルであることを表しており,mysqrtはMatlab側が認識する関数名です. mexコマンドはMatlabのディレクトリのtoolbox/matlab/general/mex.m,つまり,Matlabの関数の一つです.

mexがやっている内容は以下のようにすると表示されるので,その内容に従えばMatlabのコマンドプロンプトからではなくLinuxのコマンドラインからでもコンパイルできます.
>> mex -v mysqrt.c

サンプルプログラムその2(行列の受渡し)

次は行列を扱う例です. 新しく注意すべき点はただ一つ, ということです.
/* outer.c */
#include"mex.h"

/* a次元の列ベクトルxとb次元の列ベクトルyを受け取り,xx^Tとxy^Tを作る. */
/* [ 1 2  3 ; 4 5 6 ]は,一次元配列としては,以下のように格納される.
        1 4 2 5 3 6
   つまり,第1列,第2列,…の順番に格納される.この表記(Fortran風?)にあわせる.*/
void outer(double *xx, double *xy, int a, const double *x, int b, const double *y){
  int i,j;
  for(i=0;i<a;i++){
    for(j=0;j<a;j++)
      xx[i+j*a]=x[i]*x[j];
  }
  for(i=0;i<a;i++){
    for(j=0;j<b;j++)
      xy[i+j*a]=x[i]*y[j];
  }
}

/*
  Nreturnedは戻り値(戻される行列)の個数.returned[0],returned[1],returned[2]などが,その行列.
  Cのコード中では,returned[i]を,行の数と列の数がセットになった一次元配列と考えればよい.
  Noperandとoperandも同様.
*/
void mexFunction( int Nreturned, mxArray *returned[], int Noperand, const mxArray *operand[] ){
  double *XX,*XY, *X, *Y;
  int a,b;

  /* xとyの行の数を保存 */  
  a = mxGetM(operand[0]);
  b = mxGetM(operand[1]);

  returned[0] = mxCreateDoubleMatrix(a,a, mxREAL);
  returned[1] = mxCreateDoubleMatrix(a,b, mxREAL);
  
  /* Matlab側の変数のアドレスをC側の変数にコピーする */
  X = mxGetPr(operand[0]);
  Y = mxGetPr(operand[1]);
  XX = mxGetPr(returned[0]);
  XY = mxGetPr(returned[1]);
  
  outer(XX, XY, a, X, b, Y);
}
以下は実行結果.
>> mex outer.c
>> x = [ 1 2 3 ]'; , y= [ 4 5 6 7 ]'; disp(x'), disp(y'), [xx xy]=outer(x,y)
     1     2     3
     4     5     6     7
xx =
     1     2     3
     2     4     6
     3     6     9
xy =
     4     5     6     7
     8    10    12    14
    12    15    18    21

実際の使用状態に近い例(ライブラリの利用)

既に手元にあるC言語のコード(ライブラリ)を利用するためには次のようにします.

まず,利用したい機能をまとめたライブラリを作成します. 以下の例ではmysin()という単純な関数が対象ですが,実際にはmysin()が非常に複雑だと考えて下さい.
/* myfunc.h */
/* 再利用したいCのライブラリのヘッダファイル */
/* 引数の正の平方根を返す */
double mysin(double i);
/* mysin.c */
/* 再利用したいCのライブラリのソース */
#include<math.h>
#include"myfunc.h"
/* 引数のsinを返す */
double mysin(double x){
  return sin(x);
}
コンパイルコマンドは次の通り. ここはLinuxのコマンドラインから行います. arはライブラリを作成するコマンドです. この例のように使用するオブジェクトファイルが一つだけの場合にはlibmyfunc.aの代わりにmysin.oを使用してもかまいません(静的ライブラリの使用は管理をしやすくするため).
$ gcc -c mysin.c 
$ ar -r libmyfunc.a mysin.o
MEXのインターフェイス部分は既に示した例と同様です. コードの書くために新しい知識は必要ありません.
/* mysin_wrap.c */
#include"mex.h"
#include"myfunc.h"  /* 再利用したいライブラリのヘッダファイル */

/* MEXの利用のためのインターフェイスを提供するだけの関数  */
void mysin_wrap(double *y, double *x){
  *y=mysin(*x);
}

void mexFunction( int Nreturned, mxArray *returned[], int Noperand, const mxArray *operand[] ){
  double *x,*y;
  int rows,cols;
  rows = cols = 1;
  *returned = mxCreateDoubleMatrix(rows,cols, mxREAL);
  x = mxGetPr(*operand);
  y = mxGetPr(*returned);
  mysin_wrap(y,x);
}
MEXファイルの作成(コンパイル)と実行は,Matlabのプロンプトから次のようにやります. 「-lmyfunc」は「libmyfunc.aを材料にしますよ」というオプション,「-L./」はlibmyfunc.aの置き場所の指定です.
>> mex mysin_wrap.c -lmyfunc -L./ 
>> i=0; while i<pi, disp([i,mysin_wrap(i)]), i=i+0.1; end

LinuxなどでC++を使用してMEXファイルを作るときの注意

C++を使う場合にはコンパイラオプションに関する注意が必要な必要なようです. Debian GNU/Linux 3.1(sarge)とVine Linux 3.1で試してみたところ,デフォルトでは,ファイルの拡張子に従ってgcc,もしくはg++が呼び出され,それが原因で
Undefined Symbol : mysin
などの実行時エラーが出ることがあります.
// myfunc
// 再利用したいC++のライブラリのヘッダファイル 
// 引数の正の平方根を返す 
double mysin(double i);
// mysin.cc
// 再利用したいCのライブラリのソース
#include<cmath>
#include"myfunc"
// 引数のsinを返す
double mysin(double x){
  return sin(x);
}
/* mysin_wrap.c */
#include"mex.h"
#include"myfunc"  /* 再利用したいライブラリのヘッダファイル */

/* MEXの利用のためのインターフェイスを提供するだけの関数  */
void mysin_wrap(double *y, double *x){
  *y=mysin(*x);
}

void mexFunction( int Nreturned, mxArray *returned[], int Noperand, const mxArray *operand[] ){
  double *x,*y;
  int rows,cols;
  rows = cols = 1;
  *returned = mxCreateDoubleMatrix(rows,cols, mxREAL);
  x = mxGetPr(*operand);
  y = mxGetPr(*returned);
  mysin_wrap(y,x);
}
mysin.ccのコンパイルはごく普通に.
$ g++ -c mysin.cc
$ ar -r libmyfunc.a mysin.o
mexを使う時にオプションをつけます(C用コンパイラとC++用コンパイラをライブラリ作成時と同じにする).
>> mex CC=g++ CXX=g++ mysin_wrap.c -lmyfunc -L./
>> mysin_wrap(1.0), mysin_wrap(2.0), mysin_wrap(3.0), mysin_wrap(4.0), mysin_wrap(5.0)

おまけ

もちろん,以下のようなことも可能です.
/* mysys.c */
#include<stdlib.h>
#include"mex.h"
void mysys(void){
  system("kterm -e top");
}

void mexFunction( int Nreturned, mxArray *returned[], int Noperand, const mxArray *operand[] ){
  mysys();
}
MEXファイルの作成(コンパイル)と実行は,Matlabのプロンプトから次のようにやります. 実行するとktermが起動し,その中でtopコマンドが実行されます.
>> mex mysys.c 
>> mysys