Writing Data to Files

Learn to write and append data to files using SDL2's I/O functions.
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Posted

In this lesson, we’ll introduce how to write data from our program to external sources. We’ll focus on writing to files for now, but the techniques we cover lay the foundations for working with other data streams, such as network traffic.

As we might expect, SDL’s mechanism for writing data shares much in common with their API for reading data. We’ll rely on SDL_RWops objects, and the functions we use will be familiar to what we learned in the previous lesson.

Like before, we’ll start with a basic main function that initializes SDL, and calls Write() and Read() functions within an included File namespace.

// main.cpp
#include <SDL.h>
#include "File.h"

int main(int argc, char** argv) {
  SDL_Init(0);
  File::Write("output.txt");
  File::Read("output.txt");
  return 0;
}

Our Read() function logs out the file’s contents, using techniques we covered in the previous lesson. In this lesson, we’ll work on the Write() function, which is currently empty:

// File.h
#pragma once
#include <iostream>
#include <SDL.h>

namespace File {
  void Read(const std::string& Path) {
    char* Content{static_cast<char*>(
      SDL_LoadFile(Path.c_str(), nullptr)
    )};
    
    if (Content) {
      std::cout << "Content: " << Content;
    } else {
      std::cout << "Error loading file: "
        << SDL_GetError();
    }
    
    SDL_free(Content);
  }
  
  void Write(const std::string& Path) {
    // ...
  }

}

Running our program, we should see an error output from our Read() function, as it’s trying to read a file that we haven’t created yet:

Error loading file: Parameter 'src' is invalid

Creating a File Using SDL_RWFromFile()

We introduced the SDL_RWFromFile() function in the previous lesson. It returns an SDL_RWops object, which is SDL’s standard interface for interacting with data streams, such as files.

In the previous lesson, we passed the file path and the "rb" open mode to read the file. We’ll use the same technique here, except we’ll pass "wb" as the open mode, as we want to write to the file. We cover open modes in more detail later in this lesson.

Let’s update our File::Write() function to create an SDL_RWops handle for outputting content. Similar to the previous lesson, we’ll also add an error check, and we’ll close the file using SDL_RWclose() when we’re done:

// File.h
// ...

namespace File {
// ...
void Write(const std::string& Path) {
  SDL_RWops* Handle{
    SDL_RWFromFile(Path.c_str(), "wb")};

  if (!Handle) {
    std::cout << "Error opening file: "
      << SDL_GetError();
  }

  // Use file...

  SDL_RWclose(Handle);
}
}

The "wb" open mode will additionally cause SDL to create a file if it doesn’t exist. Therefore, running our program, after these changes we should now see our Read() function can successfully open the file, although it has no content yet:

Content:

SDL_RWwrite()

The SDL_RWwrite() function is one of the main ways we output a collection of objects or values to a file. Let’s update our File::Write() function to use it to output the C-style string "Hello World".

We introduced SDL_RWread() in the previous lesson, and the arguments to SDL_RWwrite() follow a similar pattern. It accepts 4 arguments:

  1. The SDL_RWops handle to use
  2. A pointer to the memory address where the content we want to write begins. Remember, a C-style string is simply a location in memory containing a contiguous sequence of char values, represented by a pointer to the first character - a char*.
  3. The size of each object in bytes. In this example, we’re writing char values, and a char is 1 byte. We could simply pass 1 as the argument, but we’ll use sizeof(char) as it clarifies what the 1 represents.
  4. The quantity of objects to write. We can use the strlen() function to determine how many characters are in a C-style string

Let’s update our File::Write() function to make use of this:

// File.h
// ...

namespace File {
// ...
void Write(const std::string& Path) {
  SDL_RWops* Handle{
    SDL_RWFromFile(Path.c_str(), "wb")};

  if (!Handle) {
    std::cout << "Error opening file: "
      << SDL_GetError();
  }

  const char* Content{"Hello World"};
  SDL_RWwrite(Handle, Content,
    sizeof(char), strlen(Content));

  SDL_RWclose(Handle);
}
}
Content: Hello World

SDL_RWFromFile() returns an integer, representing how many objects it wrote:

// File.h
// ...

namespace File {
// ...
void Write(const std::string& Path) {
  SDL_RWops* Handle{
    SDL_RWFromFile(Path.c_str(), "wb")};

  if (!Handle) {
    std::cout << "Error opening file: "
      << SDL_GetError();
  }

  const char* Content{"Hello World"};
  
  size_t ValuesWritten{SDL_RWwrite(
    Handle, Content,
    sizeof(char), strlen(Content)
  )};
  
  std::cout << ValuesWritten
    << " values written\n";

  SDL_RWclose(Handle);
}
}
11 values written
Content: Hello World

We can add some simple error checking by comparing this return value to what we expected. As usual, we can call SDL_GetError() for information on errors detected by SDL:

// File.h
// ...

namespace File {
// ...
void Write(const std::string& Path) {
  SDL_RWops* Handle{
    SDL_RWFromFile(Path.c_str(), "wb")};

  if (!Handle) {
    std::cout << "Error opening file: "
      << SDL_GetError();
  }

  const char* Content{"Hello World"};
  size_t ContentLength{strlen(Content)};
  size_t ValuesWritten{SDL_RWwrite(
    Handle, Content, sizeof(char),
    ContentLength
  )};

  std::cout << ValuesWritten
    << " values written\n";

  if (ValuesWritten < ContentLength) {
    std::cout << "Expected " << ContentLength
      << " - Error: " << SDL_GetError() << '\n';
  }

  SDL_RWclose(Handle);
}
}

File Open Modes

In the previous lesson, we passed the rb string, telling SDL (and ultimately the underlying platform) that we wanted to read from the file in binary mode. In this case, we’re using wb , indicating we want to create a new file for writing in binary mode.

If a file with the same name already exists, it will be entirely replaced when using wb. We’ll cover more open modes, including the ability to edit or add to an existing file later in this chapter.

Binary Mode vs Text Mode

Platforms typically offer two ways to read or write files. These are commonly called binary mode and text mode. Binary mode reads and writes data in the exact format provided. This is intended when the data is used to communicate between programs (or within the same program), where a consistent and predictable format is useful.

On the other hand, text mode performs some platform-specific transformations to format the file in a way that conforms to that platform’s conventions.

For example, we use \n to represent new lines in this course, but some platforms use a two-byte sequence: \r\n. Text mode aims to handle those transformations. However, even if our program is writing data that is intended to be read by humans, that data is usually going to be read in some program that handles formatting, so binary mode is typically preferred even in that scenario.

As such, text mode is rarely useful, but can be used by removing the b character from open modes if needed:

// Open a file for reading in text mode
SDL_RWFromFile("input.txt", "r")}

// Open a file for writing in text mode
SDL_RWFromFile("output.txt", "w")}

Adding to Existing Files

As we’ve seen, opening a file in "write" mode (such as "wb") ensures we get a new file every time we create our handle. If a file with that name already exists, it will be replaced.

In contrast, we can open a file in an "append" mode, such as "ab":

SDL_RWFromFile("some-file.txt", "ab")

This will also create the file if it doesn’t exist yet. However, if it does exist, write operations will be done at the end of the existing file, preventing us from losing any of the existing data.

This is primarily useful for logging scenarios, where we want to keep track of the actions our program performed:

// main.cpp
#include <SDL.h>
#include "File.h"

int main(int argc, char** argv) {
  SDL_Init(0);
  File::Write("logs.txt", "One");
  File::Write("logs.txt", "Two");
  File::Write("logs.txt", "Three");
  File::Read("logs.txt");
  return 0;
}
// File.h
// ...

namespace File {
// ...
void Write(
  const std::string& Path,
  const char* Content
) {
  SDL_RWops* Handle{
    SDL_RWFromFile(Path.c_str(), "ab")};

  if (!Handle) {
    std::cout << "Error opening file: "
      << SDL_GetError();
  }

  SDL_RWwrite(
    Handle, Content,
    sizeof(char), strlen(Content)
  );

  SDL_RWclose(Handle);
}
}

Three file handles were opened and closed during the execution of the program, and each write operation was appended to the previous output:

Content: OneTwoThree

If we run our program again, the logging from the second execution will add more content to the file, without replacing the output of the previous execution:

Content: OneTwoThreeOneTwoThree

Summary

In this lesson, we explored writing data to files using SDL2 in C++. We learned how to:

  • Create and open files using SDL_RWFromFile()
  • Write data to files with SDL_RWwrite()
  • Understand different file open modes, including binary and text modes
  • Append data to existing files
  • Handle potential errors during file operations

These skills form the foundation for more advanced file I/O operations in SDL2-based applications, which we’ll build on through the rest of this chapter.

Was this lesson useful?

Next Lesson

Maths and Geometry Primer

Learn the basics of maths and geometry, so we can represent concepts like positions and distances between objects in our worlds.
3D art showing a teacher in a classroom
Ryan McCombe
Ryan McCombe
Posted
Lesson Contents

Writing Data to Files

Learn to write and append data to files using SDL2's I/O functions.

sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Reading and Writing (RWops)

    50.
    Writing Data to Files

    Learn to write and append data to files using SDL2's I/O functions.


  • 51.GPUs and Rasterization
  • 52.SDL Renderers
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 53 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Maths and Geometry Primer

Learn the basics of maths and geometry, so we can represent concepts like positions and distances between objects in our worlds.
3D art showing a teacher in a classroom
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved