Очень часто вижу на форумах темы, связанные с организацией доступа к базам данных для приложений на C++. Тема, сама по себе, довольно актуальная и очень интересная, хотя у новичков зачастую вызывает трудности. И в этот раз я хочу рассказать о том, что написание кросс-платформенных приложений на C++, использующих для своей работы базы данных, не является чем-то непосильным, а также о том, что написание кросс-платформенных приложений может оказаться довольно увлекательным занятием.
Итак, эта статья о разработке приложений, использующих для своей работы базы данных SQLite.
Для работы нам понадобится:
- Библиотека wxWidgets-2.8:
- СУБД SQLite3
- Библиотека DatabaseLayer for wxWidgets
- Утилита wxActiveRecordGenerator (wxARG)
Для начала, создадим новый проект (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
Отлично, теперь можно приступать к работе с базой данных.
1 Comment