C ++自定义二进制资源文件

时间:2013-07-14 12:38:47

标签: c++ visual-c++ resources resourcebundle

我花了无数个小时搜索有关此类主题的信息。我正在使用C ++中的SDL编写自己的自定义游戏引擎。我正在尝试创建一个自定义二进制文件来管理我的游戏资源。到目前为止,在存储放置在文件中的每个“类型”对象时,我还无法让矢量发挥出色。所以我放弃了使用向量的想法,然后去了数组。我在下面有两个例子,我使用矢量或数组。所以,首先我为该文件创建一个标题。这是结构:

    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[]         

};

这个结构的主要问题是向量不能很好地运行,一个unsigned 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 个答案:

答案 0 :(得分:1)

这是我专注的事情之一,所以你走了。围绕这个有一整套编程,但我遵循的基本规则是:

1)对具有“常量”布局的事物使用FIXED-LENGTH结构。
这些是文件的标志位,字节表示子记录的数量等等。尽可能多地将文件内容放入这些结构中 - 它们非常有效,特别是当与良好的I / O系统结合使用时

您可以使用预处理器宏“#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)创建一个基类,名为“Serializable”的纯接口。他是用于将整个文件对象放入和放出原始内存的高级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;
};

注意:要支持静态“加载”,您需要所有“Serializable”具有额外的静态功能。有几种(非常不同的)支持方式,因为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'类)。如果任何一个片段无法添加,则调用返回“false”,您可以中止。

我强烈建议使用类似“引用对象”的模式来避免内存管理问题。但是,即使你不这样做,你现在也可以为用户提供一个很好的一站式购物方法来从文件中加载数据对象:

GameResourceFile* resource = GameResourceFile::Load("myfile.game");
if(!resource) { // Houston, we have a problem
   return -1;
}

最好的办法是将这类数据的所有低级操作和检索API添加到“GameResourceFile”中。然后任何低级状态机协调用于提交对磁盘和磁盘的更改。这样都局限于1个对象。