17

With the introduction of Memory, Span and ArraySegment in C# 7.2, I was wondering if I could represent an unmanaged array as an enumerable object, that lives on the heap.

This latter requirement rules out Span, which basically implemented exactly what I wanted: e.g.

unsafe { bytes = new Span<byte>((byte*)ptr + (index * Width), Width); 

Is it possible to do the same with ArraySegment or Memory? Their constructors only accept byte[], maybe there some way to trick C# into passing a byte* instead of byte[]?

4
  • Why do you want to do this with a Span or Memory? You can Marshal.GlobalHAlloc and get an IntPtr to work with a set of unmanaged memory directly. You may be able to somehow translate that into a byte[] that can be passed into those objects. Are you wanting to create a new memory block to work on, or are you trying to access another process memory block? Commented Sep 5, 2018 at 17:27
  • I'm using SkiaSharp to load images which I iterate over byte by byte. Currently I use a provided property which copies the data as byte[] to work with, but Skia also provides a native pointer to the unmanaged memory, which I want to explore because it saves me a memory copy. Commented Sep 5, 2018 at 17:33
  • @RonBeyer Memory<byte> is actually perfect for this... Commented Sep 5, 2018 at 18:49
  • @MarcGravell I agree now that I understand what the use case was, but I was unaware of the custom MemoryManager<T>, thanks! Commented Sep 5, 2018 at 18:50

1 Answer 1

35

Yes for Memory<T>, but you need to create your own MemoryManager<T>. Don't worry - this isn't as scary as it sounds - here's one I wrote earlier...:

/// <summary>
/// A MemoryManager over a raw pointer
/// </summary>
/// <remarks>The pointer is assumed to be fully unmanaged, or externally pinned - no attempt will be made to pin this data</remarks>
public sealed unsafe class UnmanagedMemoryManager<T> : MemoryManager<T>
    where T : unmanaged
{
    private readonly T* _pointer;
    private readonly int _length;

    /// <summary>
    /// Create a new UnmanagedMemoryManager instance at the given pointer and size
    /// </summary>
    /// <remarks>It is assumed that the span provided is already unmanaged or externally pinned</remarks>
    public UnmanagedMemoryManager(Span<T> span)
    {
        fixed (T* ptr = &MemoryMarshal.GetReference(span))
        {
            _pointer = ptr;
            _length = span.Length;
        }
    }
    /// <summary>
    /// Create a new UnmanagedMemoryManager instance at the given pointer and size
    /// </summary>
    public UnmanagedMemoryManager(T* pointer, int length)
    {
        if (length < 0) throw new ArgumentOutOfRangeException(nameof(length));
        _pointer = pointer;
        _length = length;
    }
    /// <summary>
    /// Obtains a span that represents the region
    /// </summary>
    public override Span<T> GetSpan() => new Span<T>(_pointer, _length);

    /// <summary>
    /// Provides access to a pointer that represents the data (note: no actual pin occurs)
    /// </summary>
    public override MemoryHandle Pin(int elementIndex = 0)
    {
        if (elementIndex < 0 || elementIndex >= _length)
            throw new ArgumentOutOfRangeException(nameof(elementIndex));
        return new MemoryHandle(_pointer + elementIndex);
    }
    /// <summary>
    /// Has no effect
    /// </summary>
    public override void Unpin() { }

    /// <summary>
    /// Releases all resources associated with this object
    /// </summary>
    protected override void Dispose(bool disposing) { }
}

Now you can use:

var mgr = new UnmanagedMemoryManager((byte*)ptr + (index * Width), Width);
Memory<byte> memory = mgr.Memory;

and memory can be stored on the heap.

However, to minimize allocations you probably want to create a single UnmanagedMemoryManager<byte> that covers the entire region - once only - and then use .Slice(...) on the .Memory that represents the entire region. That way you have a single object and lots of slices (the slices are structs, not objects).

Note this implementation assumes that you're going to control the lifetime of the memory elsewhere - the Dispose() here does not attempt to release the memory via Marshal etc.

Sign up to request clarification or add additional context in comments.

16 Comments

UnmanagedMemoryManager - A legitimate use of a paradox if I've ever seen one.
@AaronLS it does seem somewhat an oxymoron, I agree
@AaronLS Maybe it needs a manager because it's "unmanaged"? ;-)
@MarcGravell This is amazing. Is there anything special I need to do to use this? I get The type or namespace name 'unmanaged' could not be found. Already imported System.Buffers and InteropServices
I removed the unmanaged interface and all the generics and it is now working.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.