週末!プログラミング部

ソフトウェア開発ネタを中心に自分でいろいろ調べた内容を自分の勝手な解釈で思うがままに書いくためのブログ。サンプルソースコード、API、プラットフォーム、プログラミング言語、開発環境などを調査、分析して追求いく予定です。

MATLABからCライブラリを呼び出してみる

前回は、MEX関数を使ってMATLABからCコードを呼び出してみました。

今回は、すこしだけ発展させて、MEX関数をライブラリ化してMATLABから呼び出してみたいと思います。 https://jp.mathworks.com/help/matlab/call-c-library-functions.html
MATLABが呼び出すCライブラリのサンプルは、MATLABをインストールしたときに一緒にインストールされています。
以下のコマンドを使うことでサンプルプログラムがインストールされているパスを調べることができます。 今回はこのサンプルプログラムを参考にしました。

>> fullfile(matlabroot,'extern','examples','shrlib')

簡単なライブラリを作ってみる

以下はライブラリの実装例です。
構造体TEST_DATAのポインタを引数にもらって、そのメンバ変数を加算するadd()と減算するsub()を持っています。
MEX関数を作ったときと同様にmex.hをincludeしてmexFunction()を定義していますが、mexFunction()ではなにも行いません。

#include <mex.h>
#include "test.h"

__declspec(dllexport) double add(TEST_DATA* data)
{
    double ret = 0;
    
    ret = data->p1 + data->p2 + data->p3;
    
    return ret;
}

__declspec(dllexport) double sub(TEST_DATA* data)
{
    double ret = 0;
    
    ret = data->p1 - data->p2 - data->p3;
    
    return ret;
}

/* MEX関数インタフェース */
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[])
{
    /* なにも行わない */
}

こちらはライブラリのヘッダファイルです。
構造体とMATLABから実行したい関数のプロトタイプを定義しておきます。

#ifndef TEST_H
#define TEST_H

typedef struct
{
    double   p1;
    double   p2;
    double   p3;
} TEST_DATA;

__declspec(dllexport) double add(TEST_DATA* data);
__declspec(dllexport) double sub(TEST_DATA* data);

#endif

動かしてみる

実行はMATLABコマンドラインから以下のように実行します。

% Cライブラリをビルド
>> mex test.c

'Microsoft Visual C++ 2019 (C)' でビルドしています。
MEX は正常に完了しました。

% Cライブラリをロード
>> loadlibrary('test', 'test.h');

% 構造体作成
>> st.p1 = 1;
>> st.p2 = 2;
>> st.p3 = 3;

% ポインター オブジェクトを作成
>> p = libpointer('TEST_DATA', st);

% 加算関数実行
>> a = calllib('test', 'add', p);
>> a

a =

     6

% 減算関数実行
>> a = calllib('test', 'sub', p);
>> a

a =

    -4

% ワークスペースの変数をクリア
>> clear

% Cライブラリをアンロード
>> unloadlibrary('test');

前回と同様に、mexを使用してビルドします。
ライブラリ名は同様にファイル名となります。

ビルド後はloadlibraryでライブラリを読み込みます。
このときMATLABから見えるCのインタフェースとしてヘッダファイルを指定します。

Cで定義したライブラリ関数はTEST_DATA型のポインタを引数に持ちます。
したがってMATLABではポインター オブジェクトを作成してやる必要があります。
ポインター オブジェクトはlibpointerで作成することができます。

ライブラリ関数はcalllibで呼び出すことができます。
引数にはライブラリ名、ライブラリ関数、ライブラリ関数が使用する引数を指定します。

ライブラリを使い終えたらunloadlibraryで開放します。
このときポインター オブジェクトを作成してライブラリ関数に引数として渡している、またはライブラリ関数の返値がポインター オブジェクトだった場合、 ワークスペースをクリアしない状態でunloadlibraryを呼ぶと以下のようなエラーが出ます。
したがってclear <変数名>を実行しておきます。

エラー: unloadlibrary
未解決オブジェクトをもつライブラリを解放できません。


以上がざっくりした使い方になります。
もうすこし詳しい使い方を知りたい場合は、サンプルのソースコードと公式ドキュメントを見れば、大体わかると思います!