SQLite Vesion 3 DLL版 を呼び出すC/C++アプリケーションの構築

SQLite Version 3 の DLL の導入と、C/C++アプリケーションからそれを使う方法を解説します。


Step1. 必要なファイルをダウンロードする

SQLite本家サイトのダウンロードページから、次の2つのzipファイルを入手します。 ファイル名末尾の数字はリリースバージョンであり、その数字は頻繁に変わります。 3_3 以降の適切なものを選んでダウンロードしてください。

Step1 のまとめ

Step2. ローカルディスクに展開する

適当な作業フォルダを準備します。

	C>mkdir sqlite-sample
	C>cd sqlite-sample

sqlitedll-3_3_3.zip には以下の2つのファイルが格納されています。 これを適当なunzipツールで作業フォルダに展開します。

	Archive:  sqlitedll-3_3_3.zip
	  Length     Date   Time    Name
	 --------    ----   ----    ----
	   259072  06-02-01 01:24   sqlite3.dll
	     2055  06-02-01 01:24   sqlite3.def
	 --------                   -------
	   261127                   2 files

sqlite-source-3_3_3.zip にはSQliteのソース一式が格納されています。 そのうち、DLL版を使う場合に必要なソースは sqlite3.h のみです。 適当なunzipツールで sqlite3.h を作業フォルダに展開します。

	C>unzip sqlite-source-3_3_3.zip sqlite3.h

Step2 のまとめ

Step3. インポートライブラリを作成する

Visual C++ の lib コマンドを使って、インポートライブラリ sqlite3.lib を作成します。

	C>lib /def:sqlite3.def
	Microsoft (R) Library Manager Version 6.00.8447
	Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
	
	LIB : warning LNK4068: /MACHINE の指定がありません; IX86 をデフォルトとします
	   ライブラリ sqlite3.lib とオブジェクト sqlite3.exp を作成中

Step3 のまとめ

Step4. SQLite3をリンクしてビルドする

単純にデータベースファイルを開いて閉じるだけのサンプルプログラムを用意します。

///@file sample1.cpp - how to open and close SQLite3's database.
#include <stdio.h>
#include <stdlib.h>
#include "sqlite3.h"

int main(int argc, char** argv)
{
	sqlite3* db;
	int err;
	char* db_name = (argc > 1) ? argv[1] : "sample.db";

	// open database
	err = sqlite3_open(db_name, &db);

	if (err != SQLITE_OK) {
		// show error and exit.
		fputs(sqlite3_errmsg(db), stderr);
		exit(1);
	}

	// close database
	err = sqlite3_close(db);

	return EXIT_SUCCESS;
}
// sample1.cpp - end.

このサンプルをコンパイルし、Step3で作成したインポートライブラリとリンクします。

	C>cl /MD sample1.cpp sqlite3.lib
	Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
	Copyright (C) Microsoft Corp 1984-1998. All rights reserved.
	
	sample1.cpp
	Microsoft (R) Incremental Linker Version 6.00.8447
	Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
	
	/out:sample1.exe
	sample1.obj
	sqlite3.lib

出来上がったサンプルを実行してみます。 成功すればカレントフォルダに "sample.db" というファイルが生成されます。

	C>sample1
	C>dir *.db
	2006/02/16  17:58                 0 sample.db
	               1 個のファイル                   0 バイト

出来上がったサンプルの実行ファイルを別のフォルダに移動して実行すると、 Windows が "sqlite3.dll"が見つからない" というエラーメッセージボックスを出して、実行を中断します。

データベースファイル名のエンコードについて

SQLiteをWindowsで使用する場合、データベースファイル名のエンコードに注意が必要です。
sqlite3_open() は、API仕様上はUTF-8エンコードしたファイル名を受け取ることになっています。 WindowsNT/XP系では、この仕様通りに動作します。 しかしながらWindows9X系では、UTF-8エンコードすると逆にファイル名が文字化けしてしまいます。 望みのファイル名にするためには、UTF-8エンコードをせず現在ロカールのMBCS文字列を直接渡さなければなりません。
これはSQLite3のWindows版実装のバグです。

Step4 のまとめ

Step5. データベースを操作するプログラムを作成する

データベースにデータを登録し、情報を抽出するプログラムを作成します。
このプログラムは、mbox形式の受信メールボックスファイルを解析し、送信元のIPアドレスを出現頻度の多い順に10個選んで報告します。 その情報は、大量の迷惑メールを送り出す発信元を特定するための手掛かりとして使えます。
この機能を実現するために、データベースを次のように利用します。

データベース操作の概要

  1. データベースに次のレコードを持つテーブル mail を作成します。
    ip1: INTEGER ip2: INTEGER ip3: INTEGER ip4: INTEGER net: TEXT is_private: TINYINT header: TEXT
    4分割したIPアドレス値 IPアドレスのネットワーク部 プライベートIPなら1、否なら0 Receivedヘッダ
    具体的には次のSQL文を実行します。
    CREATE TABLE mail(ip1 INTEGER, ip2 INTEGER, ip3 INTEGER, ip4 INTEGER, net TEXT, is_private TINYINT, header TEXT);
    
  2. 入力ストリームから "Received: from " で始まる行を抜き出し、 "(数値.数値.数値.数値)" または "[数値.数値.数値.数値]" のパターンを含んでいるものを、mail テーブルのheaderカラムに登録します。 検出した数値のパターンをIPアドレスとみなし、残りのフィールドに登録します。
    具体的には次のSQL文を実行します。VALUESの括弧内は抜き出した内容に従います。
    INSERT INTO mail(ip1,ip2,ip3,ip4,net,is_private,header)
              VALUES(192,168,0,1,'192.168.0.1/24',1,'Received: from unknown(192.168.0.1) by ...');
    
    このとき登録処理全体を1つのトランザクションにするため、登録実行ループの前後で次のSQL文を実行します。
    BEGIN;
    登録実行ループ
    COMMIT;
    
  3. 入力ストリームを読みきり、全てを登録し終えたら、出現頻度の多い非プライベートIPアドレスを10個取り出すように、データベースに照合をかけます。
    具体的には次のSQL文を実行します。
    SELECT ip1,ip2,ip3,ip4,net,count(*) FROM mail
      GROUP BY ip1,ip2,ip3,ip4 HAVING is_private=0
      ORDER BY count(*) DESC LIMIT 10;
    
  4. 照合結果を1行毎にコールバック関数で受け取り、それを表示します。

ソースコード

///@file sample2.cpp - how to insert and select data in SQLite3's database.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sqlite3.h"

#include "subfunc.cpp"	// IP and FILE I/O functions.

/** print result of SQL select. this is called by sqlite3_exec() */
int select_callback(void*, int argc, char** argv, char** columnNames)
{
	// argv[] has a result set of SQL "SELECT ip1,ip2,ip3,ip4,net,count(*) FROM ....;"
	printf("IP:[%s.%s.%s.%s], NET:[%s], %s:%s\n",
		argv[0],	// ip1
		argv[1],	// ip2
		argv[2],	// ip3
		argv[3],	// ip4
		argv[4],	// net
		columnNames[5], argv[5]	// count(*)
		);
	return 0;
}

/** program main.
 * usage: sample2 [dbfile [mailbox]]
 * this program works:
 *  - scan received header which contain IP-adderss number from mailbox
 *  - insert them into dbfile
 *  - select top 10 IPs from dbfile
 *  - dbfile default: sample.db
 *  - mailbox default: stdin
 */
int main(int argc, char** argv)
{
	sqlite3* db;
	int err;
	char* db_name = (argc > 1) ? argv[1] : "sample.db";
	char* sql;

	// open input
	FILE* fin = (argc > 2) ? fopen(argv[2], "rb") : stdin; // use "rb" to input beyond Ctrl-Z.
	if (!fin) {
		perror(argv[2]);
		exit(1);
	}

	// open database
	err = sqlite3_open(db_name, &db);

	if (err != SQLITE_OK) {
		// show error and exit.
		fprintf(stderr, "sqlite3_open: %s\n", sqlite3_errmsg(db));
		exit(1);
	}

	// create "mail" table
	err = sqlite3_exec(db,
		"CREATE TABLE mail("
		  "ip1 INTEGER, ip2 INTEGER, ip3 INTEGER, ip4 INTEGER, net TEXT,"
		  "is_private TINYINT, header TEXT);",
		NULL, 0, 0);

	if (err != SQLITE_OK)
		fprintf(stderr, "sqlite3_exec(CREATE TABLE): %s\n", sqlite3_errmsg(db));

	// begin transaction
	sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);

	char buf[1024*8];
	while (fgets(buf, sizeof(buf), fin)) {
		// parse a received header-line from input
		int c;
		unsigned ip[4];
		if (strncmp(buf, "Received: from ", 15) != 0 || !scan_ip(buf+15, ip))
			continue;

		while ((c = fpeek(fin)) == ' ' || c == '\t')
			fcatgets(buf, sizeof(buf), fin);	// append lines until header end.

		const char* net = net_address(ip);
		bool is_private = is_private_ip(ip);

		// insert IP and header-line into "mail" table
		sql = sqlite3_mprintf(
			"INSERT INTO mail(ip1,ip2,ip3,ip4,net,is_private,header) VALUES(%u,%u,%u,%u,'%s',%d,'%q');",
			ip[0], ip[1], ip[2], ip[3], net, is_private, buf);

		err = sqlite3_exec(db, sql, NULL, NULL, NULL);

		if (err != SQLITE_OK)
			fprintf(stderr, "sqlite3_exec(INSERT INTO mail): %s\n", sqlite3_errmsg(db));

		sqlite3_free(sql);
	}//.endwhile

	// commit transaction
	sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);

	// select top 10 ip from "mail" table.
	puts("** Top 10 IPs **");
	err = sqlite3_exec(db,
		"SELECT ip1,ip2,ip3,ip4,net,count(*) FROM mail"
		"  GROUP BY ip1,ip2,ip3,ip4 HAVING is_private=0"
		"  ORDER BY count(*) DESC LIMIT 10;",
		select_callback, NULL, NULL);

	if (err != SQLITE_OK)
		fprintf(stderr, "sqlite3_exec(SELECT): %s\n", sqlite3_errmsg(db));

	// close database
	sqlite3_close(db);

	// close input
	fclose(fin);

	return EXIT_SUCCESS;
}
// sample2.cpp - end.

Step5 のまとめ

Step6. プログラムを高速化する

より高度なAPIを用いて、先のステップで作成したプログラムを高速化します。

ソースコード

///@file sample3.cpp - how to prepare and step in SQLite3's database.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sqlite3.h"

#include "subfunc.cpp"	// IP and FILE I/O functions.

/** print result of SQL select using by sqlite3_column_xxx() */
void print_result(sqlite3_stmt* select_sql)
{
	// print result set of SQL "SELECT ip1,ip2,ip3,ip4,net,count(*) FROM ....;"
	printf("IP:[%d.%d.%d.%d], NET:[%s], %s:%s\n",
		sqlite3_column_int(select_sql, 0),	// ip1
		sqlite3_column_int(select_sql, 1),	// ip2
		sqlite3_column_int(select_sql, 2),	// ip3
		sqlite3_column_int(select_sql, 3),	// ip4
		sqlite3_column_text(select_sql, 4),	// net
		sqlite3_column_name(select_sql, 5),	// count(*)
		sqlite3_column_text(select_sql, 5)	// count(*)
	);
}

/** program main.
 * usage: sample3 [dbfile [mailbox]]
 * this program works:
 *  - scan received header which contain IP-adderss number from mailbox
 *  - insert them into dbfile
 *  - select top 10 IPs from dbfile
 *  - dbfile default: sample.db
 *  - mailbox default: stdin
 */
int main(int argc, char** argv)
{
	sqlite3* db;
	int err;
	char* db_name = (argc > 1) ? argv[1] : "sample.db";
	char* sql;

	// open input
	FILE* fin = (argc > 2) ? fopen(argv[2], "rb") : stdin; // use "rb" to input beyond Ctrl-Z.
	if (!fin) {
		perror(argv[2]);
		exit(1);
	}

	// open database
	err = sqlite3_open(db_name, &db);

	if (err != SQLITE_OK) {
		// show error and exit.
		fprintf(stderr, "sqlite3_open: %s\n", sqlite3_errmsg(db));
		exit(1);
	}

	// create "mail" table
	err = sqlite3_exec(db,
		"CREATE TABLE mail("
		  "ip1 INTEGER, ip2 INTEGER, ip3 INTEGER, ip4 INTEGER, net TEXT,"
		  "is_private TINYINT, header TEXT);",
		NULL, 0, 0);

	if (err != SQLITE_OK)
		fprintf(stderr, "sqlite3_exec(CREATE TABLE): %s\n", sqlite3_errmsg(db));

	// make prepared SQL for mail table
	sqlite3_stmt* insert_sql;
	sqlite3_stmt* select_sql;
	sql = "INSERT INTO mail(ip1,ip2,ip3,ip4,net,is_private,header) VALUES(?,?,?,?,?,?,?);";
	sqlite3_prepare(db, sql, strlen(sql), &insert_sql, NULL);
	const int bind_net = 5;
	const int bind_is_private = 6;
	const int bind_header = 7;

	sql = "SELECT ip1,ip2,ip3,ip4,net,count(*) FROM mail"
		"  GROUP BY ip1,ip2,ip3,ip4 HAVING is_private=0"
		"  ORDER BY count(*) DESC LIMIT 10;";
	sqlite3_prepare(db, sql, strlen(sql), &select_sql, NULL);

	// begin transaction
	sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);

	char buf[1024*8];
	while (fgets(buf, sizeof(buf), fin)) {
		// parse a received header-line from input
		int c;
		unsigned ip[4];
		if (strncmp(buf, "Received: from ", 15) != 0 || !scan_ip(buf+15, ip))
			continue;

		while ((c = fpeek(fin)) == ' ' || c == '\t')
			fcatgets(buf, sizeof(buf), fin);	// append lines until header end.

		const char* net = net_address(ip);
		bool is_private = is_private_ip(ip);

		// insert IP and header-line into "mail" table
		sqlite3_reset(insert_sql);
		sqlite3_bind_int(insert_sql, 1, ip[0]);
		sqlite3_bind_int(insert_sql, 2, ip[1]);
		sqlite3_bind_int(insert_sql, 3, ip[2]);
		sqlite3_bind_int(insert_sql, 4, ip[3]);
		sqlite3_bind_int(insert_sql, bind_is_private, is_private);
		sqlite3_bind_text(insert_sql, bind_net,    net, strlen(net), SQLITE_STATIC);
		sqlite3_bind_text(insert_sql, bind_header, buf, strlen(buf), SQLITE_STATIC);
		err = sqlite3_step(insert_sql);

		if (err != SQLITE_DONE)
			fprintf(stderr, "sqlite3_step(INSERT INTO mail): %s\n", sqlite3_errmsg(db));
	}//.endwhile

	// commit transaction
	sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);

	// select top 10 ip from "mail" table.
	puts("** Top 10 IPs **");
	while ((err = sqlite3_step(select_sql)) == SQLITE_ROW)
		print_result(select_sql);

	if (err != SQLITE_DONE)
		fprintf(stderr, "sqlite3_step(SELECT): %s\n", sqlite3_errmsg(db));

	// discard prepared SQL
	sqlite3_finalize(insert_sql);
	sqlite3_finalize(select_sql);

	// close database
	sqlite3_close(db);

	// close input
	fclose(fin);

	return EXIT_SUCCESS;
}
// sample3.cpp - end.

Step6 のまとめ


Last Update: Feb.27, 2006

Valid HTML 4.01 Transitional