Mathematica とのインタラクション
Wolfram LibraryLink を使うと,ダイナミックライブラリを Mathematica カーネルに直接ロードして,ライブラリの関数を即座に Mathematica 内部から呼び出せるようにすることができる.整数,実数,パックアレー,文字列等のCのようなデータ型だけでなく,任意の Mathematica 式も交換することができる.また,エラーを送ったり,Mathematica にコールバックしたりするような便利な関数もある.
このセクションでは,Wolfram Librariesを使うために Mathematica が提供する関数について述べる.
| LibraryFunctionLoad | ライブラリから関数をロードする |
| LibraryFunction | ライブラリからロードされた関数へのハンドルの表現 |
| LibraryFunctionUnload | 以前ライブラリからロードされた関数をアンロードする |
| LibraryUnload | 以前ライブラリからロードされた関数をすべてアンロードする |
| LibraryFunctionInformation | LibraryFunctionについての情報を返す |
| $LibraryPath | ライブラリの検索に使用されるパス |
| FindLibrary | ライブラリを検索する |
| LibraryLoad | 他のライブラリが必要とするライブラリをロードする |
LibraryFunctionLoadはライブラリから関数をロードし,LibraryFunctionを返す.
| In[1]:= |
| Out[1]= |
整数の引数でLibraryFunctionを呼び出す.
| In[2]:= |
| Out[2]= |
LibraryFunctionInformationはロード元のライブラリ,引数や戻り値型等のLibraryFunctionについての情報を返す.
| In[3]:= |
| Out[3]= | ![]() |
ライブラリの指定
LibraryFunctionLoadの第1引数はロードするライブラリである.これはC:\Libraries\myLibrary.dllのように絶対ファイル名で与えることができる.しかし,ライブラリにパスからの相対指定を渡す方がより便利なことがよくある.また,プラットフォームごとにライブラリの拡張子が異なるという問題もある.以下に規則をまとめる.
Mathematica はFindLibraryと$LibraryPathでこの問題を解決する.FindLibraryはプラットフォーム非依存であるライブラリ指定を取ることができ,それを$LibraryPath上で探して,ライブラリを見付けることができたらシステムの実際のファイルを返す.
次の例は,$LibraryPath上を検索して使用中のプラットフォームに適したライブラリを求める.この例ではWindowsである.
| In[4]:= |
| Out[4]= | ![]() |
$LibraryPathの設定方法についてのさらなる情報は「ライブラリを探す」を参照されたい.
関数名
LibraryFunctionLoadの第2引数はロードする関数の名前を与える.この関数は「Libraryの構成」に記載のように,ライブラリからエキスポートされなければならない.ライブラリをC++としてコンパイルする場合は,おそらくCの命名規則でエキスポートしなければならないだろう.これについても「Libraryの構成」に記載されている.
型指定
LibraryFunctionLoadの第3,第4引数は,引数と戻り値の型を指定するものである.
| "Boolean" | mbool | ブール値 |
| Integer | mint | 機械整数 |
| Real | double | 機械倍精度数 |
| Complex | mcomplex | 機械複素倍精度数 |
| {base,rank} | MTensor | 指定の基底型と階数のテンソル |
| {base,rank,memory} | MTensor | 指定のメモリ管理のテンソル |
| "UTF8String" | char * | UTF8文字列 |
| LinkObject | MLINK | 引数と MathLink に書き出された結果 |
| "Void" | 結果なし(戻るのみ) |
テンソルを指定する型は Mathematica のパックアレーに直接マップしするように,また,テンソルを Mathematica コンパイラで使用するように設計されている.これによりシステムの効率が格段によくなり,ライブラリがテンソル操作を利用できるようになる.テンソルは各要素の方と型と階数を明示的に指定することも,指定せずにおくこともできる.型と階数を指定しないでおくと,テンソルに作用するアプリケーションの柔軟性が増す.テンソルはInteger,Real,Complexについてのみサポートされていることに注意されたい.
テンソルが引数として渡される場合,そのメモリがどのように扱われるかも指定できる.これについては次のセクションで詳細を述べる.
ライブラリ関数のコードの中で,引数の配列から各型のデータを集めることができる.これを簡単にする目的で,MArgument用のマクロが提供されている.そのサンプルを以下に示す.
mint I0 = MArgument_getInteger(Args[0]);
double R0 = MArgument_getReal(Args[1]);
mcomplex C0 = MArgument_getComplex(Args[2]);
MTensor T0 = MArgument_getMTensor(Args[3]);
ライブラリ関数から戻るときは,結果に割り当てなければならない.以下に倍精度数を保管する例を示す.
MArgument_setReal(Res, R1);
テンソル入力を任意の型と階数で指定した場合,実際の型と階数は以下のようにコールバック関数で知ることができる.
type = libData->MTensor_getType( T0);
rank = libData->MTensor_getRank( T0);
MTensor_getTypeは結果の型を表す整数値を返す.
ライブラリ関数が引数を取らない場合は,入力指定に空のリストを渡せばよい.
| In[7]:= |
| Out[7]= |
| In[8]:= |
| Out[8]= |
| In[9]:= |
| Out[9]= |
| In[10]:= |
MTensorのメモリ管理
mint,double,mcomplex等の型は,C関数の呼出しで一般的なように,ライブラリへ,またはライブラリから値で渡される.ライブラリにおける使い方は Mathematica での使い方と全く異なる.
反対に,MTensorはデータ構造へのポインタであり,参照として渡される.そのため,メモリがどのように管理されるのかを考えなければならない.Mathematica は安全で簡単なデフォルトの技術を選択するが,大量のデータを渡したり,後で使用するために保存したりしたい場合は,その管理を考える必要がある.
MTensor入力引数
MTensorをライブラリ関数に渡すときは,どのようにして渡すかを指定する数々のオプションが使える.
| {Integer, 1} | MTensorのコピーを渡し,自動的に消す |
| {Integer, 1, Automatic} | MTensorのコピーを渡し,自動的に消す |
| {Integer, 1, "Constant"} | 変更できないMTensorへの参照を渡す |
| {Integer, 1, "Manual"} | MTensorのコピーを渡すが,自動的には消さない |
| {Integer, 1, "Shared"} | ライブラリと Mathematica の間で共有するMTensorへの参照を渡す |
自動渡し
自動引き渡しを選択した場合は,MTensorは関数が呼ばれる前にコピーされ,関数が戻るときに消される.これは以下のような関数に適していると考えられる.
DLLEXPORT int demo_TI_R(WolframLibraryData libData,
mint Argc, MArgument *Args, MArgument Res) {
MTensor T0;
mint I0;
double R0;
T0 = MArgument_getMTensor(Args[0]);
I0 = MArgument_getInteger(Args[1]);
R0 = libData->MTensorVector_getReal(T0, I0);
MArgument_setReal(Res, R0);
return LIBRARY_NO_ERROR;
}
この関数では,MTensorを解放する必要はない.しかし,MTensorへの参照を保存して関数の終了後に使うことはできない.
自動渡しで渡されたMTensorは,関数呼出しがアクティブである限り,ライブラリによってのみ読み書きアクセスの両方ができるように所有される.
不変渡し
不変渡しを選択した場合は,MTensorへの参照が渡され,関数はMTensorを変更することはないと想定される.これにより,実質的にはMTensorデータへの高速リードオンリーアクセスが可能となる.コードがこの想定に反してデータを変更すると,その Mathematica セッションで重大なエラーが生じる可能性がある.
不変渡しで渡されたMTensorは,関数呼出しがアクティブである限り,ライブラリによってのみ読み書きアクセスの両方ができるように所有される.
手動渡し
手動渡しを選択した場合は,MTensorは関数呼出しの前にコピーされ,関数が戻るときに消去されない.これは以下のような関数に適していると考えられる.
DLLEXPORT int demo1_TI_R(WolframLibraryData libData,
mint Argc, MArgument *Args, MArgument Res) {
MTensor T0;
mint I0;
double R0;
T0 = MArgument_getMTensor(Args[0]);
I0 = MArgument_getInteger(Args[1]);
R0 = funStruct->MTensorVector_getReal( T0, I0);
libData->MTensor_free(T0);
MArgument_setReal(Res, R0);
return LIBRARY_NO_ERROR;
}
関数がどのようにMTensorを解放したかに注目されたい.解放されなかったら,メモリは失われる.しかしテンソルを解放する代りに,テンソルを保存してそれをライブラリの他の部分で利用することもできる.最後にテンソルを使い終わったら,MTensor_freeを呼び出さなければならない.代りに,テンソルをライブラリ関数から Mathematica に返し,所有者を Mathematica に戻すという方法でもよい.
手動渡しで渡されたMTensorは完全にライブラリが所有し,この状態はMTensorが解放されるか Mathematica に戻されるまで続く.
共有渡し
共有渡しを選択した場合は,MTensorは関数が呼び出される前にはコピーされず,ライブラリ関数に直接渡される.これは以下のような関数に適していると考えられる.
DLLEXPORT int demo2_TI_R(WolframLibraryData libData,
mint Argc, MArgument *Args, MArgument Res) {
MTensor T0;
mint I0;
double R0;
T0 = MArgument_getMTensor(Args[0]);
I0 = MArgument_getInteger(Args[1]);
R0 = funStruct->MTensorVector_getReal( T0, I0);
libData->MTensor_disown( T0);
MArgument_setReal(Res, R0);
return LIBRARY_NO_ERROR;
}
引数に共有渡しを使うと,MTensorは Mathematica とライブラリの間で共有される.Mathematica はMTensorを表の中に保管し,MTensorのメモリが収集されないようにする.ライブラリがMTensorを使う必要がなくなったら,MTensor_disownを呼び出さなければ成らない.
共有渡しで渡されたMTensorはライブラリと Mathematica の間で共有される.この状態はライブラリと Mathematica が完全に使い終えるまで続く.
| In[1]:= |
| Out[1]= |
| In[2]:= |
| Out[3]= |
| In[4]:= |
| Out[4]= |
パックアレーではない引数で関数を呼び出すと,呼出しは動作するが,警告メッセージが返される.これは Mathematica が入力をパックアレーに変換しなければならないため,データをコピーし,共有渡しの利点の一つを失うからである.
MTensorの返し方
ライブラリ関数からMTensorを返すときは,メモリをどのように扱うかも制御する.
| {Integer, 1} | MTensorへの参照を Mathematica に返す |
| {Integer, 1, Automatic} | MTensorへの参照を Mathematica に返す |
| {Integer, 1, "Shared"} | ライブラリと Mathematica の間で共有されるMTensorへの参照を返す |
ライブラリ関数からのMTensorの結果に対する可能なメモリ管理
自動返し
自動返しを選択すると,MTensorはライブラリから Mathematica に直接戻る.Mathematica はそれをライブラリ関数の結果として使う.
DLLEXPORT int demo_I_T(WolframLibraryData libData,
mint Argc, MArgument *Args, MArgument Res) {
MTensor T0;
mint i, I0, rank, dims[1];
I0 = MArgument_getInteger(Args[0]);
dims[0] = I0;
T0 = libData->MTensor_new(MType_Integer, 1, dims);
for ( i = 1; i <= I0; i++) {
libData->MTensorVector_setInteger( T0, i, i*2);
}
MArgument_setMTensor(Res, T0);
return LIBRARY_NO_ERROR;
}
ライブラリがMTensorを所有している場合,つまりMTensorがライブラリ内で作られたり手動渡しで渡されたりした場合は,一旦MTensorが Mathematica に返されるとそれはもはやライブラリの所有ではなくなるため,ライブラリはMTensorを一切使わなくなる.これは上記関数に示されている.
MTensorがライブラリと Mathematica の間で共有されているなら,自動返しはMTensorの所有権については変更を加えない.しかし,Mathematica はすでにMTensorへの参照を持っているため,ライブラリから共有MTensorを返すというのは奇妙である.
共有返し
共有返しを選択すると,MTensorはライブラリと Mathematica の間で共有される.技術的な観点から言うと,これは関数が戻るときに共有表に加えることで実現される.これにより,MTensorが収集されないようにする.ライブラリがMTensorを必要としなくなったら,MTensor_disown(またはMTensor_disownAll)を呼び出さなければならない.
MTensorを共有表に加える前にMTensor_disown(またはMTensor_disownAll)を呼び出すと効果がなくなり,警告メッセージが示される.
MTensorメモリ管理のまとめ
MTensorのためのメモリ管理の3つの方法を理解する方法の一つは,MTensorがさまざまな要素の間でどのように所有されるかを考えるというものである.
自動渡しでは,ライブラリへの呼出しがアクティブなときは,MTensorはそのライブラリ関数のみが所有する.関数はMTensorが変更されていてもいなくてもMTensorを返すことができるが,関数が戻った後は何があってもMTensorを使うことはできない.
手動渡しでは,関数が呼ばれた後,MTensorはライブラリが所有する.MTensorは,例えば別の関数の中のその関数の後はいつでも使える.MTensorはライブラリ関数が返すかMTensor_freeへの呼出しに渡されるまで,ライブラリが所有し続ける.手動渡しで渡されたMTensorを戻したいが,所有主はライブラリにしておきたい場合は,MTensor_cloneを呼び出してコピーするか,共有返しを使う関数から戻すかすればよい.
共有渡しと共有返しでは,MTensorの所有権は Mathematica とライブラリの間で共有される.MTensor用にパックアレーを持つ Mathematica 式が使えなくなったら,所有権は Mathematica にはなくなる.ライブラリもMTensorに対してMTensor_disownが呼び出されるまでは所有権を持ち続ける.MTensorを関数に渡し,それが関数から返されるのと同じ回数だけMTensorにMTensor_disownを呼び出さなければいけないことに注意.例えば同じパックアレーをライブラリに3回渡したら,MTensor_disownを3回呼び出さなければならない.関数MTensor_disownAllはすべての参照を取り除くのに便利であり,MTensor_shareCountはMTensorが共有された実際の回数を与える.
最後にもう一つ覚えておかなければならないのは,何らかのエラーが生じた場合,これらがメモリを解放するライブラリの一部から制御を移し去ることがあるということである.この場合は,自分でエラーハンドラを挿入した方がよいかもしれない.これについてはエラーについてのセクションに記載されている.
文字列引数
文字列は文字コードUTF8で渡される.文字列が引数として渡されると,文字列のためのメモリ管理は完全にプログラムに任される.これはMTensorの手動渡しに似ている.
DLLEXPORT int countSubstring(WolframLibraryData libData,
mint Argc, MArgument *Args, MArgument Res)
{
char *instring = MArgument_getUTF8String(Args[0]);
char *substring = MArgument_getUTF8String(Args[1]);
mint i, n = strlen(instring);
mint slen = strlen(substring);
mint c = 0;
if (n > slen) {
n -= slen;
for (i = 0; i <= n; i++) {
if (!strncmp(instring + i, substring, slen)) {
c++;
}
}
}
MArgument_setInteger(Res, c);
libData->UTF8String_disown(instring);
libData->UTF8String_disown(substring);
return LIBRARY_NO_ERROR;
}
両方の文字列のメモリがUTF8String_disownを使ってどのように解放されるかに注目されたい.これを行わないか,プログラムの中で文字列引数への参照を維持しない場合は,メモリは単純に失われる.文字列が結果として返される場合は,Mathematica はメモリにアクセスしてそれを Mathematica の内部文字列形式に返還するが,メモリの解放は試みない.そのため,プログラムが結果に文字列を割り当てると,そのメモリを解放する必要が出てくるが,結果の文字列を設定するのとは別の関数でなければならない.これは,ライブラリ関数が返された後に Mathematica が文字列メモリにアクセスする必要があるからである.
LinkObject引数と結果
MTensorの標準数値型でカバーされていないライブラリ関数に引数を送りたい場合,またはそのライブラリから結果を取得したい場合は,引数と結果にLinkObjectが使える.これを使うと,どのような Mathematica 式の構造でもライブラリに送ることができる.
ライブラリはLinkObjectの引数と結果については,基本数値型とは異なる方法で呼び出される.関数が呼ばれると,Mathematica は全引数のリストを MathLink 接続に書き出す.これは Mathematica が MathLink 上で式を書き出すLinkWriteという方法で行われる.それからライブラリ関数が呼び出される.ライブラリ関数はリンクから引数を読み,作業を行い,リンク上に結果を書いて返す.それから Mathematica はLinkReadという Mathematica が MathLink から式を読み取る方法でリンクから結果を読む.
LinkObjectの引数と結果を使った例を以下に示す.
DLLEXPORT mint reverseString( WolframLibraryData libData, MLINK mlp)
{
mint res = 0;
int i1, i2, sum;
int len;
const char *inStr = NULL;
char* outStr = NULL;
if ( !MLCheckFunction( mlp, "List", &len))
goto retPt;
if ( len != 1)
goto retPt;
if(! MLGetString(mlp, &inStr))
goto retPt;
if ( ! MLNewPacket(mlp) )
goto retPt;
outStr = reverseStringImpl(inStr);
res = MLPutString( mlp,outStr);
retPt:
if ( inStr != NULL)
MLReleaseString(mlp, inStr);
if ( outStr != NULL)
free(inStr);
return res;
}
LinkObjectの引数と結果を使ったサンプル関数をロードする.
| In[7]:= |
| Out[7]= |
| In[8]:= |
| Out[8]= |
Mathematica との通信に MathLink を使うには,"WolframLibrary.h"ヘッダの前に"mathlink.h"ヘッダがインクルードされていなければならない.
ライブラリを探す
LibraryFunctionLoadへの最初の引数はロードするライブラリである.これはC:\Libraries\myLibrary.dllのような絶対ファイル名で与えることができる.しかし,パスから見た相対指定をライブラリに与える方がより便利である.また,ダイナミックライブラリの拡張子はプラットフォームによって異なるので,これは拡張子の指定においても問題となる.ダイナミックライブラリの拡張子の慣例を以下にまとめる.
ダイナミックライブラリに対応した Mathematica の関数は自動的にこの問題を解決する.入力名に拡張子が付いていなければ,使用中のプラットフォームに適したものが加えられる.これによりどのマシンを使っているかに関係なくライブラリを操作することができる.また Mathematica は,ライブラリの検索にパス$LibraryPathを提供する.
$LibraryPathの設定の例を以下に示す.ライブラリを含む Mathematica アプリケーションが数多く含まれている.
| In[1]:= |
| Out[1]= | ![]() |
FindLibraryはライブラリを探す関数である.これはLibraryFunctionLoad等の別のコマンドで呼び出される.FindLibraryはまずライブラリの拡張子を修正し,絶対名としてライブラリを捜し,最後に$LibraryPathでライブラリを探す.そして見付かったファイルを返す.
次の例では$LibraryPathを探し,使用中のプラットフォーム(この例ではWindows)に適したライブラリを見付ける.
| In[9]:= |
| Out[9]= | ![]() |
拡張子付きの名前を使うこともできるが,それは拡張子が適しているプラットフォームでのみ動作することに注意されたい.
| In[10]:= |
| Out[10]= | ![]() |
$LibraryPathには要素を加えることができる.しかし,多くのフォルダが自動的に加えられる.これには $UserBaseDirectory/Applicationsまたは$BaseDirectory/Applicationsに含まれる Mathematica アプリケーションのLibraryResourcesフォルダも含まれる.これは J/Link がJavaクラスをロードすることができ,DatabaseLink がデータベースリソースがロードできる方法に似ている.これにより,Mathematica での作業をライブラリと組み合せる便利な方法が提供される.
独自のライブラリのインストール
Mathematica セッションで使うためにダイナミックライブラリをインストールしたい場合には,数々のオプションがある.以下にまとめる.
絶対パス名
LibraryFunctionLoad等の関数に絶対パス名を割り当てることができる.これは設定が簡単であるが,作業中のものを別のコンピュータに移動したりすると問題が生じる.
$LibraryPathの設定
ライブラリの場所を$LibraryPathに加えることができる.変数に場所を設定して何度も使えるため,これは絶対パス名を使う場合より抽象化されたものとなる.
さらに,Blockを使って$LibraryPathの設定を一時的に変更することもできる.以下に例を示す.
| In[9]:= |
$BaseDirectoryと$UserBaseDirectory
$LibraryPathは常に$UserBaseDirectory/SystemFiles/LibraryResources/$SystemIDと$BaseDirectory/SystemFiles/LibraryResources/$SystemIDの2つの場所を含む.ここにライブラリを置けば,ライブラリをロードする関数によって見付けられる.
$BaseDirectoryの中の場所を示す.
| In[3]:= |
| Out[3]= |
アプリケーション
コードを Mathematica アプリケーションとして作りたい場合は,$LibraryPathに置かれるライブラリを含むことができる.$SystemIDに合致するフォルダにディレクトリを含まなければならない.以下にアプリケーションの例を示す.
MyApplication
MyApplication.m
Kernel
init.m
FrontEnd
Documentation
English
LibraryResources
Windows
libraries for use on Windows
Windows-x86-64
libraries for use on 64 bit Windows
Linux
libraries for use on Linux
Linux-x86-64
libraries for use on 64 bit Linux
MacOSX-x86
libraries for use on MacOSX
MacOSX-x86-64
libraries for use on 64 bit MacOSX
ライブラリの依存関係
ライブラリが他のライブラリに依存している場合は,その依存しているライブラリが利用できることを確認しておく必要がある.これはこれらの余剰ライブラリをシステムのどこかにインストールするために Mathematica が実行される環境を変更するか,またはLD_LIBRARY_PATH (Linux)やPATH (Windows)のようなパス環境変数を変えることで行える.
また、自分のライブラリをロードする前にLibraryLoadを使って依存しているライブラリをロードするという方法もある.LibraryLoadはLibraryFunctionLoadと違って関数を返さない.これは依存しているライブラリをロードするためだけに存在するものである.
Mac OS Xでは,ダイナミックライブラリ(ファイル拡張子はdylib)はより複雑なメカニズムを持っており,依存しているライブラリは特定の場所のみ検索するように設定できる.otoolやinstall_name_tool等のコマンドについて調べる必要がある.
ライブラリのバージョン情報
LibraryLink パッケージを使うと,ライブラリのバージョンについての情報を得ることができる.このパッケージは共有ライブラリを扱うための追加ツールを提供する.
| In[1]:= |
| In[2]:= |
| Out[2]= | ![]() |
ここでLibraryVersionInformationがライブラリについての情報を表示する規則のリストを返す.
| In[3]:= |
| Out[3]= | ![]() |
または,次に示すようにLibraryVersionStringを使ってバージョン情報を文字列として得ることもできる.
| In[4]:= |
| Out[4]= |
ライブラリをロードする際の問題
ライブラリのロードで問題がある場合は,$LibraryErrorを使ってロードしない原因について調べてみるとよい.これはLibraryLink`コンテキストの中にあるが,使うためにパッケージをロードする必要はない.
ここではカレンダーの計算に使用するライブラリがロードされる.
| In[1]:= |
| Out[1]= | ![]() |
このライブラリは他のライブラリへの依存関係がある.通常 Mathematica は他のライブラリを先にロードする.しかしカレンダーライブラリだけを直接ロードしようとしている上,まだカレンダー関数を使ったことがない場合,このロード操作は次のように失敗に終る.
次のようにして,$LibraryErrorを使ってこのエラーの詳細を知ることができる.
| In[3]:= |
| Out[3]= |









