BaseFile Manual

About

BaseFile is a class to create arbitrary files and/or read and write data from and to files. In addition to functions reading and writing Cinema 4D data types, there are functions to do low-level byte accesses and to retrieve basic information on files.

Internally a BaseFile object keeps track of a read/write pointer, basically the position of the file, where data is read from, by the next read access or written to, by the next write access. The initial position of this read/write pointer depends on the mode used to open the file.

In general there are two fundamentally different ways to work with BaseFile:

  • Using Cinema 4D data types and accompanying read/write functions, see Read and Write below
  • Using low-level byte access, see Byte Access below

Throughout this page the code snippets use a helper function PrintFileError().

This looks like so:

// Small helper function used in BaseFile code snippets below.
// Depending on verbose parameter a file error (if any) will also be decoded to a human readable string.
// Can also be used, when there's no HyperFile (e.g. HyperFile allocation failed), then only the filename will be used.
// It returns true, if there was no error, otherwise false.
// In this way it can be used in snippets to directly return from the calling function.
static Bool PrintFileError(const Filename& fn, const BaseFile* const file, const String& errText, Bool verbose = false)
{
if (!file)
{
GePrint("Error: " + errText + ": " + fn.GetString());
return false;
}
const FILEERROR err = file->GetError();
if (err != FILEERROR_NONE)
GePrint("Error (" + String::IntToString(err) + "): " + errText + ": " + fn.GetString());
else
GePrint("No error: " + fn.GetString());
if (verbose)
GePrint(" " + String::IntToString(err) + ": " + FileErrorToString(file)); // FileErrorToString() is a custom function.
return (err == FILEERROR_NONE);
}
Note
To delete a file see GeFKill() and the accompanying File Functions Manual.

Allocation/Deallocation

BaseFile objects are created with the usual tools, see Entity Creation and Destruction Manual.

Opening and Closing

After allocating a BaseFile object, BaseFile::Open() needs to be called to bind it to a file in the filesystem (either an existing or a new one).

  • BaseFile::Open(): Opens a file, several flags influence the mode, initial position of read/write pointer, error reporting behavior and certain system specific options.
    • Parameter "mode":
      • FILEOPEN_READ: The file is opened for reading, only. Read/write pointer is set to start of file. This is the default mode, if none gets specified.
      • FILEOPEN_WRITE: The file is opened for writing, only. Read/write pointer is set to start of file. Any existing file will be overwritten.
      • FILEOPEN_READWRITE: The file is opened for read and write access. Read/write pointer is set to start of file.
      • FILEOPEN_APPEND: The file is opened for writing, only. Read/write pointer is set to end of file.
    • Parameter "error_dialog":
      • FILEDIALOG_NONE: No dialog will be shown on error. To be used if working with files in a context, where no dialogs are allowed, or to implement a custom error notification for users.
      • FILEDIALOG_ANY: A dialog will be shown on every file error.
      • FILEDIALOG_IGNOREOPEN: A dialog will be shown on every file error, except if the file does not exist. This is the default option.
    • Parameter (byte-)"order":
      • The byte order comes into play, if for example numeric values wider than a byte get read or stored (e.g. ReadInt32() and WriteInt32()). Usually you don't have to care much about this. When dealing with your own files, just make sure to use the same byte order on writing and reading. When handling arbitrary 3rd party file types, the byte order usually gets specified in the format documentation. The byte order can also be changed after opening the file using SetOrder().
      • BYTEORDER_MOTOROLA: Big endian byte order.
      • BYTEORDER_INTEL: Little endian byte order.
    • Parameter (file-)"type":
      • This parameter is only relevant for OSX and defines the type of file created.
    • Parameter (file-)"creator":
      • This parameter is only relevant for OSX and should be left at its default in most cases.
  • BaseFile::Close(): Closes a file. In most cases this is not needed, as the file will be automatically closed, when the BaseFile object gets destroyed.

Read and Write

Most commonly the following functions are used to store data in a "BaseContainer"-like fashion, where one does not need to worry about the size of datatypes or positioning the read/write pointer.

A Typical Write Access

  • Allocate a BaseFile object with BaseFile::Alloc() (or of course AutoAlloc()).
  • Open a file using BaseFile::Open(), either creating a new one or opening an existing one.
  • Simply write data into the file, see below. Cinema 4D will take care of the read/write pointer automatically.
  • Close the file using BaseFile::Close(). This step is actually optional, as the file will also be closed when the BaseFile object gets destroyed.
Note
The order of write accesses will determine the order of read accesses (the order will need to be the same).
// This example demonstrates creating a new file and writing some typed data into it.
// Let user select a target directory.
if (!fn.FileSelect(FILESELECTTYPE_ANYTHING, FILESELECT_DIRECTORY, "Select a directory..."))
return false;
fn += Filename("myfile.dat");
if (!file)
return PrintFileError(fn, file, "Failed to allocate BaseFile for");
// Create the file with the purpose of writing into it.
// BEWARE: FILEOPEN_WRITE will create a new file, any existing file will be overwritten.
return PrintFileError(fn, file, "Failed to create file");
// Write some data into the new file.
// Note: The order of data being written needs to be the same, when reading.
if (!file->WriteInt32(12345678))
return PrintFileError(fn, file, "Failed to write an Int32 into file");
if (!file->WriteString("Hello World!"))
return PrintFileError(fn, file, "Failed to write a String into file");
// Finally close the file.
// Actually not needed, as the file will be closed on destruction of the BaseFile object.
file->Close();
// This example demonstrates appending typed data to a file.
// Note: The file created via the "BaseFile Create" example is expected in this example.
// BEWARE: This code will append data to the file, so be careful, when choosing one.
// Let user select a file.
if (!fn.FileSelect(FILESELECTTYPE_ANYTHING, FILESELECT_LOAD, "Select the file from the BaseFile Create command..."))
return false;
if (!file)
return PrintFileError(fn, file, "Failed to allocate BaseFile for");
// Open a file with the purpose of appending some data to it.
return PrintFileError(fn, file, "Failed to open the file for appending");
// Write some more data into the file.
// Note: The order of data being written needs to be the same, when reading.
if (!file->WriteVector64(Vector(10.0, 20.0, 30.0)))
return PrintFileError(fn, file, "Failed to write a vector into file");

A Typical Read Access

  • Allocate a BaseFile object with BaseFile::Alloc() (or of course AutoAlloc()).
  • Open an existing file using BaseFile::Open().
  • Simply read data from the file in the same order it got written before, see below. Cinema 4D will take care of the read/write pointer automatically.
  • Close the file using BaseFile::Close(). This step is actually optional, as the file will also be closed when the BaseFile object gets destroyed.

All of the following read/write functions automatically take care of the read/write pointer (i.e. advancing it by the correct amount of bytes depending on the access type) and are able to detect access with wrong data type. Internally this is achieved by not only writing the actual data to the file, but also an additional value header preceding each data, specifying the type and also (where needed) the amount of data.

Note
The order of read accesses has to match the order of write accesses.
These functions can not be used to read data from an arbitrary file, but only from files created by Cinema 4D, when data got written by the respective write functions.
It is not recommended to mix these functions with the byte access functions (see Byte Access).
// This example demonstrates reading typed data from a file.
// Note: The file created via the "BaseFile Create" or "BaseFile Append" examples is expected in this example.
// Let user select a file.
if (!fn.FileSelect(FILESELECTTYPE_ANYTHING, FILESELECT_LOAD, "Select the file from the BaseFile Create or Append command..."))
return false;
if (!file)
return PrintFileError(fn, file, "Failed to allocate BaseFile for");
// Open a file with the purpose of reading data from it.
return PrintFileError(fn, file, "Failed to open the file for reading");
// Read data from file.
// Note: The order needs to be the same as on write.
Int32 value;
if (!file->ReadInt32(&value))
return PrintFileError(fn, file, "Failed to read an Int32 from file");
if (value != 12345678)
return PrintFileError(fn, file, "This is not the expected file");
GePrint("Int32 read from file: " + String::IntToString(value));
String myText;
if (!file->ReadString(&myText))
return PrintFileError(fn, file, "Failed to read from string into file");
GePrint("String read from file: " + myText);
Vector vec;
if (!file->ReadVector64(&vec))
return PrintFileError(fn, file, "Failed to read Vector from file (maybe the append example hasn't been executed)");
GePrint("Vector read from file: " + String::VectorToString(vec));

Char

See also Primitive Data Types Manual on Char.

String

See also String Manual.

Filename

See also Filename Manual.

Bool

See also Primitive Data Types Manual on Bool.

Integer

See also Primitive Data Types Manual on Integer.

Float

See also Primitive Data Types Manual on Float.

Vector

See also Vector Manual.

Matrix

See also Matrix Manual.

Byte Access

This is used to read or write files of arbitrary type/structure. For example, if a pure ASCII text file is supposed to be created, the above functions won't work, because no matter what is done (e.g. WriteString(), WriteChar()), there will always be the value headers prepended to the actual data. In such cases the byte access functions may be helpful.

A typical read or write looks like this:

Warning
The byte sequences read or written are not platform independent. For example, when reading the four bytes of an Int32, one will need to know, if the most or the least significant byte is first.
// This example demonstrates reading raw bytes from a file.
static const Char testBytes[] = { 'Q', 'C', '4', 'D', 'C', '4', 'D' }; // Typical first bytes of a Cinema 4D scene file (.c4d), just used as an example here. Nobody should rely on this!
const Int32 numBytes = sizeof(testBytes);
// Let user select a file
if (!fn.FileSelect(FILESELECTTYPE_ANYTHING, FILESELECT_LOAD, "Select a C4D scene file..."))
return false;
if (!file)
return PrintFileError(fn, file, "Failed to allocate BaseFile for");
// Open a file with the purpose of reading data from it.
return PrintFileError(fn, file, "Failed to open the file for reading");
// Read data from file.
Char readBytes[numBytes];
const Bool justTry = false; // Throw an error, if the file is smaller than the number of requested bytes.
if (!file->ReadBytes(&readBytes, sizeof(readBytes), justTry))
return PrintFileError(fn, file, "Failed to read " + String::IntToString(numBytes) + " bytes from file");
// Compare the read bytes.
Bool ok = true;
for (Int32 idxByte = 0; idxByte < numBytes; ++idxByte)
{
if (readBytes[idxByte] != testBytes[idxByte])
{
ok = false;
break;
}
}
if (ok)
GePrint("The file seems to be a C4D scene file.");
else
GePrint("The file does not seem to be a C4D scene file.");
// This example demonstrates writing raw bytes to a file.
const String text = "The quick brown cube jumps over the sphere.";
// Let user select a directory.
if (!fn.FileSelect(FILESELECTTYPE_ANYTHING, FILESELECT_DIRECTORY, "Select a directory for a new file..."))
return false;
fn += Filename("myTextFile.txt");
if (!file)
return PrintFileError(fn, file, "Failed to allocate BaseFile for");
// Open a file with the purpose of writing data to it.
return PrintFileError(fn, file, "Failed to open the file for writing");
// Get a byte representation of the string.
const Int32 numBytes = text.GetCStringLen();
Char* cText = text.GetCStringCopy();
if (!cText)
{
GePrint("Error: Failed to get C string");
return false;
}
// Write data to file
if (!file->WriteBytes(cText, numBytes))
return PrintFileError(fn, file, "Failed to write " + String::IntToString(numBytes) + " bytes to file");
DeleteMem(cText); // Don't forget to free the memory of the C string.
// This example demonstrates seeking a position in a file.
// Let user select a file.
if (!fn.FileSelect(FILESELECTTYPE_ANYTHING, FILESELECT_LOAD, "Select the file from BaseFile Write example..."))
return false;
if (!file)
return PrintFileError(fn, file, "Failed to allocate BaseFile for");
// Open a file with the purpose of reading/writing data from/to it.
return PrintFileError(fn, file, "Failed to open the file for reading/writing");
// Prove: After opening position is zero.
// Note: This is different, if the file is opened with FILEOPEN_APPEND.
GePrint("File opened with FILEOPEN_READWRITE, position: " + String::IntToString(file->GetPosition()));
// Change read/write position.
// Assuming the file from the BaseFile Write (RAW) example is used, skipping three bytes should point to the space before "quick".
if (!file->Seek(3))
return PrintFileError(fn, file, "Failed to seek position 3");
GePrint("Reading from position: " + String::IntToString(file->GetPosition()));
// Read a few bytes
// Assuming the file from the BaseFile Write (RAW) example is used, reading six bytes should read " quick" (including preceding space).
Char word[7]; // Have buffer one byte larger than needed.
if (!file->ReadBytes(word, sizeof(word) - 1, false))
return PrintFileError(fn, file, "Failed to read six bytes");
word[6] = 0; // Assure zero terminated string.
// Now, change position to the end of the file in order to append the word just read.
if (!file->Seek(file->GetLength(), FILESEEK_START)) // using absolute position in file
return PrintFileError(fn, file, "Failed to seek end of file");
GePrint("Writing \"" + String(word) + "\"to end of file, position: " + String::IntToString(file->GetPosition()));
// Write the read word at the end of the file.
if (!file->WriteBytes(word, sizeof(word)))
return PrintFileError(fn, file, "Failed to append bytes to the end of file");
  • BaseFile::ReadBytes(): Reads a block of bytes from the file. By default throwing an error, if there are less bytes in the file than requested.
  • BaseFile::TryReadBytes(): Reads a block of bytes from the file, without throwing an error, if there are less bytes in the file than requested. Basically the same as ReadBytes(data, len, true).
  • BaseFile::WriteBytes(): Writes a block of bytes to the file.
  • BaseFile::GetPosition(): Returns the current position of the read/write pointer.
  • BaseFile::Seek(): Sets the read/write pointer to another position.

Parameter "mode":

Note
It is possible to seek beyond the end of a file. When writing to this position, the skipped parts of the file will be filled with zeroes. A read from that position will end up in an error.

Utility

These utility functions provide some information on the file. Especially BaseFile::GetError() is frequently used to get information, what went wrong, if an error occured with one of the above functions.

Return value LOCATION:

// This example demonstrates accessing some file information.
// Let user select a file.
if (!fn.FileSelect(FILESELECTTYPE_ANYTHING, FILESELECT_LOAD, "Select a C4D scene file..."))
return false;
if (!file)
return PrintFileError(fn, file, "Failed to allocate BaseFile for");
// Open the file
return PrintFileError(fn, file, "Failed to open the file for reading");
// Print some infos about the file.
const Int64 fileSize = file->GetLength();
const LOCATION fileLocation = file->GetLocation();
GePrint("File size: " + String::IntToString(fileSize) + " bytes");
switch (fileLocation)
{
GePrint("File is located on disk");
break;
GePrint("File is located on a network device"); // Note: This is a special case, that can't occur in this situation.
break;
GePrint("File is located in memory"); // Note: This is a special case, see MemoryFileStruct manuals and examples.
break;
}

Further Reading