まずはC#でデータをためていく方法について考えましょう。
素朴にstringに加えていきます。
class Program {
public static void Main() {
string str = "";
str += "abc";
str += "def";
System.Console.WriteLine(str);
}
}
C/C++から考えると恐ろしいですが、実際には+=は末尾に付け加えるメソッドになっています。
ところが文字列でないデータ列の場合、これでは読み出せません。なぜならば、
str[n]としたところでn文字目が読み出されるからです。やはり8bitごと区切られたデータが欲しい。
それではListを使いましょうか?
using System.Collections.Generic;
class Program {
public static void Main() {
List<byte> data = new List<byte>();
data.Add(0x10);
data.Add(0x20);
data.Add(0x30);
data.Add(0x40);
foreach(byte value in data) {
System.Console.WriteLine(value);
}
}
}
うむ……これでは配列が欲しいとき困る。
そんなときにMemoryStreamです。可変長で、好きなときにWriteで書き込みできます。(内部の実装はお察し)
データをためて行くにはWriteしていけばよいです。
using System.IO;
class Program {
public static void Main() {
MemoryStream ms = new MemoryStream();
ms.Write(new byte[] { 0x10 }, 0, 1);
ms.Write(new byte[] { 0x20 }, 0, 1);
ms.Write(new byte[] { 0x30, 0x40 }, 0, 2);
byte[] data = ms.ToArray();
foreach(byte value in data) {
System.Console.WriteLine(value);
}
}
}
さて本題。今、0byte目, 1byte目, 2byte目に意味のある数字が並んでおり、それぞれa, b, cと名付けましょう。
そして、0, 2byte目は8bitの非負整数、1byte目は16bitの非負整数であるとします。与えられたストリーム(byte列としましょう)からこれらに振り分けるにはどうしたらいいでしょうか?
力尽くで、if文など駆使して書けるでしょうが、d, e, f, ...と続く場合は見づらいコードになります。
ここはC/C++のような構造体を使ってシンプルに書きたいものです。が、C#にはわかりやすい書き方はありません。
正確に言うと余り推奨していないやり方なのでしょう。
以下の例は構造体にオフセットを用いて各データを並べています。
これはごく普通のやり方なのですが、System.Runtime.InteropServices.StructLayout()で属性を設定します。
LayoutKind.Explicitを指定することでFieldOffsetにより構造体の位置を指定します。
先頭が0byte目で、例えば[FieldOffset(0)]の次に来るpublic なんちゃらは、0byte目のメモリ空間を使うことになります。[FieldOffset(1)]ならば次に来るpublicなんちゃらは1byte目のメモリ空間を使うことになります。
おなじ[FieldOffset(x)]を設定したら、それらはメモリの開始位置を共有します。
byteなら1byte, shortなら2byte, intなら4byteのように領域が決まっていますが、
例えばbyte, short, intの3つのフィールドを[FieldOffset(0)]で宣言したとき、
Bxxx
SSxx
IIII
のように、共有します。即ち、byteのデータはshortの最下位バイトと共有し、intの最下位バイトとも共有するわけです。先頭なのに何で最下位か? これはリトルエンディアンと呼ばれる方式で、
0x11223344という32bitの整数は「0x44」「0x33」「0x22」「0x11」の順番でメモリに格納されます。
従って共有されるのは最下位バイトなのです。
さて、実際に宣言してみると問題が発生します。
というのも、C#のbyte配列というものはオブジェクトであって、メモリをリニアーに配置した値型ではないのです。簡単に言うとポインタであってメモリ領域ではないのです。C/C++では構造体は配列と同じくデータ領域でしかなかったため柔軟に対応できました。
この問題を解決するためにC#では固定長配列が宣言できます。ただしunsafeで。ぶっちゃけunsafeなんか使うくらいならC/C++使った方がいいと思うんですけど(外部ライブラリ作ってでもいい)、やってみました。
fixed 基本型名 変数名[データ数]
で固定長が宣言できます。
using System.Runtime.InteropServices;
class Program {
[StructLayout(LayoutKind.Explicit)]
unsafe struct S {
[FieldOffset(0)]
public fixed byte data[4];
[FieldOffset(0)]
public byte a;
[FieldOffset(1)]
public ushort b;
[FieldOffset(3)]
public byte c;
}
public static void Main() {
S s = new S();
unsafe {
byte[] data = new byte[] { 1, 2, 3, 4 };
int i = 0;
foreach(byte d in data) {
s.data[i] = data[i];
i++;
}
}
System.Console.WriteLine("{0:X}, {1:X}, {2:X}", s.a, s.b, s.c);
}
}
もっといい方法はないかね。
0 件のコメント:
コメントを投稿