Wolframコンパイラで外部ライブラリを呼び出す

はじめに

コンパイル型言語(C,C++,Rust,Swift,Haskell等)の多くはC互換性の動的ライブラリをコンパイルすることができる.これらのライブラリの関数はコンパイルされたWolfram言語で直接呼び出すことができるため,トップレベルのWolfram言語と動的ライブラリの間の高性能リンクを書くことが可能になる.

このチュートリアルには,単独のOpenSSL関数への接続の短い例の他,大規模なSQLite機能のインターフェースの拡張された例が含まれている.

OpenSSLとの接続

OpenSSLは暗号化機能を実装するC互換ライブラリを中心としている.この例ではOpenSSLの暗号論的疑似乱数生成器に接続する.

OpenSSLは以下の型のシグネチャを使ってRAND_bytesという関数を公開する:

int RAND_bytes(unsigned char *buf, int num);

第1引数のbufは,それが乱数のバイトを書き込むバッファである.第2引数のnumbufに書き込むバイト数である.

この関数宣言は次のLibraryFunctionDeclarationで表すことができる:

(OpenSSLはWolfram言語カーネルによってデフォルトでロードされているため,第2引数でライブラリを指定する必要がなかった.)

この宣言を使って関数をコンパイルする:

上の関数は作用するいくつかの概念を使う.

まず,指定された長さで管理される"CArray"を作成するためにCreateTypeInstanceが使われる.これはランダムなバイトが書き込まれるバッファである.

次にLibraryFunctionDeclarationによって宣言されたOpenSSL関数を呼び出すためにLibraryFunction["RAND_bytes"]が使われる.

最後にCreateTypeInstanceを再び使ってバッファの要素を抽出しそれらを"NumericArray"にコピーする.このタスクにはFromRawPointerを使うこともできる.

すべてをコンパイルして,関数を実行し,暗号論的擬似乱数のバイトを10個得る:

SQLiteとの接続

SQLiteはSQLデータベースを実装するC互換ライブラリである.このチュートリアルを実行するためにはSQLiteがパクレットとしてダウンロードされていなければならない.

次のコマンドを評価してSQLiteをダウンロードする:

パクレットはPacletUninstallを実行するとアンインストールすることができる.

SQLiteの文書化されたCインターフェースには以下のシグネチャを持つsqlite3_libversion_numberという関数が含まれている:

int sqlite3_libversion_number(void);
このCシグネチャは次のLibraryFunctionDeclarationで表すことができる:
Wolfram Compilerを使って,このライブラリ関数を使うプログラムをコンパイルする:

これは単独の関数の呼出しを示しているが,同じメカニズムを使って複雑なカスタムデータ構造を動的ライブラリの間のやり取りをする複雑なリンクを作成することができる.

このチュートリアルはトップレベルのWolfram言語とSQLiteの間の効率的なリンクを作成するのに必要なステップを説明する.まずSQLiteデータベースを開き,自動的に閉じるためのシステムを作成する.それからこれらのデータベースでクエリを実行する.最後にクエリから結果を読み戻し,その後のデータ解析に使えるようにそれをWolfram言語の式に変換する.

データベースを開き閉じる

SQLiteデータベースに接続するための第一歩は,それを開き閉じることである.これは2つの関数で行うことができ,以下のシグネチャを持つ:

int sqlite3_open(
const char *filename, /* Database filename (UTF-8) */
sqlite3 **ppDb /* OUT: SQLite db handle */
);

int sqlite3_close(sqlite3*);
これらは,sqlite3*タイプのエイリアスとともに次のLibraryFunctionDeclarationのインスタンスに変換することができる:
これらを使って,パスが与えられたときにデータベースを開いたり閉じたりする簡単な関数をコンパイルする:
SQLiteデータベースへのパス上でこの関数を実行するとエラーコード0 (SQLITE_OK)が戻る:
前のプログラムでは,必要でなくなったときデータベースは手動で閉じられた.これは自動のメモリ管理を加える"Managed"型を使って簡約することができ,データベースは範囲を超えると自動的に閉じる:
既存のSQLiteデータベースを開き,メモリ管理されたオブジェクトとして返す:

この"Managed"オブジェクトにはコンパイルされたコードとトップレベルのコードの両方における共有参照カウントがあり,その参照カウントが0になると解放コードが実行される.つまりトップレベルでSQLiteデータベースオブジェクトを送り合ってもメモリ的に安全ということである.

簡単なクエリ

データベースを開いたり(自動的に)閉じたりできるようになったので,クエリを実行してみる.SQLiteの最も簡単なクエリインターフェースはsqlite3_execを介したものである:

int sqlite3_exec(
sqlite3*, /* An open database */
const char *sql, /* SQL to be evaluated */
int (*callback)(void*,int,char**,char**), /* Callback function */
void *, /* 1st argument to callback */
char **errmsg /* Error msg written here */
);
コールバックとエラーメッセージインターフェースを無視すると,sqlite3_execは次のLibraryFunctionDeclarationで表すことができる:
データベースオブジェクト(先に定義したopenDB関数から戻されるもの等)を取る関数をコンパイルし,それでクエリを実行する:
試すためには,データベースの一時ファイルを作成する:
データベースを開く:
新しいデータベースに表を作成するクエリを実行する:

これはエラーコード0を返す.表が作成できたということを意味する.

同じクエリを再び実行するとエラーコード1が戻る.表がすでに存在するためクエリが失敗したことを表す:

このexecquery関数はデータベース上で任意のクエリが実行できるが,クエリの結果を読み込む,または便利な形式で返すためのメカニズムはない.次のセクションではこれについて説明する.

読込み機能を加える

クエリ結果の走査

クエリ結果から読み込むためには,前とは少し異なるインターフェースを使う.sqlite3_execを使う代りに,sqlite3_prepareを使ってクエリ文を生成し,sqlite3_stepを使って結果に踏み込む.クエリ文を終了するためにsqlite3_finalizeも必要である.これらの型のシグネチャは次の通りである:

int sqlite3_prepare(
sqlite3 *db, /* Database handle */
const char *zSql, /* SQL statement, UTF-8 encoded */
int nByte, /* Maximum length of zSql in bytes. */
sqlite3_stmt **ppStmt, /* OUT: Statement handle */
const char **pzTail /* OUT: Pointer to unused portion of zSql */
);

int sqlite3_step(sqlite3_stmt*);

int sqlite3_finalize(sqlite3_stmt *pStmt);
これらのシグネチャを,sqlite3_stmtポインタのTypeDeclarationとともにLibraryFunctionDeclarationsに変換する:
クエリをコンパイルし,メモリ管理された文オブジェクトを返す関数を宣言する:
クエリ文オブジェクトを取り,その結果に踏み込み,結果の数を返す関数をコンパイルする:
例のデータベースを開き,顧客の表の行数を数える:

行を式に変換する

上の関数により,クエリを実行し結果を走査することができる.しかし各行からデータを読み込みそれをWolfram言語式に変換するためにはライブラリ関数がもっと必要になる.特にこれらのSQLite関数は列数とその名前,型を取り出すのに使われる.

int sqlite3_column_count(sqlite3_stmt *pStmt);

const char *sqlite3_column_name(sqlite3_stmt*, int N);

int sqlite3_column_type(sqlite3_stmt*, int iCol);
これらは次のLibraryFunctionDeclarationsで表すことができる:

これらのSQLite関数は各行のさまざまな型のデータを抽出するために使われる:

double sqlite3_column_double(sqlite3_stmt*, int iCol);

sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);

const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
これらは以下の追加のLibraryFunctionDeclarationsで表すことができる:
次に,文オブジェクトと列番号を取り,列の値を式として返す関数を宣言する.sqlite3_column_typeを使って列の型を得,値を得るために適切な関数を呼び出す:
最後にクエリ結果の指定された行からすべての列を抽出する関数を宣言する:

クエリ関数のコンパイル

openDBを使ってデータベースを開き自動的に閉じることができるようになったので,createStatementでクエリをコンパイルすることができる.クエリ結果の各行はrowAssociationで式に変換することができる.後はそれをまとめるだけである.

すべてのものを,SQLiteデータベースとクエリ文字列を取り,結果を連想のリストとして返す関数にコンパイルする:
SQLiteデータベースを開く:
クエリする:
大規模なクエリを実行し,結果をDatasetに置く:

完全なコード

FunctionCompileの1回の呼出しにおける完全なコード: