MEX化もどき

compilerがあるのを確認... 1

ilib_buildを使う方法... 1

ilib_mex_buildを使う方法... 1

ilib_for_linkを使う方法... 1

1×1の行列... 1

2×3の行列... 1

 

MATLABMEXファイル化のScilab版です。

Scilab6.0.0compilerMS Visual C++ 2013です。

 

compilerがあるのを確認

スタートアップを実行中:

  初期環境をロードしています

 

--> haveacompiler()

 ans  =

 

  T

 

 

-->

haveacompiler()compilerがあるか何かを判別する関数です。

Tとならなかったらcompilerがありません。諦めましょう。

 

ilib_buildを使う方法

API_scilabを使います。APIapplication programming interfaceの略です。

ここで注意が必要です。api_scilabの使い方、Scilab5Scilab6で変更になっているようです。

Scilab 5だと

int sci_foo(char *fname, unsigned long fname_len)

といった書き方ですが、Scilab 6だと

int sci_foo(char *fname, void *pvApiCtx)

と書きます。

ScilabのヘルプメニューやホームページはScilb 5用のものが主なのでScilab 6でやると

error C2065: 'pvApiCtx' : 定義されていない識別子です。  

とエラーが出ます。例えばホームページのGetting standard with API Scilab(2018815日時点)sci_foo.cScilab 5用の上記の書き方、C:\Program Files\scilab-6.0.1\contrib\toolbox_skeleton\sci_gateway\csci_foo.cScilab6用の下記の書き方になっています。

以下のようなtest03.cファイルを作成しました。b=test03mex(a1,a2,a3)という関数です。a1,a2,a3は同じ大きさの配列です。

#include "api_scilab.h"

#include <localization.h>//_(**)のときに必要?

#include <Scierror.h>//Scierrorを使うとき必要

#include <malloc.h>

//#include "sci_malloc.h" //Scilab 6以上でmallocを使うとき必要な筈なんだけど…

 

int test03(char *fname, void* pvApiCtx)

{

        //エラー用の変数

        SciErr sciErr;

 

        //1引数

        int m1 = 0, n1 = 0;

        int *piAddressVar1 = NULL;

        double *matrixOfDouble1 = NULL;

 

        //2引数

        int m2 = 0, n2 = 0;

        int *piAddressVar2 = NULL;

        double *matrixOfDouble2 = NULL;

 

        //3引数

        int m3 = 0, n3 = 0;

        int *piAddressVar3= NULL;

        double *matrixOfDouble3 = NULL;

 

        //1戻り値

        double *newMatrixOfDouble = NULL;

 

        int i = 0;

 

        //引数の数と戻り値の数のcheck

        // b = test03(a1,a2,a3)

        CheckInputArgument(pvApiCtx, 3,3);//引数は 3個以上、3個以下

        CheckOutputArgument(pvApiCtx, 1,1);//戻り値は1個以上、1個以下

 

        //1引数のcheck

        //address取得

        sciErr = getVarAddressFromPosition(pvApiCtx, 1, &piAddressVar1);

        if (sciErr.iErr)

        {

                 printError(&sciErr, 0);

                 return 0;

        }

        //doubleで虚数でない

        if (!isDoubleType(pvApiCtx, piAddressVar1) || isVarComplex(pvApiCtx, piAddressVar1))

        {

                 Scierror(999, _("%s: Wrong type for input argument #%d: A real matrix expected.\n"), fname, 1);

                 return 0;

        }

        //行列の取得

        sciErr = getMatrixOfDouble(pvApiCtx, piAddressVar1, &m1, &n1, &matrixOfDouble1);

        if (sciErr.iErr)

        {

                 printError(&sciErr, 0);

                 return 0;

        }

 

        //2引数のcheck

        //address取得

        sciErr = getVarAddressFromPosition(pvApiCtx, 2, &piAddressVar2);

        if (sciErr.iErr)

        {

                 printError(&sciErr, 0);

                 return 0;

        }

        //doubleで虚数でない

        if (!isDoubleType(pvApiCtx, piAddressVar2) || isVarComplex(pvApiCtx, piAddressVar2))

        {

                 Scierror(999, _("%s: Wrong type for input argument #%d: A real matrix expected.\n"), fname, 2);

                 return 0;

        }

        //行列の取得

        sciErr = getMatrixOfDouble(pvApiCtx, piAddressVar2, &m2, &n2, &matrixOfDouble2);

        if (sciErr.iErr)

        {

                 printError(&sciErr, 0);

                 return 0;

        }

 

        //3引数のcheck

        //address取得

        sciErr = getVarAddressFromPosition(pvApiCtx, 3, &piAddressVar3);

        if (sciErr.iErr)

        {

                 printError(&sciErr, 0);

                 return 0;

        }

        //doubleで虚数でない

        if (!isDoubleType(pvApiCtx, piAddressVar3) || isVarComplex(pvApiCtx, piAddressVar3))

        {

                 Scierror(999, _("%s: Wrong type for input argument #%d: A real matrix expected.\n"), fname, 3);

                 return 0;

        }

        //行列の取得

        sciErr = getMatrixOfDouble(pvApiCtx, piAddressVar3, &m3, &n3, &matrixOfDouble3);

        if (sciErr.iErr)

        {

                 printError(&sciErr, 0);

                 return 0;

        }

 

        //1戻り値用の行列の作成

        newMatrixOfDouble = (double*)malloc(sizeof(double)* m1 * n1);

 

        if (((m1 == m2) && (m2 == m3)) && ((n1 == n2) && (n2 == n3))) //1引数+2引数+3引数

        {

                 for (i = 0; i < m1 * n1; i++)

                 {

                         newMatrixOfDouble[i] = matrixOfDouble1[i] + matrixOfDouble2[i] + matrixOfDouble3[i];

                 }

        }

        else //エラー処理

        {

                 Scierror(999, _("%s: Wrong size for input arguments: Same size expected.\n"), fname, 1);

                 return 0;

        }

 

        //1戻り値用行列のポインタ作成

        sciErr = createMatrixOfDouble(pvApiCtx, nbInputArgument(pvApiCtx) + 1, m1, n1, newMatrixOfDouble);

        if (sciErr.iErr)

        {

                 printError(&sciErr, 0);

                 return 0;

        }

 

        //1戻り値をScilabに渡す

        AssignOutputVariable(pvApiCtx, 1) = nbInputArgument(pvApiCtx) + 1;

 

        ReturnArguments(pvApiCtx);

 

        return 0;

 

}

MATLABでいうMEX化します。

ilib_buildという関数を使います。

library_nameは適当です。tablescilab上で使う関数名、C言語上の関数名のペアのセットです。

--> table=['test03mex','test03'];//scilab-name & interface-name

 

--> files=['test03.c'];

 

--> ilib_build('library_name',table,files,[]);

   ゲートウェイファイルを生成

   ローダファイルの生成

   Makefileの生成

   Makefile を実行中

   コンパイル library_name.cpp

   コンパイル library_name.h

   コンパイル library_name.hxx

   コンパイル test03.c

   共有ライブラリを構築中 (お待ちください)

   クリーナ・ファイルの生成

 

-->

これでlibrary_name.dllというファイルが作成されます。

c_link関数でリンクの有無をチェックし、なければaddinter関数でリンクします。

--> [bOK,ilib]=c_link('library_name')

 ilib  =

 

  -1.

 

 bOK  =

 

  F

 

 

--> addinter('library_name.dll','library_name',['test03mex']);

共有アーカイブをロードしました。

リンク完了

1×1の行列で試してみます。

--> a1=0.3,a2=2,1,a3=-0.9

 a1  =

 

   0.3

 

 a2  =

 

   2.

 

 ans  =

 

   1.

 

 a3  =

 

  -0.9

 

 

--> test03mex(a1,a2,a3)

 ans  =

 

   1.4

 

 

--> a1+a2+a3

 ans  =

 

   1.4

3×5の行列で試してみます。

--> a1=rand(3,5),a2=rand(3,5),a3=rand(3,5)

 a1  =

 

   0.2113249   0.3303271   0.8497452   0.068374    0.7263507

   0.7560439   0.6653811   0.685731    0.5608486   0.1985144

   0.0002211   0.6283918   0.8782165   0.6623569   0.5442573

 

 a2  =

 

   0.2320748   0.8833888   0.9329616   0.3616361   0.4826472

   0.2312237   0.6525135   0.2146008   0.2922267   0.3321719

   0.2164633   0.3076091   0.312642    0.5664249   0.5935095

 

 a3  =

 

   0.5015342   0.6325745   0.0437334   0.4148104   0.7783129

   0.4368588   0.4051954   0.4818509   0.2806498   0.211903

   0.2693125   0.9184708   0.2639556   0.1280058   0.1121355

 

 

--> test03mex(a1,a2,a3),a1+a2+a3

 ans  =

 

   0.9449338   1.8462904   1.8264403   0.8448205   1.9873107

   1.4241263   1.72309     1.3821827   1.1337251   0.7425893

   0.4859969   1.8544716   1.4548141   1.3567877   1.2499023

 

 ans  =

 

   0.9449338   1.8462904   1.8264403   0.8448205   1.9873107

   1.4241263   1.72309     1.3821827   1.1337251   0.7425893

   0.4859969   1.8544716   1.4548141   1.3567877   1.2499023

配列の大きさが異なる場合はエラーが出ます。

--> test03mex(1,2,[])

 

test03mex: 入力引数 の大きさが間違っています: 同じ大きさで指定してください.

リンクを解除する方法は以下の通りです。

clearfun関数で関数をクリアし、linkで目的のlibraryの順番を調べ(0,1,2,3,…の順)ulink(順番)とします。

--> clearfun('test03mex')

 ans  =

 

  T

 

 

--> test03mex(0,0,0)

 

未定義の変数: test03mex

 

--> link

 ans  =

 

 library_name

 

 

--> ulink(0)

 

--> link

 ans  =

 

    []

 

 

ilib_mex_buildを使う方法

MATLABは行列演算は高速なのですが、for loop処理は遅いです。そこでC言語で高速化した関数を(MEXファイル)を使うことで、演算処理時間を短縮することができます。SCILABも行列演算は高速なのですが、for loop処理は遅いです。そこでSCILABにもMATLABMEX化と同様にC言語で高速化した関数を使うという方法があります。MATLABSCILABで高速化するC言語の元のファイルが共通であれば、codingの手間が省けます。そこでSCILABにはilib_mex_buildという関数が用意されています。残念ながら一旦MATLABMEX化したファイルをSCILABで使う、というものではないようです。

 

以下のファイルをtest06.cという名前で保存しました。

#include "mex.h"

 

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])

{

        //nlhs 戻り値の数

        //plhs 戻り値のポインタ

        //nrhs 引数の数

        //prhs 引数のポインタ

        int row0, row1, col0, col1;

        int i = 0, j = 0;

        double *matrix0, *matrix1, *matrix2;

 

        //1引数

        matrix0 = mxGetPr(prhs[0]);//double行列である第1引数のポインタ

        row0 = (int)mxGetM(prhs[0]);//1引数の行 本来型は size_t

        col0 = (int)mxGetN(prhs[0]);//1引数の列

 

        //2引数

        matrix1 = mxGetPr(prhs[1]);//double行列である第2引数のポインタ

        row1 = (int)mxGetM(prhs[1]);//2引数の行

        col1 = (int)mxGetN(prhs[1]);//2引数の列

 

        if (row0 != row1){ return; }

        if (col0 != col1){ return; }

 

        //1戻り値

        plhs[0] = mxCreateDoubleMatrix(row0, col0, mxREAL);//実数doubleの行列を作成

        matrix2 = mxGetPr(plhs[0]);

        for (j = 0; j < col0; j++)

        {

                 for (i = 0; i < row0; i++)

                 {

                         *(matrix2++) = *(matrix0++) + *(matrix1++);

                 }

        }

}

ホントは引数の数や型のチェックとか例外処理をいろいろ付け足すんでしょうが、とりあえず、なんで無視しています。

注:C++(ファイル名.cpp)だと、MATLABではMEX化できても、SCILABだと、buildできないことがあるので、C(ファイル名.c)にした方が無難です。

 

MATLAB R2013abuildし、実行してみます。

>> mex test06.c

>> a=rand(3,4);b=rand(3,4);

>> test06(a,b),a+b

 

ans =

 

    1.2580    0.4209    0.4636    1.5981

    1.2175    0.3912    1.3544    0.3411

    0.4624    1.7076    0.7040    1.0968

 

 

ans =

 

    1.2580    0.4209    0.4636    1.5981

    1.2175    0.3912    1.3544    0.3411

    0.4624    1.7076    0.7040    1.0968

 

>>

test06.cmex化すると、64bit Windows10の場合test06.mexw64というファイルが作成されます。

同じファイルをSCILAB 6.0.1buildし実行してみます。

--> ilib_mex_build('test06mex',['test06','test06','cmex'],[],[],'','','','');

   ゲートウェイファイルを生成

   ローダファイルの生成

   Makefileの生成

   Makefile を実行中

   コンパイル test06mex.cpp

   コンパイル test06mex.h

   コンパイル test06mex.hxx

   共有ライブラリを構築中 (お待ちください)

   クリーナ・ファイルの生成

 

--> addinter('test06mex.dll','test06mex','test06');

共有アーカイブをロードしました。

リンク完了

 

--> a=rand(3,4);b=rand(3,4);

 

--> test06(a,b),a+b

 ans  =

 

   0.9376755   0.5624019   1.733134    1.0013357

   0.9545582   0.8966048   1.3382445   0.7754494

   0.5444785   0.8448551   1.1858256   0.9749989

 

 ans  =

 

   0.9376755   0.5624019   1.733134    1.0013357

   0.9545582   0.8966048   1.3382445   0.7754494

   0.5444785   0.8448551   1.1858256   0.9749989

 

 

-->

共通のCファイルからMATLBSCILAB用の実行ファイルが作成できました。めでたし、めでたし。

 

ilib_for_linkを使う方法

ちょっと昔の方法です。使い勝手は悪いです。

1×1の行列

以下のようなtest01.cファイルを作成します。

#include<stdio.h>

void test01mex(double *a, double *b, double *c)

{

        c[0] = a[0] + b[0];

}

MATLABでいうMEX化します。

ilib_for_link('関数名','ファイル名',[],'c')という引数になります。

関数名はvoid以下の関数名になるので、ここではtest01mexになります。

[]は本来リンクだそうですが、空行列にします。

最後の'c'C言語という意味です。'c++'にすればC++言語になる筈なのですが、まだ成功していません。

--> ilib_for_link('test01mex','test01.c',[],'c')

   ローダファイルの生成

   Makefileの生成

   Makefile を実行中

   コンパイル test01.c

   共有ライブラリを構築中 (お待ちください)

   クリーナ・ファイルの生成

 ans  =

 

 libtest01mex.dll

 

 

-->

lib関数名.dllというファイルが作成されます。

MEXファイルと違って、リンクをつながないといけないようです。

link(lib関数名.dll,[関数名],'c')とします。

--> link('libtest01mex.dll',['test01mex'],'c')

共有アーカイブをロードしました。

リンク完了

 ans  =

 

   0.

 

 

-->

リンクした番号は0です。中止する時はulink(0)などとします。

実際の使い方はcall関数を使います。

call(関数名,引数,1,,引数,2,,・・・・,'out',[m,n],k,)とかになります。

型はd64bit浮動小数点doublei32bit整数integerc8bitの文字characterです。rrealだそうですが、よくわかりません。

ということは16bitの整数や32bitの浮動小数点で出力できない!ということになります。

--> a=1.5;b=3.2;

 

--> c=call('test01mex',a,1,'d',b,2,'d','out',[1,1],3,'d');

 

--> c

 c  =

 

   4.7

 

 

-->

 

2×3の行列

以下のようなtest02.cファイルを作成しました。

#include<stdio.h>

void test02mex(int *m,int *n,double *a, double *b, double *c)

{

        int mm, nn;

        for (nn = 0; nn < *n; ++nn)

        {

                 for (mm = 0; mm < *m; ++mm)

                 {

                         *c = *a + *b;

                         a++;

                         b++;

                         c++;

                 }

        }

}

久々にC書くんでインクリメント++の使い方忘れてました・・・。この書き方だと間違えてないはず。

コンパイルしてリンクします。

--> ilib_for_link('test02mex','test02.c',[],'c');

   ローダファイルの生成

   Makefileの生成

   Makefile を実行中

   コンパイル test02.c

   共有ライブラリを構築中 (お待ちください)

   クリーナ・ファイルの生成

 

--> link('libtest02mex.dll',['test02mex'],'c');

共有アーカイブをロードしました。

リンク完了

 

-->

で実行します。

--> a=rand(3,5)

 a  =

 

   0.8833888   0.9329616   0.3616361   0.4826472   0.5015342

   0.6525135   0.2146008   0.2922267   0.3321719   0.4368588

   0.3076091   0.312642    0.5664249   0.5935095   0.2693125

 

 

--> b=rand(3,5)

 b  =

 

   0.6325745   0.0437334   0.4148104   0.7783129   0.6856896

   0.4051954   0.4818509   0.2806498   0.211903    0.1531217

   0.9184708   0.2639556   0.1280058   0.1121355   0.6970851

 

 

--> call('test02mex',3,1,'i',5,2,'i',a,3,'d',b,4,'d','out',[3,5],5,'d')

 ans  =

 

   1.5159633   0.9766951   0.7764465   1.2609601   1.1872238

   1.0577089   0.6964517   0.5728765   0.5440749   0.5899804

   1.2260799   0.5765976   0.6944307   0.7056449   0.9663975

 

 

--> a+b

 ans  =

 

   1.5159633   0.9766951   0.7764465   1.2609601   1.1872238

   1.0577089   0.6964517   0.5728765   0.5440749   0.5899804

   1.2260799   0.5765976   0.6944307   0.7056449   0.9663975

 

 

-->

最後は検算です。

MATLABだとポインタを使ってCファイル内にmxGetMとかmxGetNとかで行・列の大きさを知ることができるのですが、Scilabilib_for_link関数にはそのような機能はありません。面倒ですがいちいち引数内で指定しておく必要があります。