THAM KHẢO: http://weblogs.asp.net/ralfw/archive/2006/03/04/439580.aspx
Easy high speed reading/writing of structured binary files using C#
Thật không may là không đơn giản như C / C + +. Đây là cách bạn có thể đọc thẻ ID3v1 từ một tập tin MP3 từ C/C++:
struct ID3v1Tag
{
char tag[3]; // == "TAG"
char title[30];
...
};
{
char tag[3]; // == "TAG"
char title[30];
...
};
ID3v1Tag t;
FILE *f = fopen("mysong.mp3", "r");
fseek(f, -128, SEEK_END);
fread(&t, 1, 128, f);
printf("%.30s\n", t.title);
fclose(f);
Bây giờ, nếu bạn muốn thực hiện tương tự với C # ... nó sẽ không dễ dàng . Lý do: bạn không thể đọc dữ liệu từ một tập tin (stream) trực tiếp vào một cấu trúc. Stream luôn luôn đòi hỏi một byte Array để chứa kết quả đọc được. Hoặc nếu bạn sử dụng phương thức BinaryReader theReadBytes() cũngtrả về một byte Array. Trong bất kỳ trường hợp nào các dữ liệu đọc vào một mảng byte cần phải được sao chép vào các Struct.
Cách 1: dùng Marshal.PtrToStructure() , và cách khác đơn giản hơn là dùng unsafe assignment như sau:
[StructLayout(LayoutKind.Sequential, Pack=1)]
unsafe struct ID3v1Tag
{
...
unsafe struct ID3v1Tag
{
...
public ID3v1Tag(byte[] data)
{
fixed (byte* pData = data)
{
this = *(ID3v1Tag*)pData;
}
}
}
{
fixed (byte* pData = data)
{
this = *(ID3v1Tag*)pData;
}
}
}
Hoặc bạn có thể đọc dữ liệu từ một dòng đầu vào trong khối nhỏ bằng cách sử dụng một BinaryReader, có nghĩa là bạn tách các dữ liệu vào mỗi trường bằng tay.Điều này tránh sao chép thêm dữ liệu, nhưng đòi hỏi nhiều nỗ lực. Đây là cách đọc ghi dữ liệu nhị phân bình thường của C# (managed code)
Tuy nhiên, do một khách hàng yêu cầu, tôi đã bắt đầu suy nghĩ về cách này. Khách hàng cần phải chuyển C++ tương tác lớn với các tập tin nhị phân thành C #.Các phương pháp tiếp cận được tìm thấy trong các tài liệu lập trình, mặc dù vậy quá chậm theo yêu cầu của khách hàng. Truy suất dữ liệu bên ngoài làm chậm hiệu quả của chương trình. Do đó, anh ta giữ lại các thành phần cần thiết của C++ đê được hưởng lợi từ ngôn ngữ lập trình này trong việc truy cập dữ liệu.
Tôi cảm thấy bị thách thức bởi vấn đề này. Và đây là giải pháp của tôi: Dễ dàng đọc / ghi dữ liệu nhị phân có cấu trúc bằng cách sử dụng C # 2.0 - mà không cần cho thêm một module bên ngoài nào. Nhìn vào đoạn mã sau để đọc thẻ ID3v1 của một tập tin MP3:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct ID3v1Tag
{
private fixed sbyte tag[3];
private fixed sbyte title[30];
...
}
public unsafe struct ID3v1Tag
{
private fixed sbyte tag[3];
private fixed sbyte title[30];
...
}
using (System.IO.BinaryFile fmp3 = new System.IO.BinaryFile("myfile.mp3", System.IO.FileMode.Open))
{
ID3v1Tag t;
unsafe
{
fmp3.Seek(-128, System.IO.SeekOrigin.End);
fmp3.ReadStruct<ID3v1Tag>(&t);
}
{
ID3v1Tag t;
unsafe
{
fmp3.Seek(-128, System.IO.SeekOrigin.End);
fmp3.ReadStruct<ID3v1Tag>(&t);
}
if (t.Tag == "TAG")
{
Console.WriteLine("title: " + t.Title); ...
}
}
{
Console.WriteLine("title: " + t.Title); ...
}
}
Tôi muốn nói đó là dễ dàng để đọc / ghi trên C + + tương đương. Các dữ liệu ID3v1 tag được đọc trực tiếp vào struct ID3v1Tag thông qua phương thức Read ().
Làm thế nào là thực hiện điều này?
Tôi không sử dụng System.IO để truy cập các tập tin, nhưng sử dụng các hàm fxxx(). Lớp BinaryFile trên đóng gói các cuộc gọi đến các hàm C DLL sau:
[System.Runtime.InteropServices.DllImport("CRTFileIO.dll")]
private static extern int FileOpen(string filename, string mode);
private static extern int FileOpen(string filename, string mode);
[System.Runtime.InteropServices.DllImport("CRTFileIO.dll")]
private static extern void FileClose(int hStream);
private static extern void FileClose(int hStream);
[System.Runtime.InteropServices.DllImport("CRTFileIO.dll")]
private unsafe static extern bool FileReadBuffer(int hStream, void* buffer, short bufferLen);
private unsafe static extern bool FileReadBuffer(int hStream, void* buffer, short bufferLen);
[System.Runtime.InteropServices.DllImport("CRTFileIO.dll")]
private unsafe static extern bool FileWriteBuffer(int hStream, void* buffer, short bufferLen);
private unsafe static extern bool FileWriteBuffer(int hStream, void* buffer, short bufferLen);
[System.Runtime.InteropServices.DllImport("CRTFileIO.dll")]
private unsafe static extern bool FileSeek(int hStream, int offset, short origin);
private unsafe static extern bool FileSeek(int hStream, int offset, short origin);
[System.Runtime.InteropServices.DllImport("CRTFileIO.dll")]
private unsafe static extern bool FileGetPos(int hStream, out int pos);
private unsafe static extern bool FileGetPos(int hStream, out int pos);
[System.Runtime.InteropServices.DllImport("CRTFileIO.dll")]
private unsafe static extern bool FileFlush(int hStream);
private unsafe static extern bool FileFlush(int hStream);
Tôi chỉ cần viết một wrapper DLL unmanaged xung quanh các chức năng cơ bản C stdio như fopen (), fread () ... Đó là tất cả sự kỳ diệu có. Nhìn vào chức năng C của tôi để đọc dữ liệu từ một tập tin:
extern "C" DLLEXPORT short __stdcall FileReadBuffer(FILE *stream, void *buffer, int bufferLen)
{
int n = fread(buffer, 1, bufferLen, stream);
return n == bufferLen;
}
{
int n = fread(buffer, 1, bufferLen, stream);
return n == bufferLen;
}
Hàm này được gọi bởi phương thức của lớp đóng gói và làm cho chương trình làm việc với tập tin nhị phân dễ dàng hơn. BinaryFile dấu cac File HANDLE and nhìn giống như a FileStream (đó là lý do tại sao mình bỏ BinaryFile vào System.IO namespace):
public unsafe bool ReadStruct<StructType>(void *buffer) where StructType : struct
{
return Read(buffer, (short)System.Runtime.InteropServices.Marshal.SizeOf(typeof(StructType)));
}
{
return Read(buffer, (short)System.Runtime.InteropServices.Marshal.SizeOf(typeof(StructType)));
}
public unsafe bool Read(void* buffer, short bufferLen)
{
...
return FileReadBuffer(hFile, buffer, bufferLen);
}
{
...
return FileReadBuffer(hFile, buffer, bufferLen);
}
Bạn chuyển địa chỉ của cấu trúc cho mà Read() để nhận dữ liệu từ File và số Byte đọc được. Hàm fread() sẽ bỏ dữ liệu vào struct của C#. không cần phải tách từng trường một. Bạn chỉ cần dùng đoạn mã unsafe sau:
unsafe
unsafe
{
fmp3.Read<MyStruct>(&myStructVar);
}
fmp3.Read<MyStruct>(&myStructVar);
}
Tôi muốn nói, nó không thể trở nên dễ dàng hơn hoặc nhanh hơn này, khi đọc từ tập tin nhị phân.Nếu bạn muốn cung cấp cho phương pháp này một thử, bạn có thể download sources here.
Để dung BinaryFile class chỉ cần thêm reference CRTFileIO.Import.dll vào C# project và bảo đảm C wrapper CRTFileIO.dll chép cùng thư mục với CRTFileIO.Import.dll.
Enjoy!
Resources
[1] Anthony Baraff: Fast Binary File Reading with C#, http://www.codeproject.com/csharp/fastbinaryfileinput.asp
[2] Robert L. Bogue: Read binary files more efficiently using C#,http://www.builderau.com.au/architect/webservices/0,39024590,20277904,00.htm
[3] Eric Gunnerson: Unsafe and reading from files, http://blogs.msdn.com/ericgu/archive/2004/04/13/112297.aspx
Published Saturday, March 04, 2006 11:25 PM by Ralf Westphal
Filed under: .NET Fx Programmierung, Tipps&Tricks
# re: Easy high speed reading/writing of structured binary files using C#
public T ReadStruct(string filename)<T> where T:struct, new()
using (System.IO.BinaryFile fmp3 = new System.IO.BinaryFile(filename, System.IO.FileMode.Open))
{
T t = new T()
unsafe
{
fmp3.Seek(-128, System.IO.SeekOrigin.End);
fmp3.Read(&t, (short)Marshal.SizeOf(typeof(T)));
}
return T;
}
# re: Easy high speed reading/writing of structured binary files using C#
Nonetheless using Generics could make my Read() method a little easier, since the struct length could be determined automatically.
-Ralf
# re: Easy high speed reading/writing of structured binary files using C#
# re: Easy high speed reading/writing of structured binary files using C#