SQLite3Petite
C++/CLIで.NETのmanagedクラスを作っていきます。まずはヘッダから実装します。
メンバ変数はC版SQLite3でデータベースの中心となる sqlite3* が1つ。メソッドはコンストラクタ/デストラクタ/ファイナライザ、そしてopen/close/execがあればいいでしょう。コールバックのためのdelegateも忘れずに作成します。
/*
* SQLite3Petite
* SQLite .NETのための薄皮
*/ #pragma once #include "sqlite3.h" using namespace System; using namespace System::Runtime::InteropServices; namespace SQLite3Petite { public enum class Result : int { OK = SQLITE_OK, ERROR = SQLITE_ERROR, INTERNAL = SQLITE_INTERNAL, PERM = SQLITE_PERM, ABORT = SQLITE_ABORT, BUSY = SQLITE_BUSY, LOCKED = SQLITE_LOCKED, NOMEM = SQLITE_NOMEM, READONLY = SQLITE_READONLY, INTERRUPT = SQLITE_INTERRUPT, IOERR = SQLITE_IOERR, CORRUPT = SQLITE_CORRUPT, NOTFOUND = SQLITE_NOTFOUND, FULL = SQLITE_FULL, CANTOPEN = SQLITE_CANTOPEN, PROTOCOL = SQLITE_PROTOCOL, EMPTY = SQLITE_EMPTY, SCHEMA = SQLITE_SCHEMA, TOOBIG = SQLITE_TOOBIG, CONSTRAINT = SQLITE_CONSTRAINT, MISMATCH = SQLITE_MISMATCH, MISUSE = SQLITE_MISUSE, NOLFS = SQLITE_NOLFS, AUTH = SQLITE_AUTH, FORMAT = SQLITE_FORMAT, RANGE = SQLITE_RANGE, NOTADB = SQLITE_NOTADB, ROW = SQLITE_ROW, DONE = SQLITE_DONE, }; public delegate bool Callback( array<String^>^ values, array<String^>^ names); public ref class Database { private: sqlite3* db_; public: Database(); // constructor ~Database(); // destructor !Database(); // finalizer Result open(String^ filename); // open DB file Result open(); // DB in memory Result close(); // close DB Result exec(String^ sql, Callback^ callback, [Out] String^% errmsg); // execute SQL String^ errmsg(); }; }
コンストラクタ/デストラクタ/ファイナライザ
managedクラス内にはnativeなクラス/構造体のインスタンスをおくことはできません(コンパイルエラー)が、ポインタなら許してくれます。ただしこのとき十分な注意が必要です。
managedクラス内にmanagedなクラスを置いた場合、必要に応じて自動的に確保されたメモリを解放してくれます。しかしながらnativeなクラスや構造体を置いたとき、それらは.NETのメモリ管理の対象外となるので、適宜明示的に後始末を行ってあげなくてはなりません。SQLite3Petiteではデータベースをオープンしたままでアプリケーションが終了することのないよう、後始末としてclose()を呼ぶことにします。
C++の場合、デストラクタに後始末処理を書けばいいのですが、C++/CLIのmanagedクラスはデストラクタが必ず呼ばれる保証はなく、ファイナライザで後始末を行わなくてはなりません。そのためコンストラクタ/デストラクタ/ファイナライザは次のような実装となります。
namespace SQLite3Petite { // コンストラクタ Database::Database() { db_ = 0; } // デストラクタ Database::~Database() { this->!Database(); // ファイナライザを呼ぶ } // ファイナライザ Database::!Database() { close(); // DBをクローズする } }
オープン/クローズ
open/closeはきわめて単純で、単にそれぞれsqlite3_open16、sqlite3_closeを呼ぶだけです。
ただし、openの引数に与えられるDBファイル名の型はSystem::Stringであるのに対して、sqlite3_open16に与えるのはwchar_t*でなくてはならず、System::Stringをwchar_t*に変換しなければなりません。そのために小さな変換関数を用意しました。
System::Stringはwchar_t*を引数とするコンストラクタを持っているのでwchar_t*をSystem::Stringに変換する関数は不要ですnamespace SQLite3Petite { // System::String^ → std::wstring static std::wstring to_std(String^ str) { if ( str != nullptr ) { array<__wchar_t>^ warray = str->ToCharArray(); if ( warray->Length != 0 ) { pin_ptr<__wchar_t> wp = &warray[0]; return std::wstring(wp, warray->Length); } } return std::wstring(); } Result Database::open(String^ filename) { sqlite3* d; int result = sqlite3_open16(to_std(filename).c_str(), &d); if ( result == SQLITE_OK ) { db_ = d; } return Result(result); } Result Database::open() { return open(L":memory:"); } Result Database::close() { return Result(sqlite3_close(db_)); } }
SQL文の実行
さてさてこれがいちばん厄介です。SQLite3のSQL実行関数:sqlite3_exec16はコールバックとして関数ポインタを与えますが、SQLite3Petiteはdelegateです。そのため関数ポインタとdelegateとの間でうまくすり合わせを行わなくてはなりません。つまり、ライブラリ内部にsqilte3_exec16に与えられる関数を用意し、その中からdelegateに火をつけるからくりを必要とします。コールバック関数にはユーザーが任意に与えられる引数void* pArgがあるのですが、delegateのvoid*への代入は許されていないのです。
そこでdelegate受け渡しのために、delegateをメンバに持つ小さなnative-structを用意し、そいつのポインタをvoid* pArgに引き渡すことにします。
ところがそこにも問題が生じます。nativeなstruct内にはdelegateすなわちmanagedなメンバを持たせることができないのです。
有難いことにVisual C++にはmanagedハンドルをnative化するヘルパ・クラスgcrootがヘッダ<gcroot.h>に用意されていました。これを使わせていただきましょう。
namespace SQLite3Petite { // delegateを内包するnativeな構造体 struct CallbackHolder { gcroot<Callback^> entry_; // これがdelegate CallbackHolder(Callback^ cb) : entry_(cb) {} }; // sqlite3_exec16に引き渡すコールバック関数 static int cli_callback(void* option, int count, __wchar_t** values, __wchar_t** names) { array<String^>^ values_array = gcnew array<String^>(count); array<String^>^ names_array = gcnew array<String^>(count); for ( int i = 0; i < count; ++i ) { values_array[i] = gcnew String(values[i]); names_array[i] = gcnew String(names[i]); } // CallbackHolderからdelegateを取り出して Callback^ callback = static_cast<CallbackHolder*>(option)->entry_; // それを呼び出す return callback(values_array, names_array) ? 0 : 1; } // SQL文の実行。結果はcallbackに指定したdelegateに引き渡される Result Database::exec(String^ sql, Callback^ callback, String^% errmsg) { __wchar_t* err; // delegateをCallbackHolderで包む CallbackHolder holder(callback); // CallbackHolderを引数にsqlite3_exec16を呼び出す int result = sqlite3_exec16(db_, to_std(sql).c_str(), callback == nullptr ? 0 : cli_callback, &holder, &err); if ( result != SQLITE_OK ) { errmsg = gcnew String(err); sqlite3_free(err); } return Result(result); } }
