Reputation: 738
Like in C we could use structure pointers to read or write structured binary data like file headers etc, is there a similar way to do this in C#?
Upvotes: 5
Views: 5299
Reputation: 180908
To read arbitrarily-structured data (a struct
) from a binary file, you first need this:
public static T ToStructure<T>(byte[] data)
{
unsafe
{
fixed (byte* p = &data[0])
{
return (T)Marshal.PtrToStructure(new IntPtr(p), typeof(T));
}
};
}
You can then:
public static T Read<T>(BinaryReader reader) where T: new()
{
T instance = new T();
return ToStructure<T>(reader.ReadBytes(Marshal.SizeOf(instance)));
}
To write, convert the struct
object to a byte array:
public static byte[] ToByteArray(object obj)
{
int len = Marshal.SizeOf(obj);
byte[] arr = new byte[len];
IntPtr ptr = Marshal.AllocHGlobal(len);
Marshal.StructureToPtr(obj, ptr, true);
Marshal.Copy(ptr, arr, 0, len);
Marshal.FreeHGlobal(ptr);
return arr;
}
...and then just write the resulting byte array to a file using a BinaryWriter
.
Upvotes: 3
Reputation: 137547
Using BinaryReader
and BinaryWriter
over a MemoryStream
tends to be the best way in my opinion.
Parsing binary data:
byte[] buf = f // some data from somewhere
using (var ms = new MemoryStream(buf, false)) { // Read-only
var br = new BinaryReader(ms);
UInt32 len = br.ReadUInt32();
// ...
}
Generating binary data:
byte[] result;
using (var ms = new MemoryStream()) { // Expandable
var bw = new BinaryWriter(ms);
UInt32 len = 0x1337;
bw.Write(len);
// ...
result = ms.GetBuffer(); // Get the underlying byte array you've created.
}
They allow you to read and write all of the primitive types you'd need for most file headers, etc. such as (U)Int16, 32, 64
, Single
, Double
, as well as byte
, char
and arrays of those. There is direct support for string
s, however only if
The string is prefixed with the length, encoded as an integer seven bits at a time.
This only seems useful to me if you wrote the string in this way from BinaryWriter
in this way. It's easy enough however, say your string is prefixed by a DWord length, followed by that many ASCII characters:
int len = (int)br.ReadUInt32();
string s = Encoding.ASCII.GetString(br.ReadBytes(len));
Note that I do not have the BinaryReader
and BinaryWriter
objects wrapped in a using()
block. This is because, although they are IDisposable
, all their Dispose()
does is call Dispose()
on the underlying stream (in these examples, the MemoryStream
).
Since all the BinaryReader
/BinaryWriter
are is a set of Read()
/Write()
wrappers around the underlying streams, I don't see why they're IDisposable
anyway. It's just confusing when you try to do The Right Thing and call Dispose()
on all your IDisposable
s, and suddenly your stream is disposed.
Upvotes: 9
Reputation: 2079
Here is an simple example showing how to read and write data in Binary format to and from a file.
using System;
using System.IO;
namespace myFileRead
{
class Program
{
static void Main(string[] args)
{
// Let's create new data file.
string myFileName = @"C:\Integers.dat";
//check if already exists
if (File.Exists(myFileName))
{
Console.WriteLine(myFileName + " already exists in the selected directory.");
return;
}
FileStream fs = new FileStream(myFileName, FileMode.CreateNew);
// Instantialte a Binary writer to write data
BinaryWriter bw = new BinaryWriter(fs);
// write some data with bw
for (int i = 0; i < 100; i++)
{
bw.Write((int)i);
}
bw.Close();
fs.Close();
// Instantiate a reader to read content from file
fs = new FileStream(myFileName, FileMode.Open, FileAccess.Read);
BinaryReader br = new BinaryReader(fs);
// Read data from the file
for (int i = 0; i < 100; i++)
{
//read data as Int32
Console.WriteLine(br.ReadInt32());
}
//close the file
br.Close();
fs.Close();
}
}
}
Upvotes: 0