MEX化もどき
MATLABのMEXファイル化のScilab版です。
Scilabは6.0.0、compilerはMS Visual C++ 2013です。
スタートアップを実行中:
初期環境をロードしています
-->
haveacompiler()
ans =
T
-->
haveacompiler()はcompilerがあるか何かを判別する関数です。
Tとならなかったらcompilerがありません。諦めましょう。
API_scilabを使います。APIはapplication programming interfaceの略です。
ここで注意が必要です。api_scilabの使い方、Scilabの5とScilabの6で変更になっているようです。
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(2018年8月15日時点)のsci_foo.cはScilab 5用の上記の書き方、C:\Program Files\scilab-6.0.1\contrib\toolbox_skeleton\sci_gateway\cのsci_foo.cはScilab6用の下記の書き方になっています。
以下のような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は適当です。tableはscilab上で使う関数名、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 =
[]
MATLABは行列演算は高速なのですが、for loop処理は遅いです。そこでC言語で高速化した関数を(MEXファイル)を使うことで、演算処理時間を短縮することができます。SCILABも行列演算は高速なのですが、for loop処理は遅いです。そこでSCILABにもMATLABのMEX化と同様にC言語で高速化した関数を使うという方法があります。MATLABとSCILABで高速化するC言語の元のファイルが共通であれば、codingの手間が省けます。そこでSCILABにはilib_mex_buildという関数が用意されています。残念ながら一旦MATLABでMEX化したファイルを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 R2013aでbuildし、実行してみます。
>>
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.cをmex化すると、64bit Windows10の場合test06.mexw64というファイルが作成されます。
同じファイルをSCILAB 6.0.1でbuildし実行してみます。
-->
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ファイルからMATLBとSCILAB用の実行ファイルが作成できました。めでたし、めでたし。
ちょっと昔の方法です。使い勝手は悪いです。
以下のような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,型)とかになります。
型はdが64bit浮動小数点double、iが32bit整数integer、cが8bitの文字characterです。rはrealだそうですが、よくわかりません。
ということは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
-->
以下のような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とかで行・列の大きさを知ることができるのですが、Scilabのilib_for_link関数にはそのような機能はありません。面倒ですがいちいち引数内で指定しておく必要があります。