Libraryの構成とライフサイクル

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

このセクションではWolfram Libraryの構成と,それがどのように書かれているかについて述べる.また,初期化から使い方,初期化解除まで,ライブラリのライフサイクルのいろいろな部分についても取り上げる.

Wolfram Libraryから初めて関数がLibraryFunctionLoadでロードされると,その初期化関数が呼び出される.これはコールバック関数をサポートする構成を設定するものであるが,ライブラリ自体に特有のあらゆる初期化を実行するのに使うこともできる.

一旦ライブラリがロードされると,LibraryFunctionLoadが返すLibraryFunctionでその関数を呼び出すことができる.追加の関数をロードすることもできる.

ライブラリは使い終わったらアンロードされる.これはすべての関数がLibraryFunctionUnloadでアンロードされたとき,またはライブラリがLibraryUnloadでアンロードされたときに行われる.この時点で初期化解除関数が呼び出される.

ライブラリを完全にアンロードすることはすべてのプラットフォームでサポートされている訳ではない.初期化解除関数は必ず呼び出され,ライブラリの関数はWolfram言語から使えなくなる.しかし,ダイナミックライブラリのアンロードを完全にサポートしているプラットフォームでだけは,ライブラリを変更して再ロードすることができる.このドキュメントが書かれた時点では,WindowsとMac OS X 10.5がライブラリのアンロードを完全にサポートしている

初期化

LibraryFunctionLoadを使ってライブラリを初めてロードすると,いくつかの重要な初期化が行われる.これによりライブラリ関数がWolfram言語にコールバックできるように構成を設定する.またバージョニングも管理する.ライブラリの初期化にはWolframLibrary_initializeという関数が提供されている.一般に,ライブラリの最初の部分は以下のようなものである.

#include "WolframLibrary.h"

DLLEXPORT mint WolframLibrary_getVersion( ) {
    return WolframLibraryVersion;
}

DLLEXPORT int WolframLibrary_initialize( WolframLibraryData libData) {
    return 0;
}

まず種々の重要な定義を設定するヘッダ"WolframLibrary.h"がロードされる.その次はWolframLibraryヘッダのバージョンを返す WolframLibrary_getVersionである.その後,Wolfram言語はWolframLibrary_initializeを呼び出して,ライブラリの構築に使われるバージョンのWolframLibraryヘッダに合致するデータ構造を渡す.関数構造はライブラリに適応的なので,ライブラリを新バージョンのWolfram言語にロードするときにそのライブラリを再度コンパイルする必要はない.しかし,新しいバージョンのヘッダでビルドされたライブラリを古いバージョンのWolfram言語で使うことはできない.WolframLibrary_initialize関数はライブラリで実行する必要がある可能性のある他の初期化も含むことができる.返された値が非零の値なら,それは致命的なエラーであるとみなされ,ライブラリファイルはロードされない.

使用中のライブラリがWolfram言語との通信にWolfram Symbolic Transfer Protocol (WSTP)を使う場合は,以下のように"WolframLibrary.h"ヘッダの前に"wstp.h"ヘッダをインクルードしなければならない.

#include "wstp.h"

#include "WolframLibrary.h"

関数,引数,結果

ライブラリはWolfram言語から呼び出される関数を提供する.これらの関数はLibraryFunctionLoadで指定されている.インターフェースがC呼出し規約を使うため,各関数は同じシグネチャ,つまりint demoI_I(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res)でなければならない.以下はその例である.

DLLEXPORT int demo_I_I(WolframLibraryData libData,
            mint Argc, MArgument *Args, MArgument Res) {
    mint I0;
    mint I1;
    I0 = MArgument_getInteger(Args[0]);
    I1 = I0 + 1;
    MArgument_setInteger(Res, I1);
    return LIBRARY_NO_ERROR;
}

MArgumentはWolframLibrary.hで定義されている,サポートされた基本の型のポインタの共用体のtypedefである.

typedef union {
    mbool *boolean;
    mint *integer;
    double *real;
    mcomplex *cmplex;
    MTensor *tensor;
    MSparseArray *sparse;
    MNumericArray *numeric;
    MImage *image;
    char **utf8string;
} MArgument;

関数はint demo_I_I(mint Argc, MArgument *Args, MArgument Res)というシグネチャで,intを返し,3つの引数(1つ目は引数の数を与える機械整数,2つ目はMArgumentの配列,3つ目はMArgument)を取らなければならない.2つ目と3つ目の引数はそれぞれ入力と結果を保持する.引数の配列から実際の値を抽出して結果を割り当てなければならない.この抽出とMArgumentへの割当てに便利なように,MArgumentで始まる名前のいくつかのマクロが定義されている.返されたint値はエラーコードとして使われる.非零の値が返された場合はすべてエラーとみなされ,評価の結果としてLibraryFunctionErrorが返される.エラーコードについての詳細は「エラーの結果のコード」のセクションを参照のこと.

型をどのように使うのかについての詳細は「型指定」のセクションを参照されたい.

DLLEXPORTを加えることは一般的である.これはWolframLibrary.hで定義されている.これは関数をWindows DLLからのエキスポートとして宣言するWindowsで必要となるが,これを使うと別のプラットフォームへのライブラリの移行が簡単になる.

MArgument共用体の一部ではないライブラリ関数に引数を送ったり,ライブラリから結果を取得したりするには,引数と結果にLinkObjectを使うとよい.これによりどのようなWolfram言語式の構造でもライブラリに送り,結果としてWolfram言語式を得ることが可能となる.以下にLinkObject関数の例を示す.文字列はWSTPを使わなくても直接扱うことができることに注目されたい.

DLLEXPORT int reverseString( WolframLibraryData libData, WSLINK mlp)
{
    int res = LIBRARY_FUNCTION_ERROR;
    long len;
    const char *inStr = NULL;
    char* outStr = NULL;
    
    if ( !WSTestHead( mlp, "List", &len))
        goto retPt;
    if ( len != 1)
        goto retPt;

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

    if ( ! WSNewPacket(mlp) )
        goto retPt;

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

ライブラリ関数がLinkObjectを使い,エラーが生じることなく戻されたら,その関数はその後の呼出しでも同じLinkObjectを再利用する.そのため,リンクが部分的に読み込んだ,または部分的に書き込んだデータがそこにないようにし,リンクのエラーはすべてクリアされているようにすることが大切である.エラーはWSClearErrorを使ってクリアできる.

ライブラリ関数がエラーを返したり,ライブラリ関数の実行中に放棄が生じたりした場合,LinkObjectは破棄され,新しいものが作成される.この場合,ライブラリ関数はリンクをどのような状態においておいてもよい.放棄はAbortQコールバックを使って検出できる.I

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

Wolfram言語との通信にWSTP を使う方法についての詳細情報はLinkObject引数と結果のセクションを参照されたい.

命名規則

関数名はWolfram言語にロードするときに使われる.ライブラリをC++としてコンパイルするなら,名前をCの名前としてエキスポートするか,またはLibraryFunctionLoadでC++の変更された名前を使わなければならない.前者は以下のような宣言で行える.

extern "C" {
    DLLEXPORT int funname(WolframLibraryData libData,
        mint Argc, MArgument *Args, MArgument Res);
}

これにより,ライブラリがC++としてコンパイルされていても,funnameが関数のロードに使用できるようになる.

別の方法として,WolframLibrary.hで定義されたマクロを使って,コードをよりポータブルにすることもできる.

EXTERN_C DLLEXPORT int funname(WolframLibraryData libData, 
        mint Argc, MArgument *Args, MArgument Res);

ライブラリ関数ではWolfram言語からの引数とWolfram言語への結果以外には希望するどのような型でも使うことができる.これを以下にまとめる.

mboolブール
mint機械整数
double機械倍精度数
mcomplex機械複素数
MTensorパックアレー
MSparseArray疎配列
MImage2D画像または3D画像
UTF8StringUTF8文字列

WolframLibrary.hにこれらすべての型の定義がある.

mintはWolfram言語の機械整数型に対応する整数型で,32ビットのことも64ビットのこともある.

コールバック

Wolfram言語に含まれている機能が追加で必要となることがよくある.例えばパックアレーを作ってその要素を扱うとき等である.これは関数の引数として渡すWolframLibraryData オブジェクトで行える.以下の例ではパックアレーを作り,要素を加える.

    T0 = funStruct->MTensor_new(MType_Integer, 1, dims);
    for ( i = 1; i <= I0; i++) {
        libData->MTensor_setInteger( T0, i, i*2);
    }

Wolfram言語におけるコールバック評価

ライブラリ関数からWolfram言語を呼び出して,計算を行ったり入出力関数を呼び出したり等のタスクを実行したい場合は,getWSLINKprocessWSLINKでできる.

以下の例では,WSTP接続をどのように確立し,WSTP APIの関数を使ってリンク上でEvaluatePacket内にどのように式を書き込むかを示す.終了processWSLINKを呼び出すと,Wolfram言語は評価を実行する.

    WSLINK link = libData->getWSLINK(libData);
    WSPutFunction( link, "EvaluatePacket", 1);
    WSPutFunction( link, "Message", 2);
    WSPutFunction( link, "MessageName", 2);
    WSPutSymbol( link, "MyFunction");
    WSPutString( link, "info");
    WSPutString( link, "Message called from within Library function.");
    libData->processWSLINK( link);
    pkt = WSNextPacket( link);
    if ( pkt == RETURNPKT) {
        WSNewPacket(link);
    }

Wolfram言語からの結果はWolfram言語の標準パケット規則に従う.例えばEvaluatePacketは結果をReturnPacketで返す.

エラー

ライブラリ関数にエラーが見付かり,ユーザにそれを報告したい場合は,コールバック関数Messageが使える.次の例ではメッセージLibraryFunction::rankerrorが発される.メッセージ用のテキストを提供しなければならない.

if (libData->MTensor_getRank(T0) != 1) {
    libData->Message("rankerror");
    return LIBRARY_FUNCTION_ERROR;
}

ライブラリからLIBRARY_FUNCTION_ERRORを返すと,LibraryFunctionErrorの結果が返されることに注意.

メッセージを送るためのより柔軟な方法には,前述のコールバック評価テクニックがある.

エラーの結果のコード

ライブラリ関数の結果はintであることが想定されており,エラーが生じたかどうかを見極めるのに使われる.返された結果が0またはLIBRARY_NO_ERRORの場合は,関数が正しく完了したということである.しかし,それ以外の結果が返された場合,これはエラーがあったことを示しており,関数呼出しの結果はLibraryFunctionError式となる.

返されるエラーコードは何種類もある.以下の表にそれをリストする.

LIBRARY_NO_ERROR関数からの正しい結果(値は0)
LIBRARY_TYPE_ERROR予期しない型の出現
LIBRARY_RANK_ERROR予期しない階数の出現
LIBRARY_DIMENSION_ERROR次元の不整合
LIBRARY_NUMERICAL_ERROR数値計算中のエラーの発生
LIBRARY_MEMORY_ERRORメモリ割当て時の問題
LIBRARY_FUNCTION_ERROR関数の一般的なエラー

エラーコード

非零のエラーコードそれぞれにより,関数呼出しが終了したときに特定のタイプのメッセージが示される.ただし,LIBRARY_FUNCTION_ERRORはメッセージを発せず,関数呼出しを行うアプリケーションが独自のメッセージを示す.

以下の例では,例題用関数がロードされる.この関数は入力を返すので,種々のコードの効果を見ることができる.

ライブラリ関数からLIBRARY_FUNCTION_ERRORの値である6を返す.評価の結果はLibraryFunctionError式で,エラーコード名とその数値を含んでいる.

コールバックにおけるエラー

コールバック関数でエラーが生じると,エラーコードが返される.

例えばMTensor_getIntegerコールバックは整数型のMTensorから要素を取得するが,整数を含まないMTensorで呼び出されたり,要素が存在しなかったりした場合は,エラーコードが返される.

int err;
...
err = libData->MTensor_getInteger( T0, lens, &res);

MTensorが正しい型であり,十分な要素があると確信している場合は,結果を調べる必要はない.

初期化解除

Wolfram言語がライブラリを使い終わったら,ライブラリを終了する関数を呼び出す.以下に例を示す.関数はWolframLibraryDataオブジェクトを受け取る.これは必要ならコールバック関数として使える.

DLLEXPORT void WolframLibrary_uninitialize( WolframLibraryData libData) {
    return;
}