SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

C++/CLIによるCライブラリの.NET化

C#でSQLiteを使うには


SQLite3Petite

 C++/CLIで.NETのmanagedクラスを作っていきます。まずはヘッダから実装します。

 メンバ変数はC版SQLite3でデータベースの中心となる sqlite3* が1つ。メソッドはコンストラクタ/デストラクタ/ファイナライザ、そしてopen/close/execがあればいいでしょう。コールバックのためのdelegateも忘れずに作成します。

SQLite3Petite.h
/*
* 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_open16sqlite3_closeを呼ぶだけです。

 ただし、openの引数に与えられるDBファイル名の型はSystem::Stringであるのに対して、sqlite3_open16に与えるのはwchar_t*でなくてはならず、System::Stringwchar_t*に変換しなければなりません。そのために小さな変換関数を用意しました。

逆変換は?
 System::Stringwchar_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>に用意されていました。これを使わせていただきましょう。

SQL文の実行
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);
  }
}

次のページ
試運転 C#からSQLiteを使う

この記事は参考になりましたか?

特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1382 2008/08/19 19:22

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー