0

I'm writing an UnmanagedRewindBuffer and I want to implement dynamic resizing of the buffer. I've tried several different things, but I can't seem to be able to get it right. The basic idea is that:

  1. I allocate a new block of unmanaged memory.
  2. Create a new UnmanagedMemoryStream (UMS).
  3. Copy the contents from the old UMS to the new UMS.
  4. Dispose of the old UMS and free the old allocated block.
  5. Replace the old UMS and memory block with the new ones.

Here is my resize function:

private void DynamicallyResizeBuffer(long spaceNeeded)
{
    while (_ums.Length < spaceNeeded)
    {
        // Allocate a new buffer
        int length = (int)((double)spaceNeeded * RESIZE_FACTOR);
        IntPtr tempMemoryPointer = Marshal.AllocHGlobal(length);

        // Set the temporary pointer to null
        //MemSet(tempMemoryPointer, length, 0);

        byte* bytePointer = (byte*)tempMemoryPointer.ToPointer();
        for (int i = 0; i < length; i++)
        {
            *(bytePointer + i) = 0;
        }

        // Copy the data
        // MoveMemory(bytePointer, _memoryPointer.ToPointer(), _length);

        // Create a new UnmanagedMemoryStream
        UnmanagedMemoryStream tempUms = new UnmanagedMemoryStream(bytePointer, length, length, FileAccess.ReadWrite);

        // Set up the reader and writers
        BinaryReader tempReader = new BinaryReader(tempUms);
        BinaryWriter tempWriter = new BinaryWriter(tempUms);

        // Copy the data
        _ums.Position = 0;
        tempWriter.Write(ReadBytes(_length));

        // I had deleted this line while I was using the writers and 
        // I forgot to copy it over, but the line was here when I used 
        // the MoveMemory function
        tempUms.Position = _ums.Position;
        // Free the old resources
        Free(true);

        _ums = tempUms;
        _reader = tempReader;
        _writer = tempWriter;
        _length = length;
    }
}

And here is my test for resizing:

public void DynamicResizeTest()
{
    Int32 expected32 = 32;
    Int32 actual32 = 0;
    UInt64 expected64 = 64;
    UInt64 actual64 = 0;
    string expected = "expected";
    string actual = string.Empty;
    string actualFromBytes = string.Empty;
    byte[] expectedBytes = Encoding.UTF8.GetBytes(expected);

    // Create an 4 byte buffer
    UnmanagedRewindBuffer ubs = null;
    try
    {
        ubs = new UnmanagedRewindBuffer(4, 1);
        ubs.WriteInt32(expected32);

        // should dynamically resize for the 64 bit integer
        ubs.WriteUInt64(expected64);

        ubs.WriteString(expected);

        // should dynamically resize for the bytes
        ubs.WriteByte(expectedBytes);

        ubs.Rewind();
        actual32 = ubs.ReadInt32();
        actual64 = ubs.ReadUInt64();
        actual = ubs.ReadString();
        actualFromBytes = Encoding.UTF8.GetString(ubs.ReadBytes(expected.Length));
    }
    finally
    {
        if (ubs != null)
        {
            ubs.Clear();
            ubs.Dispose();
        }
        ubs = null;
    }

    Assert.AreEqual(expected32, actual32);
    Assert.AreEqual(expected64, actual64);
    Assert.AreEqual(expected, actual);
    Assert.AreEqual(expected, actualFromBytes);
}

I've tried calling MoveMemory, which is just an unsafe extern to the kernel32 RtlMoveMemory, but when I run the test I get the following results:

actual32 is 32, expected 32
actual64 is 0, expected 64
actual is "", expected "expected"
actualFromBytes is some gibberish, expected "expected"

When I use the reader/writer to directly read from the old UMS to the new UMS, I get the following results:

actual32 is 32, expected 32
actual64 is 64, expected 64
actual is "", expected "expected"
actualFromBytes is "\b\0expect", expected "expected"

If I allocate enough space right from the start, then I have no issues with reading the values and I get the correct expected results.

What's the right way to copy the data?

Update:
Per Alexi's comment, here is the Free method which disposes of the reader/writer and the UnmanagedMemoryStream:

private void Free(bool disposeManagedResources)
{
    // Dispose unmanaged resources
    Marshal.FreeHGlobal(_memoryPointer);

    // Dispose managed resources. Should not be called from destructor.
    if (disposeManagedResources)
    {
        _reader.Close();
        _writer.Close();
        _reader = null;
        _writer = null;
        _ums.Dispose();
        _ums = null;
    }
}
Kiril
  • 39,672
  • 31
  • 167
  • 226
  • Why don't you use a MemoryStream? – dtb Jun 08 '11 at 15:53
  • @dtb I need to have control over the memory allocation and I'm also using a C++ dll which takes in a pointer and reads data from it. I fill up the buffer with data, take the pointer, send it to the C++ dll and it does some things with it. – Kiril Jun 08 '11 at 16:02

2 Answers2

2

You forgot this assignment:

_memoryPointer = tempMemoryPointer;

That can go unnoticed for a while, _memoryPointer is pointing to a released memory block that still contains the old bytes. Until the Windows heap manager re-uses the block or your code overwrites memory owned by another allocation. Exactly when that happens is unpredictable. You can take "unsafe" in the class name quite literally here.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • BINGO! Thanks Hans! I can't believe I missed it... I've been stuck on this for a day!!! – Kiril Jun 08 '11 at 17:46
0

First guess - you are not disposing StreamWriter - data may not be commited to the underlying stream. You also may be missing code that updates position in your UnmanagedRewindBuffer...

Second guess: reader created on wrong stream.

Note: Consider using Stream.CopyTo (.Net 4 - http://msdn.microsoft.com/en-us/library/system.io.stream.copyto.aspx) to copye the stream. For 3.5 check How do I copy the contents of one stream to another? .

Community
  • 1
  • 1
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • @Alexi, sorry, I had deleted the code that updates the position for the reader/writer copy attempt (although it doesn't affect it) and I forgot to post it. However, I did update the position when I used the `MoveMemory` function. I'm also using .NET 3.5 at the moment. – Kiril Jun 08 '11 at 16:19
  • BinaryReader tempReader = new BinaryReader(tempUms); - should not it be BinaryReader tempReader = new BinaryReader(_ums); ? – Alexei Levenkov Jun 08 '11 at 16:42
  • I'm disposing of the _ums, so I can't make a copy of that reference (which would happen if I passed it into the tempReader). I copy all the contents from _ums to tempUms, so it should now contain the all the data and the reader should be fine. – Kiril Jun 08 '11 at 16:48
  • +1 to Hans Passant as it looks like an answer and likley MoveMemory would work. Note that useing reader and writer on the same stream is dangerous in general as they share position. You could have used direct Read/Write on streams instead. – Alexei Levenkov Jun 08 '11 at 16:56