C#で構造体をバイト配列に変換するにはどうすればよいですか?
このような構造を定義しました:
public struct CIFSPacket
{
public uint protocolIdentifier; //The value must be "0xFF+'SMB'".
public byte command;
public byte errorClass;
public byte reserved;
public ushort error;
public byte flags;
//Here there are 14 bytes of data which is used differently among different dialects.
//I do want the flags2. However, so I'll try parsing them.
public ushort flags2;
public ushort treeId;
public ushort processId;
public ushort userId;
public ushort multiplexId;
//Trans request
public byte wordCount;//Count of parameter words defining the data portion of the packet.
//From here it might be undefined...
public int parametersStartIndex;
public ushort byteCount; //Buffer length
public int bufferStartIndex;
public string Buffer;
}
メインメソッドで、そのインスタンスを作成し、値を割り当てます。
CIFSPacket packet = new CIFSPacket();
packet.protocolIdentifier = 0xff;
packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
packet.errorClass = 0xff;
packet.error = 0;
packet.flags = 0x00;
packet.flags2 = 0x0001;
packet.multiplexId = 22;
packet.wordCount = 0;
packet.byteCount = 119;
packet.Buffer = "NT LM 0.12";
次に、このパケットをソケットで送信します。そのためには、構造体をバイト配列に変換する必要があります。どうすればいいですか?
私の完全なコードは次のとおりです。
static void Main(string[] args)
{
Socket MyPing = new Socket(AddressFamily.InterNetwork,
SocketType.Stream , ProtocolType.Unspecified ) ;
MyPing.Connect("172.24.18.240", 139);
//Fake an IP Address so I can send with SendTo
IPAddress IP = new IPAddress(new byte[] { 172,24,18,240 });
IPEndPoint IPEP = new IPEndPoint(IP, 139);
//Local IP for Receiving
IPEndPoint Local = new IPEndPoint(IPAddress.Any, 0);
EndPoint EP = (EndPoint)Local;
CIFSPacket packet = new CIFSPacket();
packet.protocolIdentifier = 0xff;
packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
packet.errorClass = 0xff;
packet.error = 0;
packet.flags = 0x00;
packet.flags2 = 0x0001;
packet.multiplexId = 22;
packet.wordCount = 0;
packet.byteCount = 119;
packet.Buffer = "NT LM 0.12";
MyPing.SendTo(It takes byte array as parameter);
}
コードスニペットはどうなりますか?
これは、マーシャリングを使用して非常に簡単です。
ファイルの先頭
using System.Runtime.InteropServices
関数
byte[] getBytes(CIFSPacket str) {
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(str, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
そしてそれを元に戻すには:
CIFSPacket fromBytes(byte[] arr) {
CIFSPacket str = new CIFSPacket();
int size = Marshal.SizeOf(str);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(arr, 0, ptr, size);
str = (CIFSPacket)Marshal.PtrToStructure(ptr, str.GetType());
Marshal.FreeHGlobal(ptr);
return str;
}
あなたの構造では、文字列の前にこれを置く必要があります
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string Buffer;
また、SizeConstが可能な最大の文字列と同じ大きさであることを確認してください。
そしておそらくあなたはこれを読むべきです: http://msdn.Microsoft.com/en-us/library/4ca6d5z7.aspx
本当に高速にしたい場合は、CopyMemoryで安全でないコードを使用して実行できます。 CopyMemoryは約5倍高速です(例:800MBのデータは、マーシャリングでコピーするのに3秒かかりますが、CopyMemoryでコピーするのに.6秒しかかかりません)。このメソッドは、構造体blob自体に実際に保存されているデータのみの使用に制限します。数値、または固定長バイト配列。
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
private static unsafe extern void CopyMemory(void *dest, void *src, int count);
private static unsafe byte[] Serialize(TestStruct[] index)
{
var buffer = new byte[Marshal.SizeOf(typeof(TestStruct)) * index.Length];
fixed (void* d = &buffer[0])
{
fixed (void* s = &index[0])
{
CopyMemory(d, s, buffer.Length);
}
}
return buffer;
}
以下の方法をご覧ください。
byte [] StructureToByteArray(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;
}
void ByteArrayToStructure(byte [] bytearray, ref object obj)
{
int len = Marshal.SizeOf(obj);
IntPtr i = Marshal.AllocHGlobal(len);
Marshal.Copy(bytearray,0, i,len);
obj = Marshal.PtrToStructure(i, obj.GetType());
Marshal.FreeHGlobal(i);
}
これはグーグルで見つけた別のスレッドの恥知らずなコピーです!
Update:詳細については、 source
メモリ割り当てが1つ少ないVicentのコードのバリアント:
_public static byte[] GetBytes<T>(T str)
{
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
GCHandle h = default(GCHandle);
try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
Marshal.StructureToPtr<T>(str, h.AddrOfPinnedObject(), false);
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return arr;
}
public static T FromBytes<T>(byte[] arr) where T : struct
{
T str = default(T);
GCHandle h = default(GCHandle);
try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
str = Marshal.PtrToStructure<T>(h.AddrOfPinnedObject());
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return str;
}
_
GCHandle
を使用してメモリを「固定」し、h.AddrOfPinnedObject()
でそのアドレスを直接使用します。
主な答えは、C#で使用できない(または使用できなくなった)CIFSPacketタイプを使用することなので、正しいメソッドを作成しました。
static byte[] getBytes(object str)
{
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(str, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
static T fromBytes<T>(byte[] arr)
{
T str = default(T);
int size = Marshal.SizeOf(str);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(arr, 0, ptr, size);
str = (T)Marshal.PtrToStructure(ptr, str.GetType());
Marshal.FreeHGlobal(ptr);
return str;
}
テスト済みで、動作します。
Marshal(StructureToPtr、ptrToStructure)、およびMarshal.copyを使用できますが、これはプラットフォームに依存します。
シリアル化には、カスタムシリアル化の関数が含まれます。
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
SerializationInfoには、各メンバーをシリアル化する関数が含まれています。
BinaryWriterとBinaryReaderには、バイト配列(ストリーム)に保存/ロードするメソッドも含まれています。
バイト配列からMemoryStreamまたはMemoryStreamからバイト配列を作成できることに注意してください。
メソッドSaveとメソッドNewを構造に作成できます:
Save(Bw as BinaryWriter)
New (Br as BinaryReader)
次に、ストリームに保存/ロードするメンバー->バイト配列を選択します。
これは非常に簡単に行えます。
[StructLayout(LayoutKind.Explicit)]
を使用して構造体を明示的に定義します
int size = list.GetLength(0);
IntPtr addr = Marshal.AllocHGlobal(size * sizeof(DataStruct));
DataStruct *ptrBuffer = (DataStruct*)addr;
foreach (DataStruct ds in list)
{
*ptrBuffer = ds;
ptrBuffer += 1;
}
このコードは、安全でないコンテキストでのみ記述できます。終了したら、addr
を解放する必要があります。
Marshal.FreeHGlobal(addr);
長さを固定する手間をかけずにanystruct
に変換できる別のアプローチを思いつきましたが、結果のバイト配列には少しオーバーヘッドがあります。
サンプルstruct
は次のとおりです。
[StructLayout(LayoutKind.Sequential)]
public class HelloWorld
{
public MyEnum enumvalue;
public string reqtimestamp;
public string resptimestamp;
public string message;
public byte[] rawresp;
}
ご覧のとおり、これらの構造はすべて、固定長の属性を追加する必要があります。多くの場合、必要以上のスペースを占有することになります。 FieldInfo
をプルするときにリフレクションが常に同じ順序を与えるようにするため、LayoutKind.Sequential
が必要であることに注意してください。私のインスピレーションはTLV
Type-Length-Valueからです。コードを見てみましょう:
public static byte[] StructToByteArray<T>(T obj)
{
using (MemoryStream ms = new MemoryStream())
{
FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo info in infos)
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream inms = new MemoryStream()) {
bf.Serialize(inms, info.GetValue(obj));
byte[] ba = inms.ToArray();
// for length
ms.Write(BitConverter.GetBytes(ba.Length), 0, sizeof(int));
// for value
ms.Write(ba, 0, ba.Length);
}
}
return ms.ToArray();
}
}
上記の関数は、単純にBinaryFormatter
を使用して未知のサイズの未加工object
をシリアル化し、サイズも追跡し、出力MemoryStream
に格納します。
public static void ByteArrayToStruct<T>(byte[] data, out T output)
{
output = (T) Activator.CreateInstance(typeof(T), null);
using (MemoryStream ms = new MemoryStream(data))
{
byte[] ba = null;
FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo info in infos)
{
// for length
ba = new byte[sizeof(int)];
ms.Read(ba, 0, sizeof(int));
// for value
int sz = BitConverter.ToInt32(ba, 0);
ba = new byte[sz];
ms.Read(ba, 0, sz);
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream inms = new MemoryStream(ba))
{
info.SetValue(output, bf.Deserialize(inms));
}
}
}
}
元のstruct
に変換する場合は、単に長さを読み取り、BinaryFormatter
に直接ダンプして、struct
にダンプします。
これらの2つの関数は汎用であり、どのstruct
でも動作するはずです。サーバーとクライアントがあり、NamedPipeStream
を介して接続および通信するC#
プロジェクトで上記のコードをテストしました。 struct
をバイト配列として転送し、変換し直しました。
struct
自体の長さは固定されておらず、構造体にあるすべてのフィールドのオーバーヘッドはint
だけであるため、私のアプローチの方が良いと思います。 BinaryFormatter
によって生成されたバイト配列内には、わずかなオーバーヘッドもありますが、それ以外はそれほど多くありません。
BinaryReaderクラスとBinaryWriterクラスを見てみましょう。私は最近、データをバイト配列にシリアル化する必要がありました(そして元に戻しました)。
http://msdn.Microsoft.com/en-us/library/system.io.binarywriter.aspx
そのページにも良い例があります。
外部ライブラリの定義済み(Cレベル)構造のように見えます。元sはあなたの友達です。小切手:
http://geekswithblogs.net/taylorrich/archive/2006/08/21/88665.aspx
手始めにこれに対処する方法。属性を使用して、バイトレイアウトや文字列処理などを定義できることに注意してください。実際、非常に素晴らしいアプローチです。
そのためにBinaryFormatterもMemoryStreamも行われません。
@Abdel Olakaraの回答完成版は.net 3.5では機能しません。以下のように修正する必要があります。
public static void ByteArrayToStructure<T>(byte[] bytearray, ref T obj)
{
int len = Marshal.SizeOf(obj);
IntPtr i = Marshal.AllocHGlobal(len);
Marshal.Copy(bytearray, 0, i, len);
obj = (T)Marshal.PtrToStructure(i, typeof(T));
Marshal.FreeHGlobal(i);
}
この例は、純粋なblittable型、たとえばCで直接memcpy'dできる型にのみ適用できます。
例-既知の64ビット構造体
[StructLayout(LayoutKind.Sequential)]
public struct Voxel
{
public ushort m_id;
public byte m_red, m_green, m_blue, m_alpha, m_matid, m_custom;
}
このように定義すると、構造体は自動的に64ビットとしてパックされます。
ボクセルのボリュームを作成できます:
Voxel[,,] voxels = new Voxel[16,16,16];
そして、それらをすべてバイト配列に保存します。
int size = voxels.Length * 8; // Well known size: 64 bits
byte[] saved = new byte[size];
GCHandle h = GCHandle.Alloc(voxels, GCHandleType.Pinned);
Marshal.Copy(h.AddrOfPinnedObject(), saved, 0, size);
h.Free();
// now feel free to save 'saved' to a File / memory stream.
ただし、OPは構造体自体の変換方法を知りたいため、Voxel構造体は次のメソッドToBytes
を持つことができます。
byte[] bytes = new byte[8]; // Well known size: 64 bits
GCHandle h = GCHandle.Alloc(this, GCHandleType.Pinned);
Marshal.Copy(hh.AddrOfPinnedObject(), bytes, 0, 8);
h.Free();
Header header = new Header();
Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);
これですぐにうまくいくはずですよね?