実装側(sqlite3_ex.cpp)ではUnicodeで渡されたSQL文字列をUTF-8に変換してsqlite3_execに与えます。lambdaで書かれた内部コールバックではUTF-8からUnicodeへの変換を施して本来の(sqlite3_exec16に与えられた)コールバックを呼んでいます。
#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;
}
おためしコードと実行結果を示します。
#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のどのバージョンでも動くのがセールスポイントです。
