C#如何使用Memory<T>或ArraySegment<T>访问不受托管的数组?

15

在C# 7.2中引入了MemorySpanArraySegment,我想知道是否可以将非托管数组表示为一个生存在堆上的可枚举对象。

后面这个要求排除了Span,它基本上实现了我想要的功能,例如:

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

是否有可能使用ArraySegmentMemory来实现相同的功能?它们的构造函数只接受byte[],也许有一些方法可以欺骗C#,传递byte*而不是byte[]


你为什么想要使用 SpanMemory 来做这件事呢?你可以使用 Marshal.GlobalHAlloc 获取一个指向一组非托管内存的 IntPtr,直接对其进行操作。你可能可以将其转换为一个 byte[],然后传递给那些对象。你是想创建一个新的内存块来操作,还是试图访问另一个进程的内存块? - Ron Beyer
我正在使用SkiaSharp加载图像,逐字节迭代。目前,我使用提供的属性将数据复制为byte[]以进行处理,但是Skia还提供了指向非托管内存的本机指针,我想探索它,因为它可以节省内存复制。 - Red Riding Hood
@RonBeyer Memory<byte> 对于这个任务来说实际上是完美的选择... - Marc Gravell
@MarcGravell,现在我明白了使用情况,我同意你的看法。但是我之前不知道自定义的MemoryManager<T>,谢谢! - Ron Beyer
1个回答

30

对于Memory<T>,是需要创建自己的MemoryManager<T>。不要担心-这并没有听起来那么可怕- 这是我之前写的一个例子...:

/// <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) { }
}

现在您可以使用:
var mgr = new UnmanagedMemoryManager((byte*)ptr + (index * Width), Width);
Memory<byte> memory = mgr.Memory;

并且 memory 可以存储在堆上。

然而,为了最小化分配,您可能想要创建一个 单个 UnmanagedMemoryManager<byte> ,它覆盖整个区域 - 仅一次 - 然后使用表示整个区域的.Memory 上的.Slice(...)。这样,您就有了一个对象和很多切片(这些切片是结构体,不是对象)。

请注意,此实现假定您将在其他地方控制内存的生命周期 - 此处的Dispose() 不会 尝试通过Marshal等方式释放内存。


15
"UnmanagedMemoryManager" - 如果我见过的话,这是一个悖论被合理利用的例子。 - AaronLS
@AaronLS 我同意这似乎有点自相矛盾。 - Marc Gravell
3
也许它需要一个经理,因为它是“未受管理的”?;-) - 41686d6564 stands w. Palestine
@MarcGravell 这太棒了。我需要做什么特别的事情才能使用它?我得到了 The type or namespace name 'unmanaged' could not be found。已经导入了 System.BuffersInteropServices - Red Riding Hood
我移除了 unmanaged 接口和所有泛型,现在它可以工作了。 - Red Riding Hood
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接