Я провел бесчисленные часы в поисках информации на тему, подобную этой. Я пишу свой собственный игровой движок для развлечения с использованием SDL на C ++. Я пытаюсь создать собственный двоичный файл, который будет управлять моими игровыми ресурсами. До сих пор я не смог заставить векторы играть хорошо, когда дело доходит до хранения каждого «типа» объекта, который я помещаю в файл. Поэтому я отказался от идеи использования векторов и пошел к массивам. У меня есть оба примера ниже, где я использую как вектор, так и массив. Итак, сначала я создаю заголовок для файла. Вот структура:
struct Header
{
const char* name; // Name of Header file
float version; // Resource version number
int numberOfObjects;
int headerSize; // The size of the header
};
Затем после создания заголовка у меня есть другая структура, которая определяет, как объект хранится в памяти. Вот:
struct ObjectData{
int id;
int size;
const char* name;
// std::vector<char> data; // Does not work very well
// unsigned char* data; // Also did not
// Also does not work, because I do not know the size yet until I have the data.
// char data[]
};
Основная проблема с этой структурой заключается в том, что вектор не воспроизводится хорошо, беззнаковый указатель char продолжал вызывать у меня проблемы, а массив данных char (для шестнадцатеричного хранения) не работал, потому что мой компилятор не любит переменные массивы.
Последняя структура — это моя структура файлов ресурсов.
struct ResourceFile
{
Header header;
int objectCount;
// Again, vectors giving me issues because of how they are constructed internally
// std::vector<ObjectData> objectList;
// Below does not work because, again, no variable data types;
// ObjectData objects[header.numberOfObjects]};
Моя цель — записать одну структуру в двоичный файл. Вот так:
Header header;
header.name = "Resources.bin";
header.version = 1.0f;
header.headerSize = sizeof(header);
//vector<char> Object1 = ByteReader::LoadFile("D:\\TEST_FOLDER\\test.obj");
//vector<char> Object2 = ByteReader::LoadFile("D:\\TEST_FOLDER\\test.obj");
ObjectData cube;
cube.id = 0;
cube.name = "Evil Cubie";
cube.data = ByteReader::LoadFile("D:\\TEST_FOLDER\\test.obj");
cube.size = sizeof(cube.id) + sizeof(cube.name) + cube.data.size();
ofstream resourceFile("D:\\TEST_FOLDER\\Resources.bin", ios::out|ios::app|ios::binary);
resourceFile << header.name << header.version << header.headerSize;;
resourceFile << cube.id << cube.name << cube.size;
for each (char ch in cube.data)
{
resourceFile << ch;
}resourceFile.close();
/*
ObjectData cube2;
cube.id = 1;
cube.name = "Ugle Cubie";
for each (char ch in Object1)
{
cube.object.push_back(ch);
}
*///resourceFile.data.push_back(cube);
//resourceFile.data.push_back(cube2);
//resourceFile.header.numberOfObjects = resourceFile.data.size();//FILE* dat = fopen(filename, "wb");
//fwrite(&resourceFile, sizeof(resourceFile), 1, dat); // <-- write to resource file
//fclose(dat);
Как вы заметили выше, я попробовал два разных способа. Первый способ, которым я попытался это использовать старый добрый fwrite. Второй способ — даже не записывать его в двоичном формате, хотя я велел компьютеру делать это с помощью флагов, принятых ofstream.
Моя цель состояла в том, чтобы заставить код работать так:
ResourceFile resourceFile;
resourceFile.header.name = "Resources.bin";
resourceFile.header.version = 1;
resrouceFile.header.numberOfObjects = 2;
resourceFile.header.headerSize = sizeof(resourceFile.header);
ObjectData cube;
ObjectData cube2;resourceFile.data.push_back(cube);
resourceFile.data.push_back(cube2);
resourceFile.header.numberOfObjects = resourceFile.data.size();FILE* dat = fopen(filename, "wb");
fwrite(&resourceFile, sizeof(resourceFile), 1, dat); // <-- write to resource file
fclose(dat);
До сих пор нет сигары. У кого-нибудь есть какие-нибудь указатели (не каламбур) или правильный пример менеджера ресурсов?
Это одна из вещей, на которой я специализируюсь, так что вот, пожалуйста. Есть целая школа программирования вокруг этого, но основные правила, которые я соблюдаю:
1) Используйте структуры FIXED-LENGTH для вещей с «постоянной» компоновкой.
Это такие вещи, как биты флага файла, байты, указывающие количество подзаписей и т. Д. Поместите в эти структуры столько содержимого файла, сколько сможете — они очень эффективны, особенно в сочетании с хорошей системой ввода-вывода. ,
Вы делаете это, используя макрос препроцессора «#pragma pack (1)», чтобы выровнять структуру по границам байтов:
#ifdef WINDOWS
#pragma pack(push)
#endif
#pragma pack(1)
struct FixedSizeHeader {
uint32 FLAG_BYTES[1]; // All Members are pointers for a reason
char NAME[20];
};
#ifdef WINDOWS
#pragma pack(pop)
#endif
#ifdef LINUX
#pragma pack()
#endif
2) Создайте базовый класс, чистый интерфейс с именем типа «Сериализуемый». Он — ваш высокоуровневый API для размещения целых файловых объектов в исходной памяти и из нее.
class Serializable { // Yes, the name comes from Java. The idea, however, predates it
public:
// Choose your buffer type- char[], std::string, custom
virtual bool WriteToBinary(char* buffer) const = 0;
};
ПРИМЕЧАНИЕ. Для поддержки статической «нагрузки» вам понадобятся все ваши «сериализуемые», чтобы иметь дополнительную статическую функцию. Есть несколько (очень разных) способов поддержки этого, ни один из которых не будет реализован одним языком, поскольку в C ++ нет «виртуальной статики».
3) Создайте свои агрегатные классы для управления каждым типом файлов. Они должны иметь то же имя, что и тип файла. В зависимости от структуры файла каждый из них, в свою очередь, может содержать больше классов-агрегаторов, прежде чем переходить к фиксированным структурам.
Вот пример:
class GameResourceFile : public Serializable
{
private:
// Operator= and the copy ctor should point to the same data for files,
// since that is what you get with FILE*
protected:
// Actual member variables- allows specialized (derived) file types direct access
FixedSizeHeader* hdr; // You don't have to use pointers here
ContentManager* innards; // Another aggregator- implements "Serializable"
GameResourceFile(FixedSizeHeader* hdr, ContentManager* innards)
: hdr(hdr), innards(innards) {}
virtual ~GameResourceFile() { delete hdr; delete innards; }
public:
virtual bool WriteToBinary(char* outBuffer) const
{
// For fixed portions, use this
memcpy(outBuffer, hdr, sizeof(FixedSizeHeader)); // This is why we 'pack'
outBuffer += sizeof(FixedSizeHeader); // Improve safety...
return innards->WriteToBinary(outBuffer);
}
// C++ doesn't enforce this, but you can via convention
static GameResourceFile* Load(const char* filename)
{
// Load file into a buffer- You'll want your own code here
// Now that's done, we have a buffer
char* srcContents;
FixedSizeHeader* hdr = new FixedSizeHeader();
memcpy(hdr, srcContents, sizeof(FixedSizeHeader));
srcContents += sizeof(FixedSizeHeader);
ContentManager* innards = ContentManager::Load( srcContents); // NOT the file
if(!innards) {
return 0;
}
return new GameResourceFile(hdr, innards);
}
};
Обратите внимание, как это работает — каждый кусок отвечает за сериализацию себя в буфер, пока мы не доберемся до «примитивных» структур, которые мы можем добавить с помощью memcpy () (вы можете сделать ВСЕ компоненты компонентами «Serializable»). Если какой-либо кусок не может быть добавлен, вызов возвращает «ложь», и вы можете прервать.
Я настоятельно рекомендую использовать шаблон типа «объект ссылки», чтобы избежать проблем с управлением памятью. Однако, даже если вы этого не сделаете, вы теперь предоставляете пользователям удобный универсальный способ покупки для загрузки объектов данных из файлов:
GameResourceFile* resource = GameResourceFile::Load("myfile.game");
if(!resource) { // Houston, we have a problem
return -1;
}
Лучше всего добавить в GameResourceFile все низкоуровневые API-интерфейсы манипулирования и поиска для такого рода данных. Тогда любой низкоуровневый конечный автомат координирует изменения на диске & такое все локализовано на 1 объект.
Других решений пока нет …