18

経由でアプリケーションにアンマネージ メモリを割り当てていますMarshal.AllocHGlobal。次に、一連のバイトをこの場所にコピーし、結果のメモリ セグメントを に変換してから、structを介してメモリを再度解放しますMarshal.FreeHGlobal

メソッドは次のとおりです。

public static T Deserialize<T>(byte[] messageBytes, int start, int length)
    where T : struct
{
    if (start + length > messageBytes.Length)
        throw new ArgumentOutOfRangeException();

    int typeSize = Marshal.SizeOf(typeof(T));
    int bytesToCopy = Math.Min(typeSize, length);

    IntPtr targetBytes = Marshal.AllocHGlobal(typeSize);
    Marshal.Copy(messageBytes, start, targetBytes, bytesToCopy);

    if (length < typeSize)
    {
        // Zero out additional bytes at the end of the struct
    }

    T item = (T)Marshal.PtrToStructure(targetBytes, typeof(T));
    Marshal.FreeHGlobal(targetBytes);
    return item;
}

これはほとんどの場合機能しますが、struct必要なサイズよりもバイト数が少ない場合、最後のフィールドに「ランダムな」値が割り当てられます(LayoutKind.Sequentialターゲット構造体で使用しています)。これらのぶら下がっているフィールドをできるだけ効率的にゼロにしたいと思います。

コンテキストとして、このコードは Linux 上の C++ から送信された高頻度のマルチキャスト メッセージを逆シリアル化しています。

失敗したテスト ケースを次に示します。

// Give only one byte, which is too few for the struct
var s3 = MessageSerializer.Deserialize<S3>(new[] { (byte)0x21 });
Assert.AreEqual(0x21, s3.Byte);
Assert.AreEqual(0x0000, s3.Int); // hanging field should be zero, but isn't

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct S3
{
    public byte Byte;
    public int Int;
}

このテストを繰り返し実行すると、2 番目のアサートが毎回異なる値で失敗します。


編集

最後に、私はleppie の提案である をunsafe使用しstackallocました。これにより、必要に応じてゼロに設定されたバイト配列が割り当てられ、メッセージ サイズに応じてスループットが 50% から 100% の間で改善されました (メッセージが大きいほどメリットが大きくなります)。

最終的な方法は次のようになりました。

public static T Deserialize<T>(byte[] messageBytes, int startIndex, int length)
    where T : struct
{
    if (length <= 0)
        throw new ArgumentOutOfRangeException("length", length, "Must be greater than zero.");
    if (startIndex < 0)
        throw new ArgumentOutOfRangeException("startIndex", startIndex, "Must be greater than or equal to zero.");
    if (startIndex + length > messageBytes.Length)
        throw new ArgumentOutOfRangeException("length", length, "startIndex + length must be <= messageBytes.Length");

    int typeSize = Marshal.SizeOf(typeof(T));
    unsafe
    {
        byte* basePtr = stackalloc byte[typeSize];
        byte* b = basePtr;
        int end = startIndex + Math.Min(length, typeSize);
        for (int srcPos = startIndex; srcPos < end; srcPos++)
            *b++ = messageBytes[srcPos];
        return (T)Marshal.PtrToStructure(new IntPtr(basePtr), typeof(T));
    }   
}

Marshal.PtrToStructure残念ながら、これには、バイトをターゲット型に変換するための呼び出しが必要です。

4

8 に答える 8

18
[DllImport("kernel32.dll")]
static extern void RtlZeroMemory(IntPtr dst, UIntPtr length);
...
RtlZeroMemory(targetBytes, typeSize);
于 2009-09-28T13:35:50.267 に答える
6

これは Windows で正常に動作します。

namespace KernelPInvoke
{
    /// <summary>
    /// Implements some of the C functions declared in string.h
    /// </summary>
    public static class MemoryWrapper
    {
        [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
        static extern void CopyMemory(IntPtr destination, IntPtr source, uint length);

        [DllImport("kernel32.dll", EntryPoint = "MoveMemory", SetLastError = false)]
        static extern void MoveMemory(IntPtr destination, IntPtr source, uint length);

        [DllImport("kernel32.dll", EntryPoint = "RtlFillMemory", SetLastError = false)]
        static extern void FillMemory(IntPtr destination, uint length, byte fill);
    }

    var ptr = Marshal.AllocHGlobal(size);
    try
    {
        MemoryWrapper.FillMemory(ptr, size, 0);
        // further work...
    }
    finally
    {
        Marshal.FreeHGlobal(ptr);
    }
}
于 2011-11-19T21:48:40.453 に答える
3

start + length内にあるかどうかを確認してみませんtypesizeか?

ところで:私はunsafeここに行き、forループを使用して追加のメモリをゼロにします。

stackallocこれも、 よりもはるかに安全で高速なを使用する利点をもたらしますAllocGlobal

于 2009-09-28T13:32:08.507 に答える
3

はい、Jon Seigelが言ったように、Marshal.WriteByte を使用してゼロにすることができます

次の例では、構造体をコピーする前にバッファーをゼロにします。

if (start + length > messageBytes.Length) 
    throw new ArgumentOutOfRangeException();   
int typeSize = Marshal.SizeOf(typeof(T));    
int bytesToCopy = Math.Min(typeSize, length);   
IntPtr targetBytes = Marshal.AllocHGlobal(typeSize);  
//zero out buffer
for(int i=0; i < typeSize; i++)
{
    Marshal.WriteByte(targetBytes, i, 0);
}
Marshal.Copy(messageBytes, start, targetBytes, bytesToCopy); 
于 2009-09-28T13:34:26.703 に答える
2

これまで C# でこのようなことをしたことはありませんが、MSDN で Marshal.WriteByte(IntPtr, Int32, Byte) を見つけました。それを試してみてください。

于 2009-09-28T13:28:54.030 に答える
0

バッファーをゼロにする最良の方法は、必要ない場合、または別の方法にできない場合です。

for(int i=0; i<buffSize; i++)
{
    Marshal.WriteByte(buffer, i, 0x00);
}
于 2013-02-01T18:19:51.533 に答える