Binary file I/O is actually quite simple no matter what method you use.
You can use C block array-based/character-based I/O, C++ streams, Win32 file I/O, or a combination of them. Don't be fooled into thinking one is more elegant than the other. I've used them all and have found that good old fashioned _open,_read, and _write get the job done quite nicely.
There are newer versions of these in MSVC .NET but they do the same thing.
Array-based I/O is really what you need since you won't be looking for text strings, etc, etc.
Remember you cannot store pointers on disk because a pointer is just an address in memory that contains another address in memory. So the program would have to load at the exact same place in memory and the OS would have to stick all the data in the exact same place in order for the pointer to be valid. Not gonna happen. You could use offsets into the segment, but my advice is don't use pointers on disk. You can de-reference a pointer and write to disk what it points to (IE: a string, some data, vertices, etc, etc.) but writing the pointer won't accomplish anything. A pointer is just a DWORD or a 32-bit variable that contains an address in memory. When you de-reference the pointer, the compiler looks at the address of the pointer, reads the value at that address, and uses that value as an address to another portion of memory.
This is why I said use IDs in your tree's instead of pointers.
But here is how I normally use file I/O for my game code. The editor in MFC uses nearly the same process except it does so through CArchive and CFile. CArchive's know how to write CString's to disk correctly so no pointer problems. Quite handy.
This is only ONE of many, many methods available to you. Use what you like.
Open file in binary and write to it
Code:
struct TestStructure
{
DWORD dwValue1;
int iValue2;
BYTE uValue3;
};
int handle=_open("MyFile.dat",_O_BINARY | O_CREAT, _S_IWRITE);
if (handle==-1)
{
//error
}
//Read structure from file
TestStructure Object;
_write(handle,&Object,sizeof(TestStructure));
//close file
_close(handle);
Stream based I/O can also accomplish this because they also support array-based reading and writing using certain functions.
Take for instance this CArchive from MFC.
Code:
//Create CFile and CArchive
CFile theFile("MyFile.dat",CFile::modeCreate | CFile::modeWrite);
CArchive theArchive(theFile,CArchive::store);
//Set some variables for testing
//Integer
int iX=5;
//DWORD
DWORD dwY=0xFFFFFFFF;
//CString
CString testString;
testString.Format("Testing: %i %u",iX,dwY);
//DWORD array
DWORD *m_pData;
m_pData=new DWORD[dwY];
memset(m_pData,0xFFFFFFFF,dwY * sizeof(DWORD));
//Write integer and CString object to disk
theArchive << iX << testString;
//Write literal string to disk
theArchive.WriteString("Hello");
//Write m_pData array to disk
theArchive.Write(m_pData,dwY*sizeof(DWORD));
//Close archive
theArchive.Close();
//Close the file
theFile.Close();
For more info concerning the extremely robust C/C++ I/O system, I recommend consulting a book on C/C++.
Also Win32 provides functions for this as well. So there are about a billion ways to do this.