Thứ Năm, 31 tháng 5, 2012

ĐỌC GHI MỘT TẬP TIN NHỊ PHÂN CÓ CẤU TRÚC DỄ DÀNG VỚI C#


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];
   ...
};
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
{
    ...
    public ID3v1Tag(byte[] data)
    {
       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];
 ...
}
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);
 }
 if (t.Tag == "TAG")
 {
  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);
[System.Runtime.InteropServices.DllImport("CRTFileIO.dll")]
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);
[System.Runtime.InteropServices.DllImport("CRTFileIO.dll")]
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);
[System.Runtime.InteropServices.DllImport("CRTFileIO.dll")]
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);
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;
}
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)));
}
public unsafe bool Read(void* buffer, short 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
{
 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

Comments

# re: Easy high speed reading/writing of structured binary files using C#

Sunday, March 05, 2006 4:28 AM by Ayende Rahien
Assuming this is C# 2.0, and you need to do this on more than one struct, you can make it easier by using generics:

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#

Sunday, March 05, 2006 5:39 AM by Ralf
@Ayende: Thx for your idea. However, it introduces the very data copy I wanted to avoid: ReadStruct() returns a struct on the stack which probably needs to be copied to the real destination in the caller´s method.

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#

Monday, February 19, 2007 10:29 AM by Daniele Bertocchi
       IntPtr ptr = IntPtr.Zero;
       Marshal.StructureToPtr(YourStruct, true);
       fs = new FileStream(Filename,FileMode.CreateNew,FileAccess.Write);        
       byte* bytedata = (byte*)ptr.ToPointer();
       for (int i = 0; i < Marshal.SizeOf(YourStruct); ++i)
       {
         fs.WriteByte(bytedata[i]);
       }

# re: Easy high speed reading/writing of structured binary files using C#

Friday, July 06, 2007 5:04 PM by Cesar Wilson
If you want to save an struct in C# like in C++
you must use MarshalAs attribute.
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct Empleado
{
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=32)]
public string name;
public UInt32 id;
}
With the MarshalAs attribute, you set that when you use the string in an unmanaged context, it would be used like an ANSI null terminated string of 32 bytes of fixed size.
you can use the string member of the struct like a normal string. When you wanto to write the struct into a binary file, you must use a code like this:
Empleado emp = new Empleado();
FileStream fstream = new FileStream("C:\\binario.bin", FileMode.Create, FileAccess.Write);
BinaryWriter binwriter = new BinaryWriter(fstream);
emp.name = "Estuardo";
emp.id = 0x00112233;
int size = Marshal.SizeOf(emp);
IntPtr handle = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(emp, handle, true);
byte* ptr = (byte*)handle.ToPointer();
while(size-- != 0)
{
   binwriter.Write(*ptr++);
}
Marshal.FreeHGlobal(handle);
binwriter.Close();
this code is only valid in an unsafe context
(you must compile your code with the /unsafe option). The Marshal class is declared in the
System.Runtime.Interop namespace

Không có nhận xét nào:

Đăng nhận xét