Фрилансеров: 3217 Работодателей: 297 Заказов: 277
 
Авторизация Нет аккаунта? Создать
Удаленная работа, работа на дому, фриланс
Выберите язык: ru en
Поиск работы / Что влияет на получение высокооплачиваемой работы?
offline admin / 27.08 (02:24)
Поиск работы / Работа на дому: за и против
offline admin / 31.08 (20:58)
Поиск работы / Удалённая работа.
offline admin / 31.08 (21:05)
Продвижение сайтов / Самое современное искусство - копирайтинг!
offline admin / 31.08 (21:18)
Поиск работы / Заработок в интернет – сказка или реальность?
offline olika / 19.10 (20:25)
Программирование / Структуры с меняющимися размерами данных
offline admin / 21.05 (11:38)
Программирование / Ввод чисел в консольных приложениях
offline admin / 21.05 (11:52)
Программирование / Локализация интерфейса в приложениях Windows
offline admin / 21.05 (12:08)
Базы данных / Перила из нержавейки (495) 772-49-08
offline metallnmru / 27.12 (22:59)
Базы данных / Перила из нержавейки (495) 772-49-08
offline metallnmru / 27.12 (23:06)
Программирование → Структуры с меняющимися размерами данных
Размещена: 21.05.2009 11:38

Автор: Виталий Рычков (WinMain) 

В разных форумах по программированию на C++ регулярно задаются вопросы: как записать структуру с текстовыми полями в бинарный файл, а затем прочитать её оттуда? Типичной ошибкой для начинающих программистов является попытка записать напрямую в файл структуру, содержащую указатели на текстовые строки. В итоге в файл записываются лишь значения указателей, но не сами строки.
Возьмём в качестве примера небольшую структуру, в которой используются классы CString для текстовых данных:

typedef struct _CUSTOM_DATA

{

      CString strName;        // Имя

      CString strFamily;      // Фамилия

      CString strAddress;     // Адрес

      CString strCompany;     // Компания

      CString strMailBox;     // Почта

} CUSTOM_DATA, *LPCUSTOM_DATA;

В таком виде структура удобна для обмена данными внутри приложения, например: ввод информации через графический интерфейс, запись в таблицу СУБД, вывод в отчётный документ или экспорт в текстовый файл, и т.д. Но для записи в бинарный файл такая структура неприемлема, поскольку класс CString содержит лишь указатель на символьный массив, а сам текст находится вне класса. Тогда каким же образом текстовые данные можно записать из данной структуры в бинарный файл, а затем прочитать их из файла обратно в структуру? Для этого используются так называемые структуры с меняющимся размером данных, у которых фиксированный размер имеет лишь начало структуры (заголовок), а за ним располагаются сами данные в определённой последовательности.
Выглядит структура так:

typedef struct _CUSTOM_DATA_PERSIST

{

      DWORD dwDataSize;

      WORD wOffsetName;

      WORD wOffsetFamily;

      WORD wOffsetAddress;

      WORD wOffsetCompany;

      WORD wOffsetMailBox;

      TCHAR tcStrData[1];

} CUSTOM_DATA_PERSIST, *LPCUSTOM_DATA_PERSIST;


Суть в том, что для инициализации такой структуры нужно сначала определить размер всех данных, которые будут записываться в файл. Т.е. необходимо узнать длину каждой строки вместе с нулевым символом на конце, сложить их вместе, прибавить к этому значению размер заголовка структуры, а затем выделить в памяти массив соответствующего размера, в котором разместятся эти данные. Текстовые строки в массиве располагаются последовательно одна за другой, разделённые нулевыми символами, а в заголовке структуры для каждой строки указываются смещения от начала первой строки. Ещё необходимо в заголовке структуры указать размер всего массива данных, чтобы потом при чтении данных из файла, можно было заранее выделить для них массив нужного размера. В таком виде эта структура записывается в файл. При чтении структуры из файла, чтобы получить указатель на нужную строку, надо прибавить к значению указателя на первую строку величину смещения для нужной строки.
Как же тогда пользоваться такой структурой для обмена данными в приложении, если процесс её инициализации и обращения к ней выглядит таким сложным и трудоёмким? А не нужно её для этого использовать. Такие структуры следует применять лишь для хранения данных в файле, для передачи данных по сети или для локального обмена данными между приложениями. А для передачи данных внутри приложения следует пользоваться более простым и удобным вариантом первой структуры. Т.е. фактически для одних и тех же данных нужно определить два типа структур. При записи данных в файл и при чтении их из файла необходимо реализовать процедуру передачи данных из одной структуры в другую.
Вот как это выглядит в коде на C++:

#include "stdafx.h"

#include                               

typedef struct _CUSTOM_DATA

{

      CString strName;        // Имя

      CString strFamily;      // Фамилия

      CString strAddress;     // Адрес

      CString strCompany;     // Компания

      CString strMailBox;     // Почта

} CUSTOM_DATA, *LPCUSTOM_DATA;

                                  

typedef struct _CUSTOM_DATA_PERSIST

{

      DWORD dwDataSize;

      WORD wOffsetName;

      WORD wOffsetFamily;

      WORD wOffsetAddress;

      WORD wOffsetCompany;

      WORD wOffsetMailBox;

      TCHAR tcStrData[1];

} CUSTOM_DATA_PERSIST, *LPCUSTOM_DATA_PERSIST;

                                  

int SaveDataToFile(LPCUSTOM_DATA data, LPCTSTR szFile)

{

      ATLASSERT (data != NULL);

      CUSTOM_DATA_PERSIST pers;

      pers.wOffsetName = 0;

      pers.wOffsetFamily = pers.wOffsetName + ::lstrlen(data->strName) + 1;

      pers.wOffsetAddress = pers.wOffsetFamily + ::lstrlen(data->strFamily) + 1;

      pers.wOffsetCompany = pers.wOffsetAddress + ::lstrlen(data->strAddress) + 1;

      pers.wOffsetMailBox = pers.wOffsetCompany + ::lstrlen(data->strCompany) + 1;

      DWORD dwStrLen = pers.wOffsetMailBox + ::lstrlen(data->strMailBox);

      pers.dwDataSize = dwStrLen * sizeof(TCHAR) + sizeof(pers);

      // Создание массива и заполнение его данными...

      LPCUSTOM_DATA_PERSIST lpDataPers =

            (LPCUSTOM_DATA_PERSIST)(new BYTE[pers.dwDataSize]);

      *lpDataPers = pers;

      ::lstrcpy(lpDataPers->tcStrData + pers.wOffsetName, data->strName);

      ::lstrcpy(lpDataPers->tcStrData + pers.wOffsetFamily, data->strFamily);

      ::lstrcpy(lpDataPers->tcStrData + pers.wOffsetAddress, data->strAddress);

      ::lstrcpy(lpDataPers->tcStrData + pers.wOffsetCompany, data->strCompany);

      ::lstrcpy(lpDataPers->tcStrData + pers.wOffsetMailBox, data->strMailBox);

      // Запись массива в файл...

      int nResult(0);

      FILE *file(NULL);

      _tfopen_s(&file, szFile, _T("wb"));

      if (file != NULL)

      {

            fwrite(lpDataPers, 1, pers.dwDataSize, file);

            fclose(file);

      } else

      {

            nResult = -1;

      }

      delete lpDataPers;

      //

      return nResult;

}
                                

int LoadDataFromFile(LPCUSTOM_DATA data, LPCTSTR szFile)

{

      ATLASSERT (data != NULL);

      CUSTOM_DATA_PERSIST pers;

      //

      int nResult(0);

      FILE *file(NULL);

      _tfopen_s(&file, szFile, _T("rb"));

      if (file != NULL)

      {

            // Считываем заголовок структуры и создаём массив...

            fread(&pers, sizeof(pers), 1, file);

            LPCUSTOM_DATA_PERSIST lpDataPers =

                  (LPCUSTOM_DATA_PERSIST)(new BYTE[pers.dwDataSize]);

            *lpDataPers = pers;

            // Потом дочитываем остальные данные...

            fread((LPBYTE)lpDataPers + sizeof(pers), 1,

                        pers.dwDataSize - sizeof(pers), file);

            fclose(file);

            // Передача данных в другую структуру...

            data->strName = lpDataPers->tcStrData + lpDataPers->wOffsetName;

            data->strFamily = lpDataPers->tcStrData + lpDataPers->wOffsetFamily;

            data->strAddress = lpDataPers->tcStrData + lpDataPers->wOffsetAddress;

            data->strCompany = lpDataPers->tcStrData + lpDataPers->wOffsetCompany;

            data->strMailBox = lpDataPers->tcStrData + lpDataPers->wOffsetMailBox;

            //

            delete lpDataPers;

      } else

      {

            nResult = -1;

      }

      return nResult;

}

                                  

// Для вывода текста на консоль...

BOOL PrintText(LPCTSTR szText);

                                  

// Применение функций записи в файл и чтения из файла,

// отображение полученных данных...

int _tmain(int argc, _TCHAR* argv[])

{      

      // Инициализация структуры...

      CUSTOM_DATA data =

      {

            _T("Виталий Сергеевич"),

            _T("Рычков"),

            _T("129329, г. Москва, ул Кольская, д. 11"),

            _T("WinMain &Co Ltd"),

            _T("rychkov@inbox.ru")

      };

                            

      LPCTSTR szFile = _T("WinMain.bin");

      

      // Запись данных в файл...

      SaveDataToFile(&data, szFile);

                                  

      // Чтение данных из файла...

      CUSTOM_DATA dataNew;

      LoadDataFromFile(&dataNew, szFile);

                                  

      // Вывод данных на консоль...

      PrintText(dataNew.strFamily);

      _puttc('\n', stdout);

      PrintText(dataNew.strName);

      _puttc('\n', stdout);

      PrintText(dataNew.strAddress);

      _puttc('\n', stdout);

      PrintText(dataNew.strCompany);

      _puttc('\n', stdout);

      PrintText(dataNew.strMailBox);

      _puttc('\n', stdout);

                                  

      // Ожидание...

      _gettc(stdin);

                                  

      return 0;

}

                                  

BOOL PrintText(LPCTSTR szText)

{

    static HANDLE hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE);

    // Вывод текста на консоль...

    DWORD dw(0);

    return ::WriteConsole(hConsole, szText, ::lstrlen(szText), &dw, NULL);

}

Представленный пример выполнен в среде Visual C++ 2005. Для его повторения нужно с помощью «визарда» создать проект приложения Win32 Console, включив опцию поддержки ATL.

Автор: offlineadmin
Комментариев нет
Обсуждение:
Оставлять комментарии могут только зарегистрированные пользователи