Wolfram言語とのインタラクション

Wolfram LibraryLink を使うと,ダイナミックライブラリをWolfram言語カーネルに直接ロードして,ライブラリの関数を即座にWolfram言語内部から呼び出せるようにすることができる.整数,実数,パックアレー,文字列等のCのようなデータ型だけでなく,任意のWolfram言語式も交換することができる.また,エラーを送ったり,Wolfram言語にコールバックしたりするような便利な関数もある.

このセクションでは,Wolfram Librariesを使うためにWolfram言語が提供する関数について述べる.

LibraryFunctionLoadライブラリから関数をロードする
LibraryFunctionライブラリからロードされた関数へのハンドルの表現
LibraryFunctionUnload以前ライブラリからロードされた関数をアンロードする
LibraryUnload以前ライブラリからロードされた関数をすべてアンロードする
LibraryFunctionInformationLibraryFunctionについての情報を返す
$LibraryPathライブラリの検索に使用されるパス
FindLibraryライブラリを検索する
LibraryLoad他のライブラリが必要とするライブラリをロードする

ライブラリを使うためのWolfram言語関数

LibraryFunctionLoadはライブラリから関数をロードし,LibraryFunctionを返す.

In[1]:=
Click for copyable input
Out[1]=

整数の引数でLibraryFunctionを呼び出す.

In[2]:=
Click for copyable input
Out[2]=

LibraryFunctionInformationはロード元のライブラリ,引数や戻り値型等のLibraryFunctionについての情報を返す.

In[3]:=
Click for copyable input
Out[12]=

ライブラリの指定

LibraryFunctionLoadの第1引数はロードするライブラリである.これ/Library/myLibrary.dylibのように絶対ファイル名で与えることができる.しかし,ライブラリにパスからの相対指定を渡す方がより便利なことがよくある.また,プラットフォームごとにライブラリの拡張子が異なるという問題もある.以下に規則をまとめる.

Windowsdll
Unix and Linuxso
Mac OS Xdylib

各システムにおけるダイナミックライブラリの拡張子

Wolfram言語はFindLibrary$LibraryPathでこの問題を解決する.FindLibraryはプラットフォーム非依存であるライブラリ指定を取ることができ,それを$LibraryPath上で探して,ライブラリを見付けることができたらシステムの実際のファイルを返す.

次の例は,$LibraryPath上を検索して使用中のプラットフォームに適したライブラリを求める.この例ではWindowsである.

In[13]:=
Click for copyable input
Out[13]=

$LibraryPathの設定方法についてのさらなる情報は「ライブラリを探す」を参照されたい.

関数名

LibraryFunctionLoadの第2引数はロードする関数の名前を与える.この関数は「Libraryの構成」に記載のように,ライブラリからエキスポートされなければならない.ライブラリをC++としてコンパイルする場合は,おそらくCの命名規則でエキスポートしなければならないだろう.これについても「Libraryの構成」に記載されている.

型指定

LibraryFunctionLoadの第3,第4引数は,引数と戻り値の型を指定するものである.

"Boolean"mboolブール値
Integermint機械整数
Realdouble機械倍精度数
Complexmcomplex機械複素倍精度数
{base,rank}MTensor指定の基底型と階数のテンソル
{base,rank,memory}MTensor指定のメモリ管理のあるテンソル
LibraryDataType[SparseArray,]MSparseArray疎配列
"UTF8String"char *UTF8文字列
LinkObjectMLINK引数とWSTPに書き出された結果
"Void" 結果なし(戻るのみ)

引数と戻り値の型の指定

テンソルを指定する型はWolfram言語のパックアレーに直接マップしするように,また,テンソルをWolfram言語コンパイラで使用するように設計されている.これによりシステムの効率が格段によくなり,ライブラリがテンソル操作を利用できるようになる.テンソルは各要素の方と型と階数を明示的に指定することも,指定せずにおくこともできる.型と階数を指定しないでおくと,テンソルに作用するアプリケーションの柔軟性が増す.テンソルはIntegerRealComplexについてのみサポートされていることに注意されたい.

{Integer, 1}指定の基底型と階数のテンソル
{Real, _}指定の基底型と任意の階数のテンソル
{_,_}任意の基底型と任意の階数のテンソル

テンソルの型指定

テンソルが引数として渡される場合,そのメモリがどのように扱われるかも指定できる.これについては次のセクションで詳細を述べる.

ライブラリ関数のコードの中で,引数の配列から各型のデータを集めることができる.これを簡単にする目的で,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は結果の型を表す整数値を返す.

MType_Integer機械整数を含むMTensor
MType_Real機械倍精度数を含むMTensor
MType_Complex複素数(実部と虚部は機械倍精度数)を含むMTensor

MTensor_getTypeの結果

ライブラリ関数が引数を取らない場合は,入力指定に空のリストを渡せばよい.

In[7]:=
Click for copyable input
Out[7]=
In[8]:=
Click for copyable input
Out[8]=

ライブラリ関数が結果を返さない場合は,戻り値指定が使える.

In[9]:=
Click for copyable input
Out[9]=
In[10]:=
Click for copyable input

MTensorのメモリ管理

mint,double,mcomplex等の型は,C関数の呼出しで一般的なように,ライブラリへ,またはライブラリから値で渡される.ライブラリにおける使い方はWolfram言語での使い方と全く異なる.

反対に,MTensorはデータ構造へのポインタであり,参照として渡される.そのため,メモリがどのように管理されるのかを考えなければならない.Wolfram言語は安全で簡単なデフォルトの技術を選択するが,大量のデータを渡したり,後で使用するために保存したりしたい場合は,その管理を考える必要がある.

MTensor入力引数

MTensorをライブラリ関数に渡すときは,どのようにして渡すかを指定する数々のオプションが使える.

{Integer, 1}MTensorのコピーを渡し,自動的に消す
{Integer, 1, Automatic}MTensorのコピーを渡し,自動的に消す
{Integer, 1, "Constant"}変更できないMTensorへの参照を渡す
{Integer, 1, "Manual"}MTensorのコピーを渡すが,自動的には消さない
{Integer, 1, "Shared"}ライブラリとWolfram言語の間で共有するMTensorへの参照を渡す

ライブラリ関数への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データへの高速リードオンリーアクセスが可能となる.コードがこの想定に反してデータを変更すると,そのWolfram言語セッションで重大なエラーが生じる可能性がある.

不変渡しで渡された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 = libData->MTensorVector_getReal( T0, I0);
    libData->MTensor_free(T0);
    MArgument_setReal(Res, R0);
    return LIBRARY_NO_ERROR;
}

関数がどのようにMTensorを解放したかに注目されたい.解放されなかったら,メモリは失われる.しかしテンソルを解放する代りに,テンソルを保存してそれをライブラリの他の部分で利用することもできる.最後にテンソルを使い終わったら,MTensor_freeを呼び出さなければならない.代りに,テンソルをライブラリ関数からWolfram言語に返し,所有者をWolfram言語に戻すという方法でもよい.

手動渡しで渡されたMTensorは完全にライブラリが所有し,この状態はMTensorが解放されるかWolfram言語に戻されるまで続く.

共有渡し

共有渡しを選択した場合は,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 = libData->MTensorVector_getReal( T0, I0);
    libData->MTensor_disown( T0);
    MArgument_setReal(Res, R0);
    return LIBRARY_NO_ERROR;
}

引数に共有渡しを使うと,MTensorはWolfram言語とライブラリの間で共有される.Wolfram言語はMTensorを表の中に保管し,MTensorのメモリが収集されないようにする.ライブラリがMTensorを使う必要がなくなったら,MTensor_disownを呼び出さなければならない.

共有渡しで渡されたMTensorはライブラリとWolfram言語の間で共有される.この状態はライブラリとWolfram言語が完全に使い終えるまで続く.

引数の渡し方にを使ってサンプル関数をロードする.

In[1]:=
Click for copyable input
Out[1]=

関数の引数に使うのに適したパックアレーを作成する.

In[2]:=
Click for copyable input
Out[3]=

関数を呼び出し,配列を渡す.

In[4]:=
Click for copyable input
Out[4]=

パックアレーではない引数で関数を呼び出すと,呼出しは動作するが,警告メッセージが返される.これはWolfram言語が入力をパックアレーに変換しなければならないため,データをコピーし,共有渡しの利点の一つを失うからである.

In[5]:=
Click for copyable input
Out[6]=

MTensorの返し方

ライブラリ関数からMTensorを返すときは,メモリをどのように扱うかも制御する.

{Integer, 1}MTensorへの参照をWolfram言語に返す
{Integer, 1, Automatic}MTensorへの参照をWolfram言語に返す
{Integer, 1, "Shared"}ライブラリとWolfram言語の間で共有されるMTensorへの参照を返す

ライブラリ関数からのMTensorの結果に対する可能なメモリ管理

自動返し

自動返しを選択すると,MTensorはライブラリからWolfram言語に直接戻る.Wolfram言語はそれをライブラリ関数の結果として使う.

DLLEXPORT int demo_I_T(WolframLibraryData libData, 
            mint Argc, MArgument *Args, MArgument Res) {
    MTensor T0;
    mint i, I0, dims[1];
    int err = LIBRARY_NO_ERROR;

    I0 = MArgument_getInteger(Args[0]);
    dims[0] = I0;
    
    err = libData->MTensor_new(MType_Integer, 1, dims, &T0);
    for ( i = 1; i <= I0 && !err; i++) {
        err = libData->MTensor_setInteger( T0, &i, i*2);
    }
    MArgument_setMTensor(Res, T0);
    return err;
}

ライブラリがMTensorを所有している場合,つまりMTensorがライブラリ内で作られたり手動渡しで渡されたりした場合は,一旦MTensorがWolfram言語 に返されるとそれはもはやライブラリの所有ではなくなるため,ライブラリはMTensorを一切使わなくなる.これは上記関数に示されている.

MTensorがライブラリとWolfram言語の間で共有されているなら,自動返しはMTensorの所有権については変更を加えない.しかし,Wolfram言語はすでにMTensorへの参照を持っているため,ライブラリから共有MTensorを返すというのは奇妙である.

共有返し

共有返しを選択すると,MTensorはライブラリとWolfram言語の間で共有される.技術的な観点から言うと,これは関数が戻るときに共有表に加えることで実現される.これにより,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の所有権はWolfram言語とライブラリの間で共有される.MTensor用にパックアレーを持つWolfram言語式が使えなくなったら,所有権はWolfram言語にはなくなる.ライブラリもMTensorに対してMTensor_disown呼び出されるまでは所有権を持ち続ける.MTensorを関数に渡し,それが関数から返されるのと同じ回数だけMTensorMTensor_disownを呼び出さなければいけないことに注意.例えば同じパックアレーをライブラリに3回渡したら,MTensor_disownを3回呼び出さなければならない.関数MTensor_disownAllはすべての参照を取り除くのに便利であり,MTensor_shareCountMTensorが共有された実際の回数を与える.

最後にもう一つ覚えておかなければならないのは,何らかのエラーが生じた場合,これらがメモリを解放するライブラリの一部から制御を移し去ることがあるということである.この場合は,自分でエラーハンドラを挿入した方がよいかもしれない.これについてはエラーについてのセクションに記載されている.

文字列引数

文字列は文字コード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を使ってどのように解放されるかに注目されたい.これを行わないか,プログラムの中で文字列引数への参照を維持しない場合は,メモリは単純に失われる.文字列が結果として返される場合は,Wolfram言語はメモリにアクセスしてそれをWolfram言語の内部文字列形式に返還するが,メモリの解放は試みない.そのため,プログラムが結果に文字列を割り当てると,そのメモリを解放する必要が出てくるが,結果の文字列を設定するのとは別の関数でなければならない.これは,ライブラリ関数が返された後にWolfram言語が文字列メモリにアクセスする必要があるからである.

MSparseArray

Wolfram言語のSparseArrayLibraryFunctionに渡すことも,LibraryFunctionから渡されることもできる.そこではSparseArrayは引数として,または型MSparseArrayの結果として現れる.MSparseArrayはデータ構造のポインタであり,MTensorの場合と同様に,参照によって渡される.そこで考えなければならないのは,そのメモリをどのように管理するかである.Wolfram言語は安全で簡単なデフォルトの方法を選ぶが,多量のデータを渡したい場合や今後使えるように保存したい場合は,その管理方法を考える必要がある.ほとんどの場合,MSparseArrayのメモリ管理はMTensorのメモリ管理と同様である.

SparseArrayの型指定

MSparseArrayの引数および結果はLibraryDataTypeを使って指定する.

LibraryDataType[SparseArray]任意の型,あるいは階数が非零の疎配列
LibraryDataType[SparseArray,type]指定の type および任意の非零階数の疎配列
LibraryDataType[SparseArray,type,rank]指定の type および rank の疎配列

MSparseArray引数または結果に対するLibraryFunctionの型指定

MSparseArrayの引数は,LibraryFunctionに渡された実際の引数を,まだ存在しないならばWolfram言語SparseArrayオブジェクトに変換することにより管理される.変換が成功し,階数が引数指定により許可されたら,型強制は終了する.型が指定されていない場合は,明示的な値と暗示的な値が同じ機械数の型になるように強制が行われる.型が指定されている場合は,指定の型の明示的および暗示的値両方の強制が試みられる.

MSparseArrayの結果は,階数もしくは次元のうちの一つが0でない限りSparseArrayオブジェクトとしてWolfram言語に返される.階数が0の場合結果は数に,次元の一つが0の場合は空のリストに変換される.

MSparseArrayデータ構造

MSparseArrayはWolfram言語におけるSparseArrayの内部構造を反映している.Wolfram言語内部の構造は通常知っている必要はないが,関数を使用するに当たって知っていると役に立つこともある.

MSparseArrayは疎行列の圧縮行格納形式の拡張として保存される.明示的に保存された 個の位置を持つ × 行列では,それぞれの位置に対応する値は,長さ で階数1のMTensorに保存される.次元で整数型の階数2のMTensorは行1から までの列指標を保存するのに使われる.長さ で整数型の階数1のMTensorは,各行,つまり行ポインタにおける位置の累積数を保存するために使われる.このMTensor最後の要素は常に に等しい.

例えば,非零の値がすべて明示的に保存されている行列を考える.

非零の の値はである.

MTensorを含んでいる.

行ポインタMTensorを含んでいる.

この形式は以下のように任意の階数の配列に拡張される.係数が1ならば,長さ のベクトルは実質的に1× 行列として保存される.回数 が2より大きければ,列は次元MTensorとして保存される.

Wolfram言語ではSparseArrayオブジェクトのInputFormの最後の2つの部分を見ることにより保存形式を知ることができる.

In[27]:=
Click for copyable input
Out[27]//InputForm=

ほとんどの場合,暗示的な値は0であり,SparseArrayではこれがデフォルトである.しかし,以下のようにどのような値でも使うことができる.

In[28]:=
Click for copyable input
Out[28]//InputForm=

上の例では暗示的な値は1であるが,データ構造は実質的に同じである.SparseArrayでは,暗示的な値は常に階数0のMTensorとして保存される.このMTensorの型は,MSparseArrayのデータの型である.値は(あるとすれば),暗示的な値と一致した型を持つ.

データ構造で使われるMTensorsは,ライブラリコールバック関数を使ってアクセスすることができる.

MSparseArray_getImplicitValue暗示的な値を含むMTensorのポインタを取得する.
MSparseArray_getExplicitValues明示的な値を含むMTensorのポインタを取得する.
MSparseArray_getColumnIndices列指標を含むMTensorのポインタを取得する.
MSparseArray_getRowPointers行ポインタ配列を含むMTensorのポインタを取得する.

MSparseArrayのCSRデータにアクセスするためのコールバック関数.

MSparseArrayコールバック関数はすべてWolframLibraryDatasparseLibraryFunctionsフィールドにあり,WolframSparseLibrary.hで宣言されている.従って,通常以下のように使われる.

#include "WolframLibrary.h"
#Include "WolframSparseLibrary.h"
....
MTensor *t;
MSparseArray s;

...
t = (*libData->sparseLibraryFunctions->MSparseArray_getImplicitValue)(s);
...

これらのコールバック関数はすべてMTensorへのポインタを返す.MTensorMSparseArrayデータ構造に属しており,MTensor_freeを使って解放されてはならない.コールバック関数はMSparseArrayが解放されるときに解放される.これらのMTensors内部のデータにアクセスすることにより,データ構造の値をその場で変更することができる.MSparseArrayのメモリがライブラリによって所有されている限り,明示的な値は簡単に変更することができる.暗示的なの変更も行われるが,暗示的な値の変更は上記の例のように異なる疎構造を意味することがある.そのため新しい値に対するCSRデータ構造を再計算するコールバック関数MSparseArray_resetImplicitValuesがある.列指標や行ポインタの変更は,細心の注意を払って行う必要がある.これらは一貫性がなければならないので,誤った変更を行うと,大きな問題につながることがある.

CSR格納を直接構築したり変更したりすることは難しい場合がある.通常表現される配列の実際の位置で作業する方が簡単である.

MSparseArray_fromExplicitPositions明示的な位置および値からMSparseArrayを構築する
MSparseArray_getExplicitPositions明示的な位置を含む階数2のMTensorを返す

明示的な位置を操作するコールバック関数.

MSparseArray_getExplicitPositionsによって構築されたMTensorは,ライブラリに属するので,使い終わったら解放される.

Wolfram言語では,SparseArrayオブジェクトを構築するためのSparseArray[{pos1->val1,pos2->val2,}]シンタックスはコールバックMSparseArray_fromExplicitPositionsに相当し,関数ArrayRulesMSparseArray_getExplicitValuesと組み合されたコールバックMSparseArray_getExplicitPositionsに相当する.

MSparseArrayの入力引数メモリ管理

MSparseArrayをライブラリ関数に渡すとき,どのように行うかを決定するオプションがいくつかある.

LibraryDataType[SparseArray,]MSparseArrayのコピーを渡し,自動的に消す
{LibraryDataType[SparseArray,],Automatic}MSparseArrayのコピーを渡し,自動的に消す
{LibraryDataType[SparseArray,],"Constant"}変更できないMSparseArrayへの参照を渡す
{LibraryDataType[SparseArray,],"Manual"}MSparseArrayのコピーを渡し,自動的には消さない
{LibraryDataType[SparseArray,],"Shared"}ライブラリとWolfram言語の間で共有されるMSparseArrayへの参照を渡す

ライブラリ関数へのMSparseArray引数の可能なメモリ管理

自動渡し

自動引き渡しを選択した場合は,MSparseArrayは関数が呼ばれる前にコピーされ,関数が戻るときに消される.

自動渡しで渡されたMSparseArrayは,関数呼出しがアクティブである限り,ライブラリ関数によってのみ読み書きアクセスの両方ができるように所有される.

不変渡し

不変渡しを選択した場合は,MSparseArrayへの参照が渡され,関数はMSparseArrayを変更することはないと想定される.これにより,実質的にはMSparseArrayデータへの高速リードオンリーアクセスが可能となる.コードがこの想定に反してデータを変更すると,そのWolfram言語セッションで重大なエラーが生じる可能性がある.

不変渡しで渡されたMSparseArrayは,関数呼出しがアクティブである限り,ライブラリ関数によって読み書きアクセスのみができる.

手動渡し

手動渡しを選択した場合は,MSparseArrayは関数呼出しの前にコピーされ,関数が戻るときに消去されない.

手動渡しで渡されたMSparseArrayは完全にライブラリが所有し,この状態はMSparseArrayが解放されるかWolfram言語に戻されるかするまで続く.

共有渡し

共有渡しを選択した場合は,MSparseArrayは関数が呼び出される前にはコピーされず,ライブラリ関数に直接渡される.

引数に共有渡しを使うと,MSparseArrayはWolfram言語とライブラリの間で共有される.Wolfram言語はMSparseArrayを表の中に保管し,MSparseArrayのメモリが収集されないようにする.ライブラリがMSparseArrayを使う必要がなくなったら,MSparseArray_disownを呼び出さなければならない.

共有渡しで渡されたMSparseArrayはライブラリとWolfram言語の間で共有される.この状態はライブラリとWolfram言語が完全に使い終えるまで続く.

MTensorの場合と同様に,与えられたWolfram言語引数がSparseArrayであり,MSparseArrayを作成するために明示的あるいは暗示的値の強制が必要ない場合のみデータが共有される.

MSparseArrayの返し方

ライブラリ関数からMSparseArrayを返すときは,メモリをどのように扱うかも制御する.

LibraryDataType[SparseArray,]MSparseArrayへの参照をWolfram言語のSparseArrayとして返す
{LibraryDataType[SparseArray,],Automatic}MSparseArrayへの参照をWolfram言語のSparseArrayとして返す
{LibraryDataType[SparseArray,],"Constant"}ライブラリとWolfram言語の間で共有されるMSparseArrayへの参照をWolfram言語のSparseArrayとして返す

ライブラリ関数からのMSparseArrayの結果に対する可能なメモリ管理

自動返し

自動返しを選択すると,MSparseArrayはライブラリからWolfram言語に直接戻る.Wolfram言語はそれをライブラリ関数の結果として使う.

ライブラリがMSparseArrayを所有している場合,つまりMSparseArrayがライブラリ内で作られたり手動渡しで渡されたりした場合は,一旦MSparseArrayがWolfram言語に返されるとそれはもはやライブラリの所有ではなくなるため,ライブラリはMSparseArrayを一切使わなくなる.

MSparseArrayがライブラリとWolfram言語の間で共有されているなら,自動返しはMSparseArrayの所有権については変更を加えない.しかし,Wolfram言語はすでにMSparseArrayへの参照を持っているため,ライブラリから共有MSparseArrayを返すというのは奇妙である.

共有返し

共有返しを選択すると,MSparseArrayはライブラリとWolfram言語の間で共有される.技術的な観点から言うと,これは関数が戻るときに共有表に加えることで実現される.これにより,MSparseArrayが収集されないようにする.ライブラリがMSparseArrayを必要としなくなったら,MSparseArray_disown(またはMSparseArray_disownAll)を呼び出さなければならない.

MSparseArrayを共有表に加える前にMSparseArray_disown(またはMSparseArray_disownAll)を呼び出すと効果がなくなり,警告メッセージが示される.

MImage

Wolfram言語のImageおよびImage3DLibraryFunctionに渡すことも,LibraryFunctionから渡されることもできる.そこではこれらは型MImageの引数,または結果として現れる.MImageはデータ構造のポインタであり,MTensorの場合と同様に,参照によって渡される.そこで考えなければならないのは,そのメモリをどのように管理するかである.Wolfram言語は安全で簡単なデフォルトの方法を選ぶが,多量のデータを渡したい場合や今後使えるように保存したい場合は,その管理方法を考える必要がある.ほとんどの場合,MImageのメモリ管理はMTensorのメモリ管理と同様である.

MImageの型指定

MImageの引数および結果は,LibraryDataTypeを使って指定する.

LibraryDataType[Image]任意の型の2D画像
LibraryDataType[Image3D]任意の型の3D画像
LibraryDataType[Image|Image3D]任意の型の2Dまたは3D画像
LibraryDataType[imd,type]imd で指定された次元を持つ指定された型の画像

MSparseArrayの引数または結果に対するLibraryFunctionの型指定.

式の型MImageの引数 arg は,arg が引数指定によって許可された次元数と矛盾のないWolfram言語のImageまたはImage3Dオブジェクトであるかどうかをチェックすることにより管理される.画像型が指定され,実際の型が指定された方と一致しない場合は,画像型の強制にImage[arg,type]と同等のものが使われる.それから,画像式に含まれるMImageデータ構造への参照が関数に渡される.

MImageの結果は常にImageまたはImage3DオブジェクトとしてWolfram言語に返される.

MImageデータ構造

MImageはWolfram言語のImageおよびImage3Dの内部構造を反映している.MImageデータ構造は画像についての情報を含んでおり,そのほとんどはコールバック関数からアクセスできる.

MImageデータ構造の最も重要な部分は,画像の型に対応するデータ配列の型である.

C型
説明
"Bit"raw_t_bit整数0か1(単一ビット)
"Byte"raw_t_ubit8整数0から255まで(8ビット)
"Bit16"raw_t_ubit16整数0から65535まで(16ビット)
"Real32"raw_t_real32単一精度実数(32ビット)
"Real"raw_t_real64倍精度実数(32ビット)

画像型と対応するCの型.

この配列の並び方は次元数,チャンネル,インターリーブにより異なる.配列の単一画素への配列へのアクセスを与えるコールバック関数が提供されており,チャンネルがどのようにインターリーブするかを知っている必要はない.

MImage_getBit指定された画素およびチャンネルにおけるビット値を取得する
MImage_setBit指定された画素およびチャンネルにおけるビット値を指定する

単一画素値の取得と設定のための関数.

5つの画像型のそれぞれに対してMImage_gettype およびMImage_settype関数がある.

MImageコールバック関数は, すべてWolframLibraryDataimageLibraryFunctionsフィールドにあり,WolframImageLibrary.hで宣言されている.従って,通常以下のように使われる.

#include "WolframLibrary.h"
#Include "WolframImageLibrary.h"
....
int err;
raw_t_bit v;
mint channel, pos[3];
MImage img;

...
err = (*libData->imageLibraryFunctions->MImage_getBit)(img, pos, channel, &v);
...

多くの操作で配列全体へのアクセスが必要とされるが,画素,チャンネル,配列指標を対応させるためにどのようにチャンネルがインターリーブされるかを知る必要がある.

MImage_interleavedQチャンネルデータがインターリーブされているかどうかを調べる
MImage_getBitData画像型が"Bit"である画像に対するraw_t_bitデータの配列を取得する
MImage_getRawData任意の画像型の配列に対するvoid * ポインタを取得する

画像データ配列を取得するための関数.

MImage入力引数メモリ管理

MImageをライブラリ関数に渡すときは,どのようにして渡すかを指定する数々のオプションが使える.

LibraryDataType[Image,]MImageのコピーを渡し,自動的に消す
{LibraryDataType[Image,],Automatic}MImageのコピーを渡し,自動的に消す
{LibraryDataType[Image,],"Constant"}変更できないMImageへの参照を渡す
{LibraryDataType[Image,],"Manual"}MImageのコピーを渡すが,自動的には消さない
{LibraryDataType[Image,],"Shared"}ライブラリとWolfram言語の間で共有するMImageへの参照を渡す

ライブラリ関数へのImage引数およびImage3D引数の可能なメモリ管理

自動渡し

自動渡しを選択した場合は,MImageは関数が呼ばれる前にコピーされ,関数が戻るときに消される.

自動渡しで渡されたMImageは,関数呼出しがアクティブである限り,ライブラリによってのみ読み書きアクセスの両方ができるように所有される.

不変渡し

不変渡しを選択した場合は,MImageへの参照が渡され,関数はMImageを変更することはないと想定される.これにより,実質的にはMImageデータへの高速リードオンリーアクセスが可能となる.コードがこの想定に反してデータを変更すると,そのWolfram言語セッションで重大なエラーが生じる可能性がある.

不変渡しで渡されたMImageは,関数呼出しがアクティブである限り,ライブラリによって読み込みアクセスのみができるように所有される.

手動渡し

手動渡しを選択した場合は,MImageは関数呼出しの前にコピーされ,関数が戻るときに消去されない.

手動渡しで渡されたMImageは完全にライブラリが所有し,この状態はMImageが解放されるかWolfram言語に戻されるかするまで続く.

共有渡し

共有渡しを選択した場合は,MImageは関数が呼び出される前にはコピーされず,ライブラリ関数に直接渡される.

引数に共有渡しを使うと,MImageはWolfram言語とライブラリの間で共有される.Wolfram言語はMImageを表の中に保管し,MImageのメモリが収集されないようにする.ライブラリがMImageを使う必要がなくなったら,MImage_disownを呼び出さなければならない.

共有渡しで渡されたMImageはライブラリとWolfram言語の間で共有される.この状態はライブラリとWolfram言語が完全に使い終えるまで続く.

MTensorの場合と同様に,与えられたWolfram言語引数がImageであり,MImageを作成するために明示的あるいは暗示的値の強制が必要ない場合のみデータが共有される.

MImageの返し方

ライブラリ関数からMImageを返すときは,メモリをどのように扱うかも制御する.

LibraryDataType[Image,]MImageへの参照をWolfram言語のImageとして返す
{LibraryDataType[Image,],Automatic}MImageへの参照をWolfram言語のImageとして返す
{LibraryDataType[Image,],"Constant"}ライブラリとWolfram言語の間で共有される参照を,Wolfram言語のImageとしてMImageに返す

ライブラリ関数からのMImageの結果に対する可能なメモリ管理

自動返し

自動返しを選択すると,MImageはライブラリからWolfram言語に直接戻る.Wolfram言語はそれをライブラリ関数の結果として使う.

ライブラリがMImageを所有している場合,つまりMImageがライブラリ内で作られたり手動渡しで渡されたりした場合は,一旦MImageがWolfram言語に返されるとそれはもはやライブラリの所有ではなくなるため,ライブラリはMImageを一切使わなくなる.

MImageがライブラリとWolfram言語の間で共有されているなら,自動返しはMImageの所有権については変更を加えない.しかし,Wolfram言語はすでにMImageへの参照を持っているため,ライブラリから共有MImageを返すというのは奇妙である.

共有返し

共有返しを選択すると,MImageはライブラリとWolfram言語の間で共有される.技術的な観点から言うと,これは関数が戻るときに共有表に加えることで実現される.これにより,MImageが収集されないようにする.ライブラリがMImageを必要としなくなったら,MImage_disown(またはMImage_disownAll)を呼び出さなければならない.

MImageを共有表に加える前にMImage_disown(またはMImage_disownAll)を呼び出すと効果がなくなり,警告メッセージが示される.

管理されたライブラリ式

メモリがライブラリによって管理されているオブジェクトやデータのインスタンスに対するハンドラとして,Wolfram言語式を使いたいという場合もあろう.これらのインスタンスにアクセスするために,管理されたライブラリ式を使うことにより,式がWolfram言語セッションで参照されなくなったときにインスタンスを自動的に解放することが可能になる.

CreateManagedLibraryExpressionライブラリデータに関連付けることのできる式を生成する
ManagedLibraryExpressionQ式が,管理されたライブラリ式であるかどうかをテストする
ManagedLibraryExpressionID管理されたライブラリ式に関連付けられた正の整数IDを与える

管理されたライブラリ式に対するWolfram言語関数

registerLibraryExpressionManager(mname, mfun)名前が mname でマネージャ関数が mfun のライブラリ式マネージャを登録する
unregisterLibraryExpressionManager(mname)名前が mname のライブラリ式マネージャの登録を抹消する
releaseManagedLibraryExpression(mname,mid)マネージャが mname でIDが mid の,管理されたライブラリ式を解放する

管理されたライブラリ式に対するC/C++のコールバック関数

通常,ライブラリ式マネージャはライブラリの初期化で登録し,ライブラリの非初期化で登録解除するのが一番よい.それによりライブラリがロードされている限りマネージャはアクティブになる.

/* Initialize Library */
EXTERN_C DLLEXPORT int WolframLibrary_initialize( WolframLibraryData libData)
{
    return (*libData->registerLibraryExpressionManager)("manager", manage_instance);
}

/* Uninitialize Library */
EXTERN_C DLLEXPORT void WolframLibrary_uninitialize( WolframLibraryData libData)
{
    int err = (*libData->unregisterLibraryExpressionManager)("manager");
}

マネージャ関数 mfun の型はvoid (*mfun)(WolframLibraryData libData, mbool mode, mint id)である.mname がマネージャ関数 mfun で登録されているならば,CreateManagedLibraryExpression[mname, f]の評価によって,名前が mname のマネージャに対して一意である正の整数ID mid が生成される.まず が評価され, mode 0で idmid に等しい関数 mfun が呼ばれ,最後に評価 が結果として戻る.Wolfram言語セッションのいずれかの時点で結果 への参照が存在しなくなると,mode 1で idmid に等しい関数 mfun が呼ばれる.通常,mode 0はデータの生成を,mode 1はデータの解放を意味する.

Wolfram言語式への隠れた参照を持つことが可能な場合もある.ライブラリデータがもはや必要なくなったとき,releaseManagedLibraryExpressionを使って式を解放することができる.式を解放するときに,releaseManagedLibraryExpression(mname, mid)は,mode 1で idmid に等しい mname に関連付けられたマネージャ関数 fun を呼ぶ.

これを使って,簡単な線形合同法のさまざまなインスタンスを設定する例がdemo_managed.cxxに実装されており,demo_managedの中のLibraryLinkで示されている.

ライブラリコールバック関数

関数がそのタスクを実行するために,別の関数を繰返し評価する必要がある場合がある.その典型的な例が,根を求める関数と最適化の関数である.

非常に一般的な式に対するコールバックは常にWolfram Symbolic Transfer Protocol (WSTP)引数インターフェースを使って実行することができるが,通信のオーバーヘッドの危険性も備えている.比較的単純な関数を繰返し評価する場合は,オーバーヘッドを最小限に抑えることが重要である.このためには,引数の型に制約を付ける.

CompiledFunctionで許可される引数は,実際にはLibraryFunctionに許可される引数の部分集合であるため,コールバックのためにCompiledFunctionに接続することは, 妥当な一般性を保ちながらスピードを求めることにおいて,当然の選択である.さらにCompiledFunctionCompilationTarget->"C"でコンパイルされていたら,コールバックは機械コードレベルで行うことができる.

ConnectLibraryCallbackFunctionCompiledFunctionに接続して,ライブラリから呼び出せるようにする

管理されたライブラリ式に対するWolfram言語関数

registerLibraryCallbackManager(mname, mfun)名前が mname でマネージャ関数が mfun であるライブラリコールバックマネージャを登録する
unregisterLibraryCallbackManager(mname)名前が mname であるライブラリコールバックマネージャの登録を解除する
callLibraryCallbackFunction(id, libData, Args, Res)IDが id である接続されたライブラリコールバック関数を呼び出す
releaseLibraryCallbackFunction(id)IDが id であるマネージャを含むライブラリコールバック関数を解放する

ライブラリコールバック関数に対するC/C++のコールバック関数.

通常,ライブラリコールバックマネージャはライブラリの初期化で登録し,ライブラリの非初期化で登録解除するのが一番よい.それにより,ライブラリがロードされている限りマネージャはアクティブになる.

/* Initialize Library */
EXTERN_C DLLEXPORT int WolframLibrary_initialize( WolframLibraryData libData)
{
    return (*libData->registerLibraryCallbackFunction)("manager", manage_callback);
}
 
/* Uninitialize Library */
EXTERN_C DLLEXPORT void WolframLibrary_uninitialize( WolframLibraryData libData)
{
    int err = (*libData->unregisterLibraryCallbackFunction)("manager");
}

マネージャ関数 mfun の型はmbool (*mfun)(WolframLibraryData libData, mint id, MTensor argtypes)である.mname がマネージャ関数 mfun で登録されているならば,ConnectLibraryCallbackFunction[mname, cf]の評価によって,一意である正の整数ID mid が生成される.次に idmid に等しく,argtypes が階数2のMTensor(各行が対応する引数引数と同じ型と階数を持つ)で関数 mfun が呼ばる.n 個の引数がある場合,argtypes の長さは n+1で最後の行は結果と同じ型および階数になる.型はWolframLibrary.hで定義されているように,マクロMType_Integer等を使って符号化される.階数0はスカラーに対応する.ConnectLibraryCallbackFunction[mname, cf]mfunTrueを返すときだけTrueを返す.CompiledFunction cfreleaseLibraryCallbackFunction(mid)が呼ばれるまで保存され,CompiledFunctionへの参照が他になくなると,解放される.

一旦接続されると,関数は,与えられたIDを持つcallLibraryCallbackFunctionを使って呼び出すことができる.

コールバック機能の使い方を説明した例はdemo_callback.cに実装されており,demo_callbackの中のLibraryLinkで見ることができる.

LinkObject引数と結果

MTensorの標準数値型でカバーされていないライブラリ関数に引数を送りたい場合,またはそのライブラリから結果を取得したい場合は,引数と結果にLinkObjectが使える.これを使うと,どのようなWolfram言語式の構造でもライブラリに送ることができる.

ライブラリはLinkObjectの引数と結果については,基本数値型とは異なる方法で呼び出される.関数が呼ばれると,Wolfram言語は全引数のリストをWSTP接続に書き出す.これはWolfram言語がWSTP上で式を書き出すLinkWriteという方法で行われる.それからライブラリ関数が呼び出される.ライブラリ関数はリンクから引数を読み,作業を行い,リンク上に結果を書いて返す.それからWolfram言語はLinkReadというWolfram言語がWSTPから式を読み取る方法でリンクから結果を読む.

LinkObjectの引数と結果を使った例を以下に示す.

DLLEXPORT mint reverseString( WolframLibraryData libData, WLINK mlp)
{
    mint res = 0;
    int i1, i2, sum;
    int len;
    const char *inStr = NULL;
    char* outStr = NULL;
    
    if ( !WSCheckFunction( mlp, "List", &len))
        goto retPt;
    if ( len != 1)
        goto retPt;

    if(! WSGetString(mlp, &inStr))
        goto retPt;

    if ( ! WSNewPacket(mlp) )
        goto retPt;

    outStr = reverseStringImpl(inStr);
    res = WSPutString( mlp,outStr);
retPt:
    if ( inStr != NULL)
        WSReleaseString(mlp, inStr);
    if ( outStr != NULL)
        free(inStr);
    return res;
}

LinkObjectの引数と結果を使ったサンプル関数をロードする.

In[7]:=
Click for copyable input
Out[7]=

関数を呼び出す.

In[8]:=
Click for copyable input
Out[8]=

Wolfram言語との通信にWSTPを使うには,"WolframLibrary.h"ヘッダの前に"mathlink.h"ヘッダがインクルードされていなければならない.

ライブラリを探す

LibraryFunctionLoadへの最初の引数はロードするライブラリである.これは/Library/myLibrary.dylibのような絶対ファイル名で与えることができる.しかし,パスから見た相対指定をライブラリに与える方がより便利である.また,ダイナミックライブラリの拡張子はプラットフォームによって異なるので,これは拡張子の指定においても問題となる.ダイナミックライブラリの拡張子の慣例を以下にまとめる.

Windowsdll
UnixとLinuxso
Mac OS Xdylib

各システムにおけるライブラリの拡張子

ダイナミックライブラリに対応したWolfram言語の関数は自動的にこの問題を解決する.入力名に拡張子が付いていなければ,使用中のプラットフォームに適したものが加えられる.これによりどのマシンを使っているかに関係なくライブラリを操作することができる.またWolfram言語は,ライブラリの検索にパス$LibraryPathを提供する.

$LibraryPathの設定の例を以下に示す.ライブラリを含むWolfram言語アプリケーションが数多く含まれている.

In[14]:=
Click for copyable input
Out[14]=

FindLibraryはライブラリを探す関数である.これはLibraryFunctionLoad等の別のコマンドで呼び出される.FindLibraryはまずライブラリの拡張子を修正し,絶対名としてライブラリを捜し,最後に$LibraryPathでライブラリを探す.そして見付かったファイルを返す.

次の例では$LibraryPathを探し,使用中のプラットフォーム(この例ではWindows)に適したライブラリを見付ける.

In[15]:=
Click for copyable input
Out[15]=

拡張子付きの名前を使うこともできるが,それは拡張子が適しているプラットフォームでのみ動作することに注意されたい.

In[16]:=
Click for copyable input
Out[16]=

$LibraryPathには要素を加えることができる.しかし,多くのフォルダが自動的に加えられる.これには $UserBaseDirectory/Applicationsまたは$BaseDirectory/Applicationsに含まれるWolfram言語アプリケーションのLibraryResourcesフォルダも含まれる.これは J/Link がJavaクラスをロードすることができ,DatabaseLink がデータベースリソースがロードできる方法に似ている.これにより,Wolfram言語での作業をライブラリと組み合せる便利な方法が提供される.

独自のライブラリのインストール

Wolfram言語セッションで使うためにダイナミックライブラリをインストールしたい場合には,数々のオプションがある.以下にまとめる.

絶対パス名

LibraryFunctionLoad等の関数に絶対パス名を割り当てることができる.これは設定が簡単であるが,作業中のものを別のコンピュータに移動したりすると問題が生じる.

$LibraryPathの設定

ライブラリの場所を$LibraryPathに加えることができる.変数に場所を設定して何度も使えるため,これは絶対パス名を使う場合より抽象化されたものとなる.

さらに,Blockを使って$LibraryPathの設定を一時的に変更することもできる.以下に例を示す.

In[9]:=
Click for copyable input
$BaseDirectoryと$UserBaseDirectory

$LibraryPathは常に$UserBaseDirectory/SystemFiles/LibraryResources/$SystemID$BaseDirectory/SystemFiles/LibraryResources/$SystemIDの2つの場所を含む.ここにライブラリを置けば,ライブラリをロードする関数によって見付けられる.

$BaseDirectoryの中の場所を示す.

In[17]:=
Click for copyable input
Out[17]=
アプリケーション

コードをWolfram言語アプリケーションとして作りたい場合は,$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

ライブラリの依存関係

ライブラリが他のライブラリに依存している場合は,その依存しているライブラリが利用できることを確認しておく必要がある.これはこれらの余剰ライブラリをシステムのどこかにインストールするためにWolfram言語が実行される環境を変更するか,またはLD_LIBRARY_PATH (Linux)やPATH (Windows)のようなパス環境変数を変えることで行える.

また、自分のライブラリをロードする前にLibraryLoadを使って依存しているライブラリをロードするという方法もある.LibraryLoadLibraryFunctionLoadと違って関数を返さない.これは依存しているライブラリをロードするためだけに存在するものである.

Mac OS Xでは,ダイナミックライブラリ(ファイル拡張子はdylib)はより複雑なメカニズムを持っており,依存しているライブラリは特定の場所のみ検索するように設定できる.otoolinstall_name_tool等のコマンドについて調べる必要がある.

ライブラリのバージョン情報

LibraryLink パッケージを使うと,ライブラリのバージョンについての情報を得ることができる.このパッケージは共有ライブラリを扱うための追加ツールを提供する.

パッケージを使うには,まずロードしなければならない.

In[18]:=
Click for copyable input

ライブラリを見付ける.

In[19]:=
Click for copyable input
Out[19]=

ここでLibraryVersionInformationがライブラリについての情報を表示する規則のリストを返す.

In[20]:=
Click for copyable input
Out[20]=

または,次に示すようにLibraryVersionStringを使ってバージョン情報を文字列として得ることもできる.

In[4]:=
Click for copyable input
Out[4]=

ライブラリをロードする際の問題

ライブラリのロードで問題がある場合は,$LibraryErrorを使ってロードしない原因について調べてみるとよい.これはLibraryLink`コンテキストの中にあるが,使うためにパッケージをロードする必要はない.

ここではカレンダーの計算に使用するライブラリがロードされる.

In[22]:=
Click for copyable input
Out[22]=

このライブラリは他のライブラリへの依存関係がある.通常Wolfram言語は他のライブラリを先にロードする.しかしカレンダーライブラリだけを直接ロードしようとしている上,まだカレンダー関数を使ったことがない場合,このロード操作は次のように失敗に終る.

次のようにして,$LibraryErrorを使ってこのエラーの詳細を知ることができる.

In[3]:=
Click for copyable input
Out[3]=
Click for copyable input

Windowsでは見付けられなかった依存ライブラリの名前を与えないが,他のプラットフォームでは名前が分かる.