2011-08-02 27 views
1

我有這個資源文件,我需要處理,它包裝了一組文件。如何將字節塊讀入結構體

首先,資源文件列出其中包含的所有文件,加上其他一些數據,比如在這個結構:

struct FileEntry{ 
    byte Value1; 
    char Filename[12]; 
    byte Value2; 
    byte FileOffset[3]; 
    float whatever; 
} 

所以我需要準確讀取的塊這種規模。

我正在使用FileStream中的Read函數,但是如何指定結構的大小? 我用:

int sizeToRead = Marshal.SizeOf(typeof(Header)); 

,然後將該值傳遞給閱讀,後來我只能看懂一組爲byte [],我不知道如何轉換爲指定的值(好我不知道如何獲取單個字節值......但不包括其餘部分)。

此外,我需要指定一個不安全的情況下,我不知道它是否是正確的或不...

在我看來,閱讀字節流比我想象中的.NET :)

強硬

謝謝!

+0

您能指出您使用的語言嗎?我大膽猜測並將[tag:c#]添加到標籤。 –

+1

[AC#相當於C的fread文件I/O]的可能重複(http://stackoverflow.com/questions/1935851/ac-equivalent-of-cs-fread-file-io) –

+0

對不起,這是確實是C#。 –

回答

7

假設這是C#,我不會創建一個結構作爲FileEntry類型。我會用字符串替換char [20],並使用BinaryReader - http://msdn.microsoft.com/en-us/library/system.io.binaryreader.aspx來讀取各個字段。您必須按照寫入順序讀取數據。

喜歡的東西:

class FileEntry { 
    byte Value1; 
    char[] Filename; 
    byte Value2; 
    byte[] FileOffset; 
    float whatever; 
} 

    using (var reader = new BinaryReader(File.OpenRead("path"))) { 
    var entry = new FileEntry { 
     Value1 = reader.ReadByte(), 
     Filename = reader.ReadChars(12) // would replace this with string 
     FileOffset = reader.ReadBytes(3), 
     whatever = reader.ReadFloat()   
    }; 
    } 

如果你堅持有結構,你應該讓你的結構不變,並創建爲每個域的參數的構造函數。

 

+0

這工作就像一個魅力。你會如何「用字符串替換它」?使用ReadString(),你不能指定一個大小,所以它讀取超出所需的位置。 –

+0

實際上,如果字符串之前被寫爲字符串,則字符串的大小將包含在流中。來自MSDN - 「從當前流中讀取一個字符串,該字符串的前綴是長度,一次編碼爲7位整數。」 (http://msdn.microsoft.com/en-us/library/system.io.binaryreader.readstring.aspx)。但是,在此之前,您還必須使用BinaryWriter.Write(string)。你可以使用字符構造一個字符串 - 「StringField = new string(reader.ReadChars(20));」。 – Vasea

+0

現在感覺很蠢。謝謝你們! –

2

結束語您FileStreamBinaryReader會給你專門用於原始類型Read*()方法: http://msdn.microsoft.com/en-us/library/system.io.binaryreader.aspx

從我腦袋裏,你很可能標誌着你struct[StructLayout(LayoutKind.Sequential)](以確保適當的代表性內存)並使用unsafe塊中的指針實際填充結構C風格。但是,如果您不需要它(互操作,像圖像處理等繁重操作),則不推薦使用unsafe

5

如果可以使用不安全的代碼:

unsafe struct FileEntry{ 
    byte Value1; 
    fixed char Filename[12]; 
    byte Value2; 
    fixed byte FileOffset[3]; 
    float whatever; 
} 

public unsafe FileEntry Get(byte[] src) 
{ 
    fixed(byte* pb = &src[0]) 
    { 
     return *(FileEntry*)pb; 
    } 
} 

固定關鍵字嵌入在struct陣列。由於它是固定的,如果你不斷創建這些並且永不放棄他們,這個可能會導致GC問題。請記住,恆定大小是n * sizeof(t)。所以Filename [12]分配24個字節(每個char是2個字節的unicode),FileOffset [3]分配3個字節。如果你不處理磁盤上的unicode數據,這很重要。我建議將其更改爲一個字節[]並將結構轉換爲一個可用類,您可以在其中轉換字符串。

如果您不能使用不安全的,你可以做整個BinaryReader在做法:

public unsafe FileEntry Get(Stream src) 
{ 
    FileEntry fe = new FileEntry(); 
    var br = new BinaryReader(src); 
    fe.Value1 = br.ReadByte(); 
    ... 
} 

不安全的方式幾乎是瞬間,遠遠快,特別是當你在一次轉換了很多結構的。問題是你想使用不安全的。我的建議是隻使用不安全的方法,如果你絕對需要的性能提升。

+0

可能會更聰明,不要使用不安全的,因爲它只是小文件,其中承諾費率不會真正引人注目。 –

3

基於this article,只有我把它作爲通用的,這是如何將數據直接編組到結構。對較長的數據類型非常有用。

public static T RawDataToObject<T>(byte[] rawData) where T : struct 
{ 
    var pinnedRawData = GCHandle.Alloc(rawData, 
             GCHandleType.Pinned); 
    try 
    { 
     // Get the address of the data array 
     var pinnedRawDataPtr = pinnedRawData.AddrOfPinnedObject(); 

     // overlay the data type on top of the raw data 
     return (T) Marshal.PtrToStructure(pinnedRawDataPtr, typeof(T)); 
    } 
    finally 
    { 
     // must explicitly release 
     pinnedRawData.Free(); 
    } 
} 

實例應用:

[StructLayout(LayoutKind.Sequential)] 
public struct FileEntry 
{ 
    public readonly byte Value1; 

    //you may need to play around with this one 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)] 
    public readonly string Filename; 

    public readonly byte Value2; 

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] 
    public readonly byte[] FileOffset; 

    public readonly float whatever; 
} 

private static void Main(string[] args) 
{ 
    byte[] data =;//from file stream or whatever; 
    //usage 
    FileEntry entry = RawDataToObject<FileEntry>(data); 
} 
+0

是的,字符串可能更好地讀爲'Byte []',然後使用某種方法將其轉換爲'Char []'然後再轉換爲'String'。通過一些簡單的「ascii-only」檢查和經典的0結尾c字符串行爲,這就像'String filename = new String(filenameArr.TakeWhile(x => x!= 0).Select(x => x <128?Convert.ToChar(x):'?')。ToArray());' – Nyerguds

0

不是一個完整的答案(它被覆蓋,我認爲),但文件名的特定注:

Char類型可能不是一個單因爲.Net字符是unicode,這意味着它們支持遠遠超過255的字符值,因此將文件名數據解釋爲Char[]數組會產生問題。所以第一步肯定是讀取Byte[12]而不是Char[12]

從字節數組字符數組的直轉換也未在二進制索引這樣,文件名是大於允許12個字符將有可能與0字節填充,所以直轉換建議,雖然,因爲將會產生一個總是12個字符的字符串,並可能以這些零字符結束。

然而,簡單地修剪這些零關是不建議的,因爲閱讀這些數據的系統通常簡單地讀取到第一個遇到的零和數據背後數組中實際上可能包含垃圾如果書寫系統沒有按在將字符串放入它之前,不用專門清零它的緩衝區。這是很多程序不打擾的事情,因爲他們認爲閱讀系統只會將字符串解釋爲第一個零。因此,假設這確實是一種典型的零終止(C風格)字符串,以每字符一個字節的文本編碼(如ASCII或Win-1252)保存,第二步是剪切關閉第一個零的字符串。你可以用Linq的TakeWhile函數輕鬆做到這一點。然後第三步也是最後一步是將得到的字節數組轉換爲字符串,不管它與寫一個字節每字符文本編碼恰好是:

public String StringFromCStringArray(Byte[] readData, Encoding encoding) 
{ 
    return encoding.GetString(readData.TakeWhile(x => x != 0).ToArray()); 
} 

正如我所說的,編碼可能會是像純ASCII,可以從或Windows-1252(標準的美國/西歐Windows文本編碼)進行訪問,您可以使用Encoding.GetEncoding("Windows-1252")進行檢索。