Отправляет email-рассылки с помощью сервиса Sendsay

Программирование игр в Linux

  Все выпуски  

Программирование игр в Linux


Информационный Канал Subscribe.Ru

Programming Linux Games

Выпуск 6 (30.03.2004)


Вступительное слово.

Здравствуйте, уважаемые подписчики. Сегодня я расскажу вам о том, как реализовать сериализацию. Сериализация - это запись целого объекта (экземпляр класса или структуры) в файл, с возможностью его восстановления. Другими словами, вы просто сбрасываете дамп объекта (кусок памяти) в файл, а потом восстанавливаете его. Это может использоваться для сохранения информации о текущем ходе игры: имя игрока, количество очков, номер уровня и т.п. Но нет необходимости придумывать свой формат файла для хранения этого добра. Вы просто создаете структуру или класс со всей необходимой информацией и сохраняете его на диск. Очень надеюсь, что эта статья окажется для вас полезной. Возможно существует более лучший способ, но мне он неизвестен. Во время экспериментов ни одной структуры не пострадало.

Искренне ваш,
E$h

Сериализация объектов.

Для начала познакомимся с прототипом объекта, который мы подвергнем сериализации. Это будет структура, но вы можете смело использовать класс - процесс совершенно одинаковый.

typedef struct _serialized
{
 char Name[32];
 int Level;
 int Points;
 int _empty;
} SSerialized;

Как видим, мы сохраняем имя игрока, текущий уровень, количество набранных очков и еще какую-нибудь информацию (поле _empty). Сразу бросается в глаза массив char. Как известно, это не самый лучший выбор для хранения строк. Но в этом и заключается недостаток моего метода сериализации - в сохраняемой структуре не должно быть указателей! Все данные обязательно должны быть статическими переменными. Если вы поместите указатель, например сделаете char* Name, то в файл сохранится указатель на строку, который будет совершенно неправильным после перезапуска программы! Кроме того, необходимо зараннее знать максимальный размер строки (или любого массива). Это, конечно, минус и на текущий момент у меня нет никаких идей.
Наше демонстрационное приложение будет состоять из двух независимых программ: одна будет сохранять объект, а другая потом восстановить его и покажет содержимое на экран. Практически весь смысл сводится к использованию функции mmap - проецирование файлов в памяти. Перечитывая в очередной раз потрясающую книгу "Advanced Linux Programming", главу об отображении файлов в память, я неожиданно для себя сделал открытие, что эту функцию можно и даже нужно использовать для сериализации, а не для замены функций write и read, как советуют авторы. Совершенно естественно, что весь вечер был потрачен на эксперименты.

В сегодняшнем выпуске мы разберем две програмы. Одна из них будет сохранять (сериализовать) нашу структуру в файл на диск, а вторая соответственно читать. Сначала вкратце алгоритм.

  • Создаем экземпляр структуры SSerialized;
  • Открываем (или создаем) файл и специальным образом его подготавливаем;
  • Проецируем открытый файл в памяти при помощи mmap;
  • Закрываем дескриптор открытого файла за ненадобностью;
  • Копируем содержимое структуры в спроецированный файл (по сути - другой участок памяти);
  • Освобождаем файл при помощи munmap и удаляем нашу структуру.
Создать и заполнить экземпляр структуры (или класса) ни у кого проблем не составит, но если для вас это проблема, то тогда призываю задуматься над карьерой художника или менеджера.
SSerialized* my_serialized = new SSerialized;
my_serialized->Level = 255;
И так далее. Теперь нужно открыть файл. Делать это будем низкоуровневыми функциями (по сути системными вызовами).
int fd; // дескриптор файла
int file_lenght = sizeof(SSerialized); // размер файла (сохраняемой структуры)
fd = open("serialize.dat", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
lseek(fd, file_lenght+1, SEEK_SET); // перемещаемся в конец
write(fd, "\0", 1); // делаем запись
lseek(fd, 0, SEEK_SET); // возвращяемся в начало
С этого места подробнее, хотя я не ставлю задачу дать вам почитать мануал по open, read, write и lseek. Размер файла должен совпадать (или по крайней мере быть не меньше) с размером структуры. Далее открываем файл (open) с флагами на чтение/запись (O_RDWR) и создания, если файл не существует (O_CREAT) и с правами на чтение и запись владельца (S_IRUSR|S_IWUSR) - фактически с атрибутом 600. Далее мы проделываем загадочные действия - записываем в конец файла символ "\0". По правде говоря, я не уверен на 100%, но думаю, что так мы ограничиваем отображаемую память. И возвращяем указатель на начало файла.
Далее мы отображаем файл в памяти:
void* file_memory; // указатель на отображенный в памяти файл
file_memory = mmap(NULL, file_lenght, PROT_WRITE, MAP_SHARED, fd, 0(
Предлагаю почитать ман по mmap. Здесь мы указываем, что память будет доступна на запись (PROT_WRITE) и что изменения сразу зафиксируются в файле на диске (MAP_SHARED). Затем закрываем открытый файл - close(fd), т.к. дескриптор нам больше не нужен. И наконец копируем кусок памяти, который занимает структура в кусок памяти, который останется на диске:
memcpy(file_memory, (const void*)my_serialized, file_lenght);
munmap(file_memory, file_lenght);
delete my_serialized;
Вот собственно и все! Структура записана в файл serialize.dat! Теперь во второй программе нужно ее зачитать обратно. Делается все примерно так же, но с небольшим отличием:
  • Открываем файл на чтение;
  • Отображаем его в память и получаем указатель на эту память;
  • Закрываем дескриптор файла;
  • Создаем структуру SSerialized не через new, а путем подсовывания куска памяти из файла;
  • Выводим данные и удаляем память;
Чтобы открыть файл, мы проделываем те же самые операции.
// открываем файл для чтения
fd = open("serialize.dat", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);

// отображаем его содержимое в память и получаем указатель
file_memory = mmap(NULL, file_lenght, PROT_READ, MAP_SHARED, fd, 0);

// освобождаем дескриптор
close(fd);
Обратите внимание, что мы отображаем файл с флагом для чтения PROT_READ! Иначе конструкция не будет работать. Теперь нужно инициализировать структуру куском памяти из файла. Точнее, мы просто создаем указатель на структуру и указываем его на начало памяти из файла, что по сути и является неоформленной структурой. Дико извиняюсь за культуру речи.
// инициализируем структуру данными из файла
SSerialized* my_serialized = (SSerialized*)file_memory;
// выводим на экран данные структуры
std::cout << my_serialized->Level << std::endl;
// и т.д.

// освобождаем память
munmap(file_memory, file_lenght);
// Внимание! Структура my_serialized уже не существует!
Если вам ничего не понятно про указатели и память, то предлагаю почитать учебники по Си и С++. Обратите внимание, что структура умерла когда мы освободили память файла и указатель my_serialized стал указывать, как говориться, пальцем в небо. В принципе в серьезном проекте такое положение вещей нас не должно устраивать и можно опять таки копировать содержимое файла в созданную через new структуру. Хотя, данная структура по сути является временной и мы просто считываем из нее данные и убиваем ее. Заметьте, что использовать указатели в сохраняемой структуре недопустимо. Все. На любые вопросы с удовольствием отвечу на форуме. Скачать исходники для этого выпуска можно по этой ссылке.


Рассылку выпускал E$h (bbroth@plg.lrn.ru); Сайт рассылки: http://plg.lrn.ru; Периодичность: не менее 2-х раз в месяц.
 



http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу


В избранное