Frage

Ich ordne in meiner Anwendung etwas nicht verwalteten Speicher zu Marshal.AllocHGlobal.Anschließend kopiere ich eine Reihe von Bytes an diesen Speicherort und konvertiere das resultierende Speichersegment in ein struct bevor Sie den Speicher über wieder freigeben Marshal.FreeHGlobal.

Hier ist die Methode:

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;
}

Dies funktioniert jedoch größtenteils, wenn ich weniger Bytes als die Größe habe struct erfordert, dann werden den letzten Feldern 'zufällige' Werte zugewiesen (ich verwende LayoutKind.Sequential auf der Zielstruktur).Ich möchte diese hängenden Felder so effizient wie möglich beseitigen.

Zum Kontext deserialisiert dieser Code hochfrequente Multicast-Nachrichten, die von C++ unter Linux gesendet werden.

Hier ist ein fehlgeschlagener Testfall:

// 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;
}

Das wiederholte Ausführen dieses Tests führt dazu, dass die zweite Behauptung jedes Mal mit einem anderen Wert fehlschlägt.


BEARBEITEN

Am Ende habe ich verwendet Leppies Vorschlag des Gehens unsafe und nutzen stackalloc.Dadurch wurde ein Byte-Array zugewiesen, das nach Bedarf auf Null gesetzt wurde, und der Durchsatz wurde je nach Nachrichtengröße um 50 % bis 100 % verbessert (größere Nachrichten hatten einen größeren Nutzen).

Die endgültige Methode ähnelte letztendlich der folgenden:

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));
    }   
}

Leider ist hierfür immer noch ein Anruf erforderlich Marshal.PtrToStructure um die Bytes in den Zieltyp zu konvertieren.

War es hilfreich?

Lösung

Warum nicht einfach prüfen, ob start + length ist drinnen typesize?

Übrigens:Ich würde einfach gehen unsafe hier und verwenden Sie eine for-Schleife, um den zusätzlichen Speicher auf Null zu setzen.

Auch das bringt Ihnen Vorteile stackalloc Das ist viel sicherer und schneller als AllocGlobal.

Andere Tipps

[DllImport("kernel32.dll")]
static extern void RtlZeroMemory(IntPtr dst, int length);
...
RtlZeroMemory(targetBytes, typeSize);

Dies wird unter Windows gut funktionieren:

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);
    }
}

Ja als Jon Seigel sagte, Sie können es mit Marshal.WriteByte auf Null setzen

Im folgenden Beispiel setze ich den Puffer auf Null, bevor ich die Struktur kopiere.

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); 

Ich habe so etwas noch nie in C# gemacht, aber ich habe Marshal.WriteByte(IntPtr, Int32, Byte) in MSDN gefunden.Probieren Sie das aus.

for(int i=0; i < buffSize / 8; i += 8 )
{
    Marshal.WriteInt64(buffer, i, 0x00);
}

for(int i= buffSize % 8 ; i < -1 ; i-- )
{
    Marshal.WriteByte (buffer, buffSize - i, 0x00);
}

Ich denke, Sie werden feststellen, dass es mit 64-Bit-Wrights um ein Vielfaches schneller geht als mit 8-Bit-Wrights (die Sie noch für die letzten paar Bytes benötigen).

Ich denke, der beste Weg, einen Puffer auf Null zu setzen, ist dieser, wenn Sie nicht in die andere Richtung gehen möchten oder können:

for(int i=0; i<buffSize; i++)
{
    Marshal.WriteByte(buffer, i, 0x00);
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top