SHOEISHA iD

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

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

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

特集記事

Visual C++ 2012:stateless-lambdaとSQLiteのぷち拡張

 実装側(sqlite3_ex.cpp)ではUnicodeで渡されたSQL文字列をUTF-8に変換してsqlite3_execに与えます。lambdaで書かれた内部コールバックではUTF-8からUnicodeへの変換を施して本来の(sqlite3_exec16に与えられた)コールバックを呼んでいます。

list-17
#include <sqlite3.h>    // SQLite3
#include <sqlite3_ex.h> // SQLite3 extensions

#include <Windows.h> // MultiByteToWideChar / WideCharToMultiByte
#include <vector>    // vector
#include <cassert>   // assert
#include <string>    // string
#include <utility>   // pair
#include <numeric>   // accumulate

int sqlite3_exec16(sqlite3* db, const wchar_t* sql, 
                   int (*callback)(void*,int,wchar_t**,wchar_t**), void* arg, char** errmsg) {
  // SQL文を UTF-8 に変換し、
  int len = WideCharToMultiByte(CP_UTF8,0, sql,-1, 0,0, 0,0);
  assert(len != 0);
  std::string sql8(len,'\0');
  len = WideCharToMultiByte(CP_UTF8,0, sql,-1, &sql8[0], sql8.size(), 0,0);
  assert(len != 0);
  typedef std::pair<void*,int (*)(void*,int,wchar_t**,wchar_t**)> arg_t;
  arg_t internal_arg(arg,callback);
  // sqlite3_exec を呼び出す
  //  (結果の変換はコールバック関数(lambda)内で行う)
  return sqlite3_exec(
    db, sql8.c_str(), 
    // ここからlambda
    [](void* arg, int n, char** vals, char** cols) -> int {
      arg_t* internal_arg = static_cast<arg_t*>(arg);
      if ( internal_arg->second == nullptr ) return 0;

      std::vector<int> vlen(n*2, 0);
      // UTF-8→UNICODE 変換に必要な領域の大きさを求め、確保する
      for ( int i = 0; i < n; ++i ) {
        if ( vals[i] != nullptr ) {
          int len = MultiByteToWideChar(CP_UTF8,0, vals[i], -1, nullptr, 0);
          assert(len != 0);
          vlen[i] = len;
        }
        if ( cols[i] != nullptr ) {
          int len = MultiByteToWideChar(CP_UTF8,0, cols[i], -1, nullptr, 0);
          assert(len != 0);
          vlen[n+i] = len;
        }
      }
      std::vector<wchar_t> buf(std::accumulate(begin(vlen),end(vlen),0));
      wchar_t* pos = &buf[0];
      // 変換し、配列に設定する
      std::vector<wchar_t*> vptr(n*2, nullptr);
      for (int i = 0; i < n; ++i ) {
        if ( vlen[i] > 0 ) {
          int len = MultiByteToWideChar(CP_UTF8, 0, vals[i], -1, pos, vlen[i]);
          assert(len != 0);
          vptr[i] = pos;
          pos += len;
        }
        if ( vlen[n+i] > 0 ) {
          int len = MultiByteToWideChar(CP_UTF8, 0, cols[i], -1, pos, vlen[n+i]);
          assert(len != 0);
          vptr[n+i] = pos;
          pos += len;
        }
      }
      // コールバック関数を呼び出す
      return (*internal_arg->second)(internal_arg->first, n, &vptr[0], &vptr[n]);
    }
    // ここまでlambda
    , &internal_arg, errmsg);
}

int sqlite3_exec16_ex(sqlite3* db, const wchar_t* sql, 
                      int (*callback)(void*,int,wchar_t**,wchar_t**), void* arg) {
  char* errmsg;
  int result = sqlite3_exec16(db, sql, callback, arg, &errmsg);
  if ( result != SQLITE_OK ) {
    sqlite_error err(result, errmsg);
    sqlite3_free(errmsg);
    throw err;
  }
  return result;
}

int sqlite3_exec_ex(sqlite3* db, const char* sql, 
                    int (*callback)(void*,int,char**,char**),void* arg) {
  char* errmsg;
  int result = sqlite3_exec(db, sql, callback, arg, &errmsg);
  if ( result != SQLITE_OK ) {
    sqlite_error err(result, errmsg);
    sqlite3_free(errmsg);
    throw err;
  }
  return result;
}

 おためしコードと実行結果を示します。

list-18
#include <sqlite3.h>    // SQLite3
#include <sqlite3_ex.h> // extention
#include <locale>       // locale
#include <iostream>     // wcout, endl
#include <cassert>      // assert
#include <Windows.h>

using namespace std;

int main() {
  wcout.imbue(locale("jpn"));
  sqlite3* db;
  
  // データベースのオープン 
  int rc = sqlite3_open16(L":memory:", &db);
  assert( rc == SQLITE_OK );

  // SQL statement トレース(おまけ)
  sqlite3_trace(db, 
                [](void*, const char* msg) {
                  wchar_t wmsg[512];
                  MultiByteToWideChar(CP_UTF8, 0, msg, -1, wmsg, 512);
                  wcout << wmsg << endl;
                }, 
                nullptr);

  try {
    sqlite3_exec16_ex(db,
      L"CREATE TABLE 果物 ( 名前 TEXT, 価格 INTEGER);"
      L"INSERT INTO 果物 VALUES ( 'りんご' ,    100 );"
      L"INSERT INTO 果物 VALUES ( 'ばなな',     120 );"
      L"INSERT INTO 果物 VALUES ( 'さくらんぼ', 140 );"
      L"INSERT INTO 果物 VALUES ( 'どりあん',  NULL );");

    sqlite3_exec16_ex(db,
      L"SELECT * FROM 果物 WHERE 名前 > 'と';",
      [](void* ,int n, wchar_t** vals, wchar_t** cols) {
        for ( int i = 0; i < n; ++i ) {
          wcout << cols[i] << L"=[" 
                << (vals[i] ? vals[i] : L"(null)") << L"]\n";
        }
        wcout << endl;
        return 0;
      });

  } catch (const sqlite_error& ex) {
    cout << "SQLite error " << ex.code() << ':' << ex.what() << endl;
  }
  sqlite3_close(db);
}

最後に

 ...実は同様のココロミを5年前の「SQLiteで組み込みDB体験(2007年版)」でやらかしてます。そこではSQLiteのソースに手を入れて実装しましたが、今回はUTF-8版に文字コード変換のカワを被せることでSQLite本体を触らない実装です。すこしばかり遠回りではありますが、SQLiteの非公開関数を呼ぶことがないのでインタフェースが変わらぬ限りSQLiteのどのバージョンでも動くのがセールスポイントです。

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

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

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

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

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

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

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/6983 2013/02/21 14:00

イベント

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

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

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

メールバックナンバー