Очень часто вижу на форумах темы, связанные с организацией доступа к базам данных для приложений на C++. Тема, сама по себе, довольно актуальная и очень интересная, хотя у новичков зачастую вызывает трудности. И в этот раз я хочу рассказать о том, что написание кросс-платформенных приложений на C++, использующих для своей работы базы данных, не является чем-то непосильным, а также о том, что написание кросс-платформенных приложений может оказаться довольно увлекательным занятием.
Итак, эта статья о разработке приложений, использующих для своей работы базы данных SQLite.
Для работы нам понадобится:

Для начала, создадим новый проект (Win32 Project) в Visual Studio 2005 и назовем его SQLiteTest

В мастере создания проекта выберем тип проекта Windows Application и в дополнительных свойствах укажем Empty project

Затем распакуем библиотеку DatabaseLayer в папку, в которой находится файл SQLiteTest.sln

В Solution Explorer жмём правой кнопкой на названии Solution’а, выбираем пункт Add -> Existing Project

И в окне выбора файла проекта, выбираем проект databaselayer/build/databaselayer_databaselayer_sqlite.dsp (или .vcproj).

Теперь нам необходимо указать зависимости проектов. Наше приложение должно быть собрано после сборки библиотеки Databaselayer, поэтому в Solution Explorer жмем правой кнопкой на названии проекта SQLiteTest и выбираем пункт меню Project Dependencies…

В списке проектов ставим маркер на названии проекта databaselayer_sqlite и жмем OK

Далее, выбираем пункт меню Build -> Configuration Manager и указываем конфигурации сборки каждого проекта для каждой конфигурации сборки Solution’а. Я использую статическую раздельную Unicode-сборку wxWidgets, поэтому для Debug-конфигурации сборки Solution’а я выбираю конфигурацию Static Unicode Debug Multilib проекта databaselayer_sqlite и конфигурацию Debug проекта SQLiteTest. Такие же действия необходимо проделать и для Release-конфигурации сборки Solution’а, все вхождения слова Debug в названиях конфигураций на Release

После этого, распаковываем архив sqlite-source-x_y_z.zip в папку databaselayer/sqlite/include и архив sqlitedll-x_y_z.zip в папку databaselayer/sqlite/lib
В каталоге databaselayer/sqlite/lib должны появиться файлы sqlite3.dll и sqlite3.def
Всё это очень хорошо, но для использования динамической библиотеки в нашем проекте, неплохо было бы иметь .lib файл, которого у нас сейчас нет. Для того, чтобы он у нас был, создаем два batch-скрипта:
setupvars.bat
"C:/Program Files/Microsoft Visual Studio 8/VC/bin/vcvars32.bat"
export.bat
lib.exe /def:sqlite3.def /machine:x86 /out:sqlite3.lib
Запускаем командную строку, переходим в папку databaselayer/sqlite/lib и по очереди запускаем эти два batch-файла
setupvars
export
После выполнения этих нехитрых действий, у нас должны появиться два файла: sqlite3.exp и sqlite3.lib
Копируем файл sqlite3.dll в папку SQLiteTest/bin
Отлично, теперь создаем в нашем проекте SQLiteTest четыре новых файла:
- SQLiteTestApp.h
- SQLiteTestApp.cpp
- SQLiteTestMainFrame.h
- SQLiteTestMainFrame.cpp
Теперь открываем свойства проекта, переходим в раздел C/C++ и в настройках Additional Include Directories добавляем две новые записи:
- $(ProjectDir)../databaselayer/sqlite/include
- $(ProjectDir)../databaselayer/ include

Переходим в раздел Linker и в настройках Additional Library Directories добавляем две новые записи:
- $(ProjectDir)../databaselayer/sqlite/lib
- $(ProjectDir)../databaselayer/ lib

В разделе Linker -> Input в настройках Additional Dependencies добавляем записи:
- advapi32.lib
- comctl32.lib
- uuid.lib
- rpcrt4.lib
- wxbase28ud.lib
- wxmsw28ud_core.lib
- wxmsw28ud_adv.lib
- wxpngd.lib
- wxcode_msw28ud_databaselayer_sqlite.lib
- sqlite3.lib
Не забываем, что суффикс d в названиях библиотек wxWidgets указывает на то, что используется отладочная версия библиотеки. Release-версии библиотек не имеют в названии этого суффикса.

Далее, в разделе Linker -> General для всех конфигураций указываем параметр Output File равным ../bin/$(ProjectName).exe и в разделе Debugging параметр Working Directory равным ../bin. Это позволит генерировать исполняемый файл в отдельную папку.

Итак, предварительная настройка завершена, можно приступать к написанию кода.
SQLiteTestMainFrame.h
#ifndef _SQLITE_TEST_MAINFRAME_H
#define _SQLITE_TEST_MAINFRAME_H
#include <wx/wx.h>
class SQLiteTestMainFrame : public wxFrame
{
void CreateControls();
public:
SQLiteTestMainFrame();
bool Create(wxWindow * parent, wxWindowID id, const wxString & title);
DECLARE_EVENT_TABLE()
void OnExit(wxCommandEvent & event);
};
#endif
SQLiteTestMainFrame.cpp
#include "SQLiteTestMainFrame.h"
#include "SQLiteTestApp.h"
BEGIN_EVENT_TABLE(SQLiteTestMainFrame, wxFrame)
EVT_MENU(wxID_EXIT, SQLiteTestMainFrame::OnExit)
END_EVENT_TABLE()
SQLiteTestMainFrame::SQLiteTestMainFrame()
{
Create(NULL, wxID_ANY, _("SQLite Addressbook"));
}
bool SQLiteTestMainFrame::Create(wxWindow * parent, wxWindowID id, const wxString & title)
{
bool res = wxFrame::Create(parent, id, title, wxDefaultPosition, wxSize(700, 500));
if(res)
{
CreateControls();
}
return res;
}
void SQLiteTestMainFrame::CreateControls()
{
wxMenuBar * menuBar = new wxMenuBar;
SetMenuBar(menuBar);
wxMenu * fileMenu = new wxMenu;
fileMenu->Append(wxID_EXIT, _("ExittAlt+F4"));
menuBar->Append(fileMenu, _("File"));
CreateStatusBar(2);
Centre();
}
void SQLiteTestMainFrame::OnExit(wxCommandEvent & event)
{
wxUnusedVar(event);
Close();
}
SQLiteTestApp.h
#ifndef _SQLITE_TEST_APP_H
#define _SQLITE_TEST_APP_H
#include <wx/wx.h>
#include <Databaselayer.h>
#include <SqliteDatabaseLayer.h>
class SQLiteTestApp : public wxApp
{
DatabaseLayer * m_Database;
public:
virtual bool OnInit();
virtual int OnExit();
bool ConnectToDatabase();
DatabaseLayer * GetDatabase();
};
DECLARE_APP(SQLiteTestApp)
#endif
SQLiteTestApp.cpp
#include <wx/image.h>
#include <DatabaseLayerException.h>
#include "SQLiteTestApp.h"
#include "SQLiteTestMainFrame.h"
IMPLEMENT_APP(SQLiteTestApp)
bool SQLiteTestApp::OnInit()
{
if(!ConnectToDatabase())
{
wxFAIL_MSG(_("Error connecting to database!"));
return false;
}
wxImage::AddHandler(new wxPNGHandler);
wxImage::AddHandler(new wxJPEGHandler);
SQLiteTestMainFrame * frame = new SQLiteTestMainFrame;
SetTopWindow(frame);
frame->Show();
return true;
}
int SQLiteTestApp::OnExit()
{
if(m_Database)
{
if(m_Database->IsOpen())
{
m_Database->Close();
}
wxDELETE(m_Database);
}
return wxApp::OnExit();
}
bool SQLiteTestApp::ConnectToDatabase()
{
m_Database = new SqliteDatabaseLayer();
wxString db_filename(wxT("addressbook.db"));
PreparedStatement * pStatement(NULL);
bool bCreate = !wxFileExists(db_filename);
if(bCreate)
{
wxMessageBox(_("Database does not exist... recreating."));
}
try
{
m_Database->Open(db_filename);
// Try to recreate tables
try
{
m_Database->RunQuery(wxT("CREATE TABLE groups(id integer primary key,
name varchar(128) not null,
description varchar(512));"));
}
catch(DatabaseLayerException & e) {wxUnusedVar(e);}
try
{
m_Database->RunQuery(wxT("CREATE TABLE persons(
id integer primary key,
groupid integer not null,
first_name varchar(64) not null,
last_name varchar(64) not null,
gender boolean not null,
address varchar(128) not null,
city varchar(64) not null,
country varchar(32) not null,
phone varchar(32),
email varchar(128),
website varchar(260));"));
}
catch(DatabaseLayerException & e) {wxUnusedVar(e);}
if(bCreate)
{
m_Database->RunQuery(
wxT("INSERT INTO groups(name, description) VALUES ('Friends', 'My friends')"));
pStatement = m_Database->PrepareStatement(
wxT("INSERT INTO persons(groupid, first_name,last_name,gender,address,city,country,phone) VALUES
(?,?,?,?,?,?,?,?)"));
if (pStatement)
{
pStatement->SetParamInt(1, 1);
pStatement->SetParamString(2, _("John"));
pStatement->SetParamString(3, _("Doe"));
pStatement->SetParamBool(4, 1);
pStatement->SetParamString(5, _("SomeStreet st., 123/45"));
pStatement->SetParamString(6, _("Some City"));
pStatement->SetParamString(7, _("Some Country"));
pStatement->SetParamString(8, _("+000-00-000-00-00"));
pStatement->RunQuery();
m_Database->CloseStatement(pStatement);
pStatement = NULL;
}
}
}
catch(DatabaseLayerException & e)
{
if(pStatement)
{
m_Database->CloseStatement(pStatement);
pStatement = NULL;
}
wxFAIL_MSG(e.GetErrorMessage());
return false;
}
return true;
}
DatabaseLayer * SQLiteTestApp::GetDatabase()
{
return m_Database;
}
Итак, что же мы сделали: мы создали класс приложения SQLiteTestApp
, содержащий указатель на объект класса DatabaseLayer, который и будет обеспечивать связь с нашей базой данных. При запуске приложения проверяется наличие файла addressbook.db и, в случае если файл не найден, он создается. Вместе с базой данных создаются две таблицы: groups и persons и заполняются тестовыми данными.
Главная форма приложения содержит строку меню и строку статуса и, пока, ничего не делает.

После успешной сборки приложения и его запуска, в папке bin должен появиться файл базы данных addressbook.db

Отлично, теперь можно приступать к работе с базой данных.
Читать продолжение статьи…