CodeZine(コードジン)

特集ページ一覧

SQLiteで“おこづかいちょう”

SQLite、C++/CLI、C#で実用(?)アプリケーションを作る

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2008/05/14 14:00
目次

"おこづかいちょう"のからくり

 おこづかいちょうのフォーム(UI)はC#で作りました。毎月の一覧にListView、日付の入力にDateTimePicker、そしておなじみのTextBokButtonLabelを配置しています。

 一覧には年月、摘要、収入、支出、残額のカラムを並べています。

 C#で実装するUIはデータベースの存在をまったく意識せず、C++/CLIによるデータベースのラッパがUIの実現に必要なメソッドを提供し、UIはそれらを利用します。データベースのラッパに必要なのは以下の3つです。

  • レコードの挿入
  • (キーを指定した)レコードの削除
  • (年/月を指定した)レコードの列挙

 データベース上のテーブルには日付、摘要、金額(+なら収入/-なら支出)、さらに削除レコードを一意に特定するため、重複しないキーを与えることにしましょう。

 ListViewへのレコードの列挙はUI側で用意したハンドラをcallbackすることで実現します。列挙の開始と終了時にはイベント引数を変えて開始/終了が検出できるようにします。

 C++/CLIで実装するUIとのインターフェース「CashBookModel.h」を示します。

CashBookModel.h
// CashBookModel.h

#pragma once

struct sqlite3;

namespace CashBookModel {

  // Record : 列挙時のイベント引数を兼ねる
  public ref class Record : System::EventArgs {
  public:
    int              key;     // キー
    System::DateTime date;    // 日付
    System::String^  summary; // 摘要
    int              amount;  // 金額
  };

  // Record列挙の最初/最後を知らせるマーカ
  public ref class EnumStart : System::EventArgs {};
  public ref class EnumEnd   : System::EventArgs {};


  public ref class CashBook {
  private:
    sqlite3*              db_;
    System::EventHandler^ notification_;
    System::String^       select_;
    void EnumRecords();

  public:
    CashBook();  // コンストラクタ
    ~CashBook(); // デストラクタ
    !CashBook(); // ファイナライザ

    // Record列挙ハンドラの設定
    void SetRecordListener(System::EventHandler^ notif);
    // Record挿入
    void AddRecord(Record^ rec);
    // Record削除
    void RemoveRecord(int key);
    // 月/日を指定してRecord列挙
    void EnumRecords(int yyyy, int mm);

  };

}

SQLiteによる実装(C++/CLI)

 それでは上記のCashBookの実装です。SQLite-APIを適宜呼び出してサクサクいきましょう。

CashBookModel.cpp
#define _CRT_SECURE_NO_WARNINGS

#include <cstring>
#include <sqlite3.h>
#include "CashBookModel.h"

using namespace System;

namespace CashBookModel {

  // System::String^ を wchar_t* に変換
  wchar_t* dup_wcs(String^ s) {
    if ( s == nullptr || s->Length == 0 ) {
      return 0;
    }
    array<wchar_t>^ warray = s->ToCharArray();
    pin_ptr<wchar_t> pin = &(warray[0]);
    wchar_t* result = new wchar_t[warray->Length+1];
    wcsncpy(result, pin, warray->Length);
    result[warray->Length] = L'\0';
    return result;
  }

  // dup_wcs で確保した文字列を解放する
  void free_wcs(void* p)
  { delete static_cast<wchar_t*>(p); }

  // wide-string → String^
  inline String^ to_clistr(const void* p) 
  { return gcnew String(static_cast<const wchar_t*>(p)); }

  // コンストラクタ
  CashBook::CashBook() {
    // データベースをopen
    sqlite3* db;
    sqlite3_open16(L"cashbook.db", &db);
    db_ = db;
    // テーブルをcreate
    sqlite3_stmt* statement;
    const wchar_t* command = 
        L"CREATE TABLE IF NOT EXISTS cashbox "
        L"( key INTEGER PRIMARY KEY AUTOINCREMENT," 
        L"  date TEXT NOT NULL, summary TEXT, "
        L"  amount INTEGER NOT NULL)";
    sqlite3_prepare16_v2(db_, command, -1, &statement, 0);
    sqlite3_step(statement);
    sqlite3_finalize(statement);
  }

  // デストラクタ
  CashBook::~CashBook() { 
    this->!CashBook(); 
  }

  // ファイナライザ
  CashBook::!CashBook() { 
    // データベースをclose
    if ( db_ != 0 ) {
      sqlite3_close(db_); 
      db_ = 0;
    }
  }

  // レコードの挿入
  void CashBook::AddRecord(Record^ rec) {
    sqlite3_stmt* statement;
    const wchar_t* command
       = L"INSERT INTO cashbox ( date, summary, amount ) 
                  VALUES ( ?, ?, ? )";
    sqlite3_prepare16_v2(db_, command, -1, &statement, 0);
    sqlite3_bind_text16(statement, 1, 
           dup_wcs(rec->date.ToString(L"yyyy/MM/dd")), -1, &free_wcs);
    sqlite3_bind_text16(statement, 2, 
           dup_wcs(rec->summary), -1, &free_wcs);
    sqlite3_bind_int(statement, 3, rec->amount);
    sqlite3_step(statement);
    sqlite3_finalize(statement);
    EnumRecords();
  }

  // レコードの削除
  void CashBook::RemoveRecord(int key) {
    sqlite3_stmt* statement;
    const wchar_t* command = L"DELETE FROM cashbox WHERE key=?";
    sqlite3_prepare16_v2(db_, command, -1, &statement, 0);
    sqlite3_bind_int(statement,1,key);
    sqlite3_step(statement);
    sqlite3_finalize(statement);
    EnumRecords();
  }

  // 月/日を指定したレコードの列挙
  void CashBook::EnumRecords(int yyyy, int mm) {
    System::DateTime dt(yyyy,mm,1);
    select_ = dt.ToString(L"yyyy/MM") + L"/%";
    EnumRecords();
  }

  // レコードの列挙
  void CashBook::EnumRecords() {
    if ( notification_ == nullptr ) return;
    notification_(this, gcnew EnumStart()); // '列挙開始'を通知
    // テーブルから該当する月/日のレコードを検索
    sqlite3_stmt* statement;
    const wchar_t* command = L"SELECT * FROM cashbox 
                                WHERE date LIKE ? ORDER BY date";
    sqlite3_prepare16_v2(db_, command, -1, &statement, 0);
    sqlite3_bind_text16(statement,1, dup_wcs(select_),-1, &free_wcs);
    while ( sqlite3_step(statement) == SQLITE_ROW ) {
      // Recordを生成して通知
      Record^ rec = gcnew Record();
      rec->key     = sqlite3_column_int(statement,0);
      rec->date    = DateTime::Parse(to_clistr(sqlite3_column_text16(
                                               statement,1)));
      rec->summary = to_clistr(sqlite3_column_text16(statement,2));
      rec->amount  = sqlite3_column_int(statement,3);
      notification_(this,rec);
    }
    sqlite3_finalize(statement);
    notification_(this, gcnew EnumEnd()); // '列挙終了'を通知
  }

  // ハンドラの登録
  void CashBook::SetRecordListener(EventHandler^ notif) {
    notification_ = notif;
  }

}

// リンク時のwarning抑止
struct sqlite3 {};
struct sqlite3_stmt {};
マーシャリング・ライブラリ
 managed文字列(System::String)とwchar_t*との相互変換ルーチンがありますが、Visual C++ 2008(VC++9)ならマーシャリング・ライブラリを使って楽に書けます。
マーシャリング・ライブラリのサンプル
#include <locale>
#include <iostream>
#include <msclr/marshal.h> // marshal-lib.

using namespace msclr::interop;
using namespace std;

int main() {
  wcout.imbue(locale("japanese"));
  marshal_context ctx;
  System::String^ input = L"本日は晴天なり";
  wcout << ctx.marshal_as<const wchar_t*>(input) << endl;
  // ここで得られたwchar_t*の領域はctxのデストラクトと共に解放される
}

  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

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

    C++に首まで浸かったプログラマ。 Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"り わんくま同盟でたまにセッションスピーカやったり 中国茶淹れてにわか茶人を気取ってたり、 あと Facebook とか。 著書: - STL標準...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5