12th
Июл

Работа с MySQL в C++

Posted by Chas under Статьи, Топик-обзор

Небольшая статья по взаимодействию с СУБД MySQL из программы на C++

автор статьи: psycho-coder
Тема на форуме

Немного теории

Код:
MYSQL mysql; // Дескриптор соединения. Структура, содержащая HANDLE для одного подключения к серверу.
MYSQL_RES *res; // Дескриптор результирующей таблицы
MYSQL_ROW row; // Массив полей текущей строки
MYSQL_FIELD *field; // Структура, которая содержит всю информацию, касающуюся отдельного поля таблицы

Функиции которые нам понабодятся:

Функция инициализации
  MYSQL *mysql_init(MYSQL *mysql);

Где соответственно host — компьютер, на котором запущена СУБД MySQL, user — имя юзера для подключения, passwd — пароль, db — название предполагаемой для использования базы данных, port — порт, unix_socket — сокет или pipe-канал, который необходимо использовать.

MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned int client_flag)

client_ flag может принимать несколько значений:
CLIENT_COMPRESS — используется сжатие.
CLIENT_FOUND_ROWS — возвращать число найденных строк.
CLIENT_IGNORE_SPACE — делает все имена функций зарезервированными словами.
CLIENT_INTERACTIVE — разрешает interactive_timeout секунд бездействовать (вместо wait_timeout) перед закрытием подключения.
CLIENT_NO_SCHEMA — запрещает синтаксис вида “db_name.tbl_name.col_name” (имя_базы_данных.имя_таблицы.имя_ко лонки). Используется для ODBC.
CLIENT_ODBC — устанавливает то, что это клиент ODBC.
CLIENT_SSL — используется защищенный протокол SSL.

Мы флагами пользоваться не будем.

Функция выполняющая запрос
int mysql_query(MYSQL *mysql, const char *query);

Функция возвращяющая строку с описанием ошибки
Код:

char *mysql_error(MYSQL *mysql);

функция, которая получает все строки результата запроса и хранит их в буфере-клиенте
MYSQL_RES * mysql_store_result(MYSQL *mysql);

Получает количество строк в результате запроса
my_ulonglong mysql_num_rows(MYSQL_RES *res);

Получает количество полей (столбцов) в результате запроса
unsigned int mysql_num_fields(MYSQL_RES *res);

Заполняет массив полей для текущей строки
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);

Заполняет структуру для текущего поля (fieldnr)
MYSQL_FIELD *mysql_fetch_field_direct(MYSQL_RES *res, unsigned int fieldnr);

Начальная “настройка”

Для работы в Builder необходимо конвертировать libmysql.lib.
Для этого, нужно открыть консоль и набрать там это

C:\>”C:\Program Files\Borland\CBuilder6\Bin\coff2omf.exe”
-lib:st “C:\Program Files\Borland\CBuilder6\Lib\libmysql.lib”
“C:\Program Files\Borland\CBuilder6\Lib\libmysql_.lib”

Здесь “C:\Program Files\Borland\CBuilder6\Lib\libmysq l.lib” оригинальная библиотека,
а “C:\Program Files\Borland\CBuilder6\Lib\libmysq l_.lib” конвертированная

У каждого пути будут свои.
Также в папке с программой (или в “C:\Program Files\Borland\CBuilder6\Lib\”) должны быть libmysql_.lib, а для VS libmysql.dll.
Заголовочные файлы можно бросить в папку с программой или в “C:\Program Files\Borland\CBuilder6\Include\”.
Для VS “C:\Program Files\Microsoft Visual Studio Х.0\VC\include”. Где Х – версия VS.
В среде MS VC++ можно использовать библиотеку без конвертации, т.е. libmysql.lib.
Все заголовочные файлы могут быть в папке с программой, но тогда нужно подключать их локально.

Есть замечания для VC++ WinForms.
Так как типы String^ и char[] несовместимы, то для конвертирования из String^ в char[] можно использовать следующие функции (взято из MySQL++):

private: String^ ToUCS2(const char* utf8)
{
   try
   {
     return gcnew String(utf8, 0, strlen(utf8), System::Text::Encoding::Default);
   }
   catch(…)
   {
     return “”;
   }
}
private: Void ToUTF8(char* pcOut, int nOutLen, String^ sIn)
{
   try
   {
     array^ bytes = System::Text::Encoding::Default->GetBytes(sIn);
     nOutLen = Math::Min(nOutLen – 1, bytes->Length);
     System::Runtime::InteropServices::Marshal::Copy(bytes, 0, IntPtr(pcOut), nOutLen);
     pcOut[nOutLen] = ”;
   }
   catch (…)
   {
     pcOut[nOutLen] = ”;
   }
}

Пример использования
const int buf = 512;
char host[buf];
ToUTF8(host, buf, hostText->Text); // Перевод из String^ в char[]
String ^tmp = ToUCS2(mysql_error(&mysql)); // Перевод из char* в String^

Вот все необходимое для работы:
libmysql_lib.rar 2.5 кб
libmysql.lib.rar 5.1 кб
LibMySQL.dll.rar 447 кб
include.rar 73.7 кб

Вывод в консоль

Вывод таблиц в консоли. Интерфейс правда не супер, но для практики думаю хватит.
В коде есть комментарии. Если будут вопросы задавайте здесь

#define __LCC__ // Объявляем директиву без которой программа не может работать. Можно конечно поключить windows.h, но это будет не красиво

#pragma comment(lib, “libmysql_.lib”) // подключаем библиотеку
#include // Заголовочный файл с описание функций
#include
#include

void mysql(const char query[])
{
   MYSQL mysql; // Дескриптор соединения
   MYSQL_ROW row; // Массив полей текущей строки
   MYSQL_RES *res; // Дескриптор результирующей таблицы

   char host[] = “localhost”; // хост
   char user[] = “admin”; // пользователь
   char passwd[] = “admin”; // пароль
   char db[] = “library”; // название базы данных
   int port = 0; // порт. Если порт у сервера MySQL не по умолчанию (3306), то нужно указывать конкретный номер порта

   mysql_init(&mysql); // Инициализация
   mysql_real_connect(&mysql, host, user, passwd, db, port, NULL, 0); // соединение

   if (mysql_query(&mysql, query) > 0) // запорс. Если ошибок нет, то продолжаем работу
   {

     // Если была ошибка, …
     printf(”%s”, mysql_error(&mysql)); // … вывдем ее
     return; // и завершим работу
   }

   res = mysql_store_result(&mysql); // Берем результат,
   int num_fields = mysql_num_fields(res); // количество полей
   int num_rows = mysql_num_rows(res); // и количество строк.

   for (int i = 0; i < num_fields; i++) // Выводим названия полей
   {
     field = mysql_fetch_field_direct(res, i); // Получение названия текущего поля
     printf(”| %s |”, field->name);
   }

   printf(”\n”);

   for (int i = 0; i < num_rows; i++) // Вывод таблицы
   {
     row = mysql_fetch_row(res); // получаем строку

     for (int l = 0; l < num_fields; l++)
       printf("| %s |", row[l]); // Выводим поля

     printf(”\n”);
   }

   printf(”Count records = %d”, num_rows); // Вывод информации о количестве записей
     mysql_free_result(res); // Очищаем результаты
     mysql_close(&mysql); // Закрываем соединение
}

int main()
{
   mysql(”SELECT * FROM t_mid_author”); // Запрос
   getch(); // Ожидаем нажатие клавиши
   return 0;
}

Графический интерфейс
Очередная статья по взаимодействию с СУБД MySQL из программы на С++
Мутим простейший интерфейс
рис 1

Кидаем на форму:
TLabel (5 шт.). В свойство Caption пишем хост, порт и т.д.
TEdit (5 шт.). Названия TEdit’ов: hostText, userText, passText, dbText и portText.
TButton (2 шт.). В свойства Caption пишем “Пошел!” и “Закрыть”.
TMemo (1 шт.)
TStringGrid (1 шт.)

Подключаем заголовочные файлы, библиотеку и объявим одну константу
#define __LCC__
#include
#pragma comment(lib, “libmysql_.lib”) // Для Builder 6. Подробней см. в первой статье
#pragma comment(lib, “libmysql.lib”) // Для MS VC++
// Для других сред программирования не пробовал
const int buf = 512;

Обработчик кнопки “Закрыть” думаю понятен

А в обработчик копки “Пошел!” пишем следующее
/* Проверим что все данные были введены? в.ч. и сам запос (Memo1) */
   if (hostText->Text.IsEmpty() || userText->Text.IsEmpty() ||
     passText->Text.IsEmpty() || dbText->Text.IsEmpty() ||
     portText->Text.IsEmpty() || Memo1->Text.IsEmpty())
   {
   MessageBox(this->Handle, “Не все поля заполнены!”, “Ошибка!”,
     MB_OK | MB_ICONERROR);
   return;
   }

// Тут Вам все должно быть знакомо
   MYSQL mysql;
   MYSQL_ROW row;
   MYSQL_RES *res;
   MYSQL_FIELD *field;

/* Объявляем массивы для работы */
   char host[buf];
   char user[buf];
   char passwd[buf];
   char db[buf];
   char query[buf];
   int port = portText->Text.ToInt();
   int num_fields = 0;
   int num_rows = 0;

/* Инициализируем имя хоста, пользователя, пароль и БД */
   strcpy(host, hostText->Text.c_str());
   strcpy(user, userText->Text.c_str());
   strcpy(db, dbText->Text.c_str());
   strcpy(passwd, passText->Text.c_str());
   strcpy(query, Memo1->Text.c_str()); //*/

   mysql_init(&mysql);
   if (!mysql_real_connect(&mysql, host, user, passwd, db, port, NULL, 0))
   { /* Пробуем подключиться, если кдето ошибка то сообщим об этом */
     MessageBox(this->Handle, mysql_error(&mysql), “Error!”,
       MB_OK | MB_ICONERROR);
     return;
   }

   if (mysql_query(&mysql, query) > 0)
   { /* Пробуе выполнить запрос, если запрос не верен то сообщаем об ошибке,
   Выведем ее и выходим
   */
   MessageBox(this->Handle, mysql_error(&mysql), “Error!”,
     MB_OK | MB_ICONERROR);
   return;
   }

// Получаем результат
   res = mysql_store_result(&mysql);

   /* Устанавливаем кол-во строк в таблице и сохраняем кол-во строк */
   StringGrid1->RowCount = num_rows = mysql_num_rows(res);

/* Устанавливаем кол-во полей и сохраняем это кол-во столбцов */
   StringGrid1->ColCount = num_fields = mysql_num_fields(res);
   StringGrid1->FixedRows = 1; // Фиксируем первую строку.

   for (int i = 0; i < num_fields; i++) // Выводим названия полей
   {
     field = mysql_fetch_field_direct(res, i);
     StringGrid1->Cells[0] = field->name; // В первую строку, которую мы зафиксировали
   }

   for (int i = 1; i < num_rows; i++) // Вывод результата запроса
   {
     row = mysql_fetch_row(res); // Получаем строку
     for (int l = 0; l Cells[l] = row[l]; // Выводим строку по ячейкам
   }

   mysql_free_result(res); // Освобождаем память
   mysql_close(&mysql); // Закрываем соединение

Вот и все. пишем запрос и “Пошел!”.

Графический интерфейс. Специальный проект
Теперь напишем “специальный” клиент для базы данных Библиотека
Для начала создадим базу банных

# create.sql
# Создаем базу данных
CREATE database lib;

# Переключаемся на нее
use lib;

# Добавляем пользователя admin с паролем admin и связываем его с базой library
GRANT ALL ON lib.* TO ‘admin’@’%’ IDENTIFIED BY ‘admin’;

# Создаем таблицу книги
CREATE TABLE IF NOT EXISTS t_books
(
ID INT(6) UNSIGNED NOT NULL AUTO_INCREMENT,
Title CHAR(150) NOT NULL,
FIO CHAR(150) NOT NULL,
PRIMARY KEY(ID),
KEY(Title)
);

Интерфейс формы у меня получился такой.
рис 2

Компоненты:
TLabel и TEdit . по 5 штук, как из предыдущей статьи
ListBox – 1 шт список книг
GroupBox – 1 шт. В нем 2 TLabel и 2 TEdit, название книги и автор
TButton – 5 шт. Их желательно обозвать как у меня (Удалить, Изменить, Добавить, Подключиться, Закрыть).
BitBtn – 1 шт кнопка обновление данных.
TTimer – 1 шт проверка содинения.
Все подробности на скрине.

Объявляем необходимые переменные и подключаем все что нужно…

#define __LCC__
#pragma comment(lib, “libmysql_.lib”)
#include

const int buf = 512; // Буфер
bool connected = false; // Есть соединение или нет
int *arrIDs = NULL; // Массив идентификаторов
int ID = 0; // Идентификатор текущей книги
MYSQL mysql; // Дескриптор соединения
MYSQL_RES *res; // Структура результатов
MYSQL_ROW row; // Массив строк результата

/* Объявляем массивы для работы */
char host[buf];
char user[buf];
char passwd[buf];
char db[buf];
int port = 0;

Настройки таймера:
Enabled := false; // Выключен
Interval := 5000; // 5 секунд

Код обработчика таймера:
if (mysql_ping(&mysql) > 0) // Если соединение разорвано…
{
   connected = false; // ставим флаг дисконнекта
   if (arrIDs) delete []arrIDs; // Очищаем массив
   Timer1->Enabled = false; // … выключаем таймер
   MessageBox(this->Handle, “Соединение с сервером потеряно”, “Ошибка!”,
     MB_OK | MB_ICONERROR); // Выведем сообщение лоб этом
   Button5->Click(); // Запустим повторное соединение
}

Далее обработчики кнопок

Код кнопки “Подключиться”

// Подключение
if (connected) return; // Если уже соединены, то выходим.
/* Проверим, что все данные были введены */
if (hostText->Text.IsEmpty() || userText->Text.IsEmpty() ||
passText->Text.IsEmpty() || dbText->Text.IsEmpty() ||
portText->Text.IsEmpty())
{
MessageBox(this->Handle, “Не все поля заполнены!”, “Ошибка!”,
MB_OK | MB_ICONERROR);
return;
}

/* Инициализируем имя хоста, пользователя, пароль, порт и БД */
strcpy(host, hostText->Text.c_str());
strcpy(user, userText->Text.c_str());
strcpy(db, dbText->Text.c_str());
strcpy(passwd, passText->Text.c_str());
port = portText->Text.ToInt();

mysql_init(&mysql); // Инициализация дескриптора
if (!mysql_real_connect(&mysql, host, user, passwd, db, port, NULL, 0))
{ /* Пробуем подключиться, если кде-то ошибка, то сообщим об этом */
MessageBox(this->Handle, mysql_error(&mysql), “Error!”,
MB_OK | MB_ICONERROR);
return;
}

connected = true; // Соединены
Timer1->Enabled = true; // Порверка соединения каждые 5 сек.
BitBtn1->Click(); // Обновить список

Код кнопки “Обновить” (BitBtn1)

// Обновление списка книг
if (!connected) return; // Если соединения нет, то выходим

/* Так как, мы знаем что нам нужно, то и запрос будет статическим.
Получим названия всех книг и идентификаторов, потом заполним ими ListBox
Сортируем в порядке возрастания по названию книги */

if (mysql_query(&mysql, “SELECT ID, Title FROM t_books ORDER BY Title”) > 0)
{ // Проверка на ошибки
MessageBox(this->Handle, mysql_error(&mysql), “Ошибка!”,
MB_OK | MB_ICONERROR);
return;
}

ListBox1->Clear(); // Очистка списка.
if (arrIDs) delete []arrIDs; // Очистка массива

// Заполняем структуру
res = mysql_store_result(&mysql);
// Получаем количество записей
int count = mysql_num_rows(res); // Получаем количество строк
arrIDs = new int[count]; // Инициализируем массив

for (int i = 0; i < count; i++)
{
// Полчаем строку
row = mysql_fetch_row(res);
// Заполняем массив
arrIDs = StrToInt(row[0]);
// Добавляем в ListBox название книги
ListBox1->Items->Add(row[1]);
}

mysql_free_result(res); // Освобождаем ресурсы

Графический интерфейс. Специальный проект. Продолжение

Код кнопки “Добавить”

// Добавление книг
// Если соединения нет или поля ввода пустые, то выходим

   if (!connected && (bookText->Text.IsEmpty() || authorText->Text.IsEmpty()))
     return;

// Формируем запрос на добавление книги
   AnsiString tmp = “INSERT INTO t_books (ID, Title, FIO) VALUES (NULL,\
     ‘” + bookText->Text + “‘, ‘” + authorText->Text + “‘)”;

   char query[buf]; // Переменная дла запроса
   strcpy(query, tmp.c_str()); // Ковертируем в нужный формат.

// Запрос. Если ошибки есть, то выводим их и выходим из функции
   if (mysql_query(&mysql, query) > 0)
   {
     MessageBox(this->Handle, mysql_error(&mysql), “Ошибка!”,
       MB_OK | MB_ICONERROR);
     return;
   }

   // Вывод сообщения о том, что все хорошо.
   MessageBox(this->Handle, “Книга добавлена!”, “”, MB_OK | MB_ICONINFORMATION);
   bookText->Clear();
   authorText->Clear();
   BitBtn1->Click(); // Обновим данные

Код кнопки “Изменить”

   // Изменение книг
   // Если соединения нет и книга не выбрана, то выходим

   if (!connected && ID < 1) return;

   AnsiString tmp = “UPDATE t_books SET Title = ‘” + bookText->Text +”‘,\
     FIO = ‘” + authorText->Text + “‘ WHERE ID = ” + IntToStr(ID);

   char query[buf];
   strcpy(query, tmp.c_str());

   if (mysql_query(&mysql, query) > 0)
   {
     MessageBox(this->Handle, mysql_error(&mysql), “Ошибка!”,
       MB_OK | MB_ICONERROR);
   return;
   }

   MessageBox(this->Handle, “Информация о книге обновлена”, “”,
     MB_OK | MB_ICONINFORMATION);

Код кнопки “Удалить”

   // Удаление книг
   // Если соединения нет и нет выделенной книги для удаления, то выходим

   if (!connected && ID < 1) return;

   // Подтверждение удаления
   if (MessageBox(this->Handle, “Удалить?”, “Удаление”,
     MB_YESNO | MB_ICONQUESTION) != 6) return;

   // Формируем запрос на удаление
   AnsiString tmp = “DELETE FROM t_books WHERE ID = ” + IntToStr(ID);
   char query[buf];
   // Приводим его
   strcpy(query, tmp.c_str());

   // Выполняем.
   if (mysql_query(&mysql, query) > 0)
   {
     MessageBox(this->Handle, mysql_error(&mysql), “Ошибка!”,
       MB_OK | MB_ICONERROR);
     return;
   }

   MessageBox(this->Handle, “Книга удалена”, “”, MB_OK | MB_ICONINFORMATION);
   BitBtn1->Click();

Выбор книги из ListBox будет производится по двойному щелчку

   // Выбор книги
   // Если соединения нет, то выходим

   if (!connected) return;

   // Тут надеюсь все понятно. Если нет, смотрите “Обновление списка”
   AnsiString tmp = “SELECT FIO FROM t_books WHERE ID = ”
     + IntToStr(arrIDs[ListBox1->ItemIndex]);

   char query[buf];
   strcpy(query, tmp.c_str());

   if (mysql_query(&mysql, query) > 0)
   {
     MessageBox(this->Handle, mysql_error(&mysql), “Ошибка!”,
     MB_OK | MB_ICONERROR);
     return;
   }

   // Получаем результаты
   res = mysql_store_result(&mysql);
   row = mysql_fetch_row(res);

   // Получаем идентификатор книги
   ID = arrIDs[ListBox1->ItemIndex];

   // Выводим название книги
   bookText->Text = ListBox1->Items->Strings[ListBox1->ItemIndex];
   // Выводим автора
   authorText->Text = row[0];
   mysql_free_result(res);

И, по закрытию программы написать

   // Закрытие программы
   // Если соединены, то разрываем соединение.

   if (connected)
   {
     mysql_close(&mysql);
     if (arrIDs) delete []arrIDs;
   }

все исходники статьи и скрипт sql
Вся статья (сделана под форум). В аттаче.
mysql.txt

Обсудить на форуме – Работа с MySQL в C++

Похожие статьи