SQLite3の拡張DLLをMinGWで作ってみる
SQLite はアプリケーション組み込み等の用途に使われているデータベースエンジン。2chビューアとかにも使われてるかな。version 3.3.9 から拡張DLLでユーザ定義のSQL関数等を定義できるようになって、直接バイナリに手を入れられない状況でも拡張性が高くなった。たとえばWebサーバのPHPからSQLiteを扱う人にはいいニュースなんじゃないかな。え、PHPに組み込まれてるのはSQLite version 2だって?かっこ悪いねー。
個人的にはWindowsでSQLiteを扱う機会が多いので、MinGWでSQLiteの拡張DLLを作成する手順を調べてみたよ。
バイナリとヘッダのダウンロード
SQliteの公式サイトのダウンロードページから次のファイルを入手する。
- sqlite3.exe http://www.sqlite.org/sqlite-3_3_10.zip
- sqlite3.dll,sqlite3.def http://www.sqlite.org/sqlitedll-3_3_10.zip
- sqlite3.h,sqlite3ext.h http://www.sqlite.org/sqlite-source-3_3_10.zip
MinGW用のインポートライブラリの生成
拡張DLLを作るだけなら不要なんだけど、MinGWからsqlite3.dllを使う時に必要になる。
dllに同梱されているdefファイルをMinGWのdlltoolで
dlltool --dllname sqlite3.dll --input-def sqlite3.def --output-lib libsqlite3.a
コードとMakefile
下記のコードとMakefileをまとめてmake test 。
Makefile
all : doSQL.exe myExtension.dll test : all rm -fr test.db ./doSQL.exe test.db "create table t1(id integer primary key,i1 integer,t1 text)" ./doSQL.exe test.db "insert into t1(i1)values(1)" ./doSQL.exe test.db "insert into t1(i1)values(256)" ./doSQL.exe test.db "insert into t1(i1)values(65536)" ./doSQL.exe test.db "insert into t1(i1)values(16777216)" ./doSQL.exe test.db "insert into t1(i1)values(4294967296)" ./doSQL.exe test.db "insert into t1(i1)values(1099511627776)" ./doSQL.exe test.db "SELECT load_extension('myExtension.dll');select *,half(i1) from t1" ./sqlite3.exe test.db <test2.sql doSQL.exe : doSQL.c gcc -g -O2 -I.. -L.. -o $@ $< -lsqlite3 myExtension.dll : myExtension.c myExtension.def gcc -g -O2 -I.. -c myExtension.c dllwrap -k -def myExtension.def --driver-name gcc -o myExtension.dll myExtension.o # 拡張DLLはインポートしないのでインポートライブラリは不要
doSQL.c
#include <stdio.h> #include <string.h> #include <sqlite3.h> #define ENABLE_SQLITE_KEY 0 #if ENABLE_SQLITE_KEY extern int sqlite3_key(sqlite3 *db, const void *pKey, int nKey); #endif static int callback(void *NotUsed, int argc, char **argv, char **azColName){ int i; for(i=0; i<argc; i++){ printf("%s=%s%c",azColName[i],(argv[i] ? argv[i] : "NULL"),(i+1==argc?'\n':',')); } return 0; } int main(int argc, char **argv){ int retval = 1; if( argc < 3 ){ fprintf(stderr, "Usage: %s DATABASE SQL-STATEMENT [password]\n", argv[0]); }else{ char* tok; sqlite3* db; do{ int rc = sqlite3_open(argv[1], &db); if( rc != SQLITE_OK ){ fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); break; } #if ENABLE_SQLITE_KEY if( argc > 3 ){ rc = sqlite3_key(db,argv[3],strlen(argv[3])); if(rc != SQLITE_OK ){ fprintf(stderr, "sqlite3_key failed: %s\n", sqlite3_errmsg(db)); break; } } #endif rc = sqlite3_enable_load_extension(db,1); if(rc != SQLITE_OK ){ fprintf(stderr, "sqlite3_enable_load_extension failed: %s\n", sqlite3_errmsg(db)); break; } for(tok = strtok(argv[2],";");tok;tok=strtok(NULL,";")){ char* zErrMsg =0; rc = sqlite3_exec(db,tok, callback, 0, &zErrMsg); if( zErrMsg ){ fprintf(stderr, "SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); fprintf(stderr, "sql=%s\n",tok); } if(rc != SQLITE_OK ){ break; } } retval =0; }while(0); sqlite3_close(db); } return retval; }
myExtension.c
#include <sqlite3ext.h> SQLITE_EXTENSION_INIT1 // SQLユーザ関数 half() の実装例 // 入力値の半分の値を返す static void halfFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ sqlite3_result_double(context, 0.5*sqlite3_value_double(argv[0])); } // SQLite拡張の初期化。DLLロード時に呼ばれる。 // 共有ライブラリ中の拡張シンボルはこれ一つで十分ですよ。 // ここでユーザ関数、照合順序、仮想テーブルモジュールを定義すること。 int __stdcall sqlite3_extension_init( sqlite3* db, char** pzErrMsg, const sqlite3_api_routines *pApi ){ SQLITE_EXTENSION_INIT2(pApi) sqlite3_create_function(db, "half", 1, SQLITE_ANY, 0, halfFunc, 0, 0); return 0; }
myExtension.def
LIBRARY myExtension.dll EXPORTS sqlite3_extension_init@12
test2.sql
SELECT load_extension('myExtension.dll'); select *,half(i1) from t1;