Search code examples
c++iodirectxbinaryfilesvertex-shader

c++ reading (.cso) compiled shader object returning \0


I've tried two different methods to read from this cso file. Which is microsofts compiled shader

HRESULT BasicReader::ReadData(_In_z_ wchar_t const* fileName, _Inout_ std::unique_ptr<uint8_t[]>& data, _Out_ size_t* dataSize) {
ScopedHandle hFile(safe_handle(CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)));
    LARGE_INTEGER fileSize = { 0 };
    FILE_STANDARD_INFO fileInfo;
    GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo));
    fileSize = fileInfo.EndOfFile;
    data.reset(new uint8_t[fileSize.LowPart]);
    DWORD bytesRead = 0;
    ReadFile(hFile.get(), data.get(), fileSize.LowPart, &bytesRead, nullptr);
    *dataSize = bytesRead;
}

GetFileInformationByHandleEx returned true and ReadFile returned true

HRESULT BasicReader::ReadData(_In_z_ wchar_t const* fileName, _Inout_ std::unique_ptr<uint8_t[]>& data, _Out_ size_t* dataSize) {
    std::ifstream fstream;
    fstream.open(fileName, std::ifstream::in | std::ifstream::binary);
    if (fstream.fail())
        return false;
    char* val;
    fstream.seekg(0, std::ios::end);
    size_t size = size_t(fstream.tellg());
    val = new char[size];
    fstream.seekg(0, std::ios::beg);
    fstream.read(val, size);
    fstream.close();
    auto f = reinterpret_cast<unsigned char*>(val);
    data.reset(f);
    *dataSize = size;
}

Both of these methods make data = \0 However; when I point to another file in the same directory, it gives me data. What is happening here? Here's the file.

I read the first few bytes of the file and it's this:

0 2 254 255 254 255 124 1 68 66 85 71 40 0 0 0 184 5 0 0 0 0 0 0 1 0 0 0 144 0 0
0 72 0 0 0 148 0 0 0 4 0 0 0 104 5 0 0 212 2 0 0 67 58 92 85 115 101 114 115 92 
106 97 99 111 98 95 48 48 48 92 68 111 99 117 109 101 110 116 115 92 86 105 115 
117 97 108 32 8...

And the working file looks like this:

68 68 83 32 124 0 0 0 7 16 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
32 0 0 0 0 64 0 0 0 0 0 0 0 32 0 0 0 0 0 255 0 0 255 0 0 255 0 0 0 0 0 0 0 0 16
0 0 0 0 0 0 0 0 0 0...

Solution

  • Your code working as expected: char* data array contains file's data. What's going wrong here is that your char* data array is misinterpreted by your visualizers (whatever you use to visualize: debugger visualizer, std::cout, etc). They all try to print null-terminated (c-style) string, but it terminates instantly, as first char is 0. Raw arrays can also be visualized in debuggers as pointers: address and only first data member's value (because it cannot know where array ends). In C# situation is different as arrays are objects there, much like std::vectors, so their size is known.

    Offtopic (sorry for that):

    I would like to comment your second, native C++ BasicReader::ReadData method implementation, as it hurts my C++ feelings ;) You trying to write C code in C++11 style. "There is more than one way to skin a cat", but there are some advices:

    • don't use raw pointers (char*), use STL containers instead (std::vector, std::string)
    • have you really good reason to use std::unique_ptr<uint8_t[]> data + size_t dataSize instad of std::vector<uint8_t>?
    • avoid using raw operator new(), use STL containers, std::make_shared, std::make_unique (if available)
    • seekg()+tellg() file size counting can report wrong size in case of big files

    Doesn't this code looks a little cleaner and more safe:

    std::vector<uint8_t> ReadData(const std::string filename)
    {
        std::vector<uint8_t> data;
    
        std::ifstream fs;
        fs.open(filename, std::ifstream::in | std::ifstream::binary);
        if (fs.good())
        {
            auto size = FileSize(filename);
                // TODO: check here if size is more than size_t
            data.resize(static_cast<size_t>(size));
            fs.seekg(0, std::ios::beg);
            fs.read(reinterpret_cast<char*>(&data[0]), size);
            fs.close();
        }
        return data;
    }
    

    And usage is even more cleaner:

    std::vector<uint8_t> vertexShaderData = ReadData("VertexShader.cso");
    if(vertexShaderData.empty()) {  /* handle it*/ }
    auto wannaKnowSize = vertexShaderData.size();
    

    As a bonus, you got a nice-looking debugger visualization.

    And safe FileSize() implementation. You can use either boost::filesystem, of std::tr2 if your STL had implemented it.

    #include <filesystem>
    namespace filesystem = std::tr2::sys;
    /* or namespace filesystem = boost::filesystem */
    
    uintmax_t FileSize(std::string filename)
    {
        filesystem::path p(filename);
        if (filesystem::exists(p) && filesystem::is_regular_file(p))
            return filesystem::file_size(p);
        return 0;
    }
    

    Hope it helps somehow.