5

在 C# 中,我需要将 T[] 写入流,理想情况下不需要任何额外的缓冲区。我有一个动态代码将 T[] (其中 T 是一个无对象结构)转换为 void* 并将其修复在内存中,效果很好。当流是文件时,我可以使用本机 Windows API 直接传递 void *,但现在我需要写入一个接受 byte[] 的通用 Stream 对象。

问题:任何人都可以建议一种黑客方法来创建一个实际上没有任何堆分配的虚拟数组对象,而是指向一个已经存在(和固定)的堆位置?

这是我需要的伪代码:

void Write(Stream stream, T[] buffer)
{
    fixed( void* ptr = &buffer )    // done with dynamic code generation
    {
        int typeSize = sizeof(T);   // done as well

        byte[] dummy = (byte[]) ptr;   // <-- how do I create this fake array?

        stream.Write( dummy, 0, buffer.Length*typeSize );
    }
}  

更新:fixed(void* ptr=&buffer)我在这篇文章中 详细描述了如何做。我总是可以创建一个字节 [],在内存中修复它并从一个指针到另一个指针进行不安全的字节复制,然后将该数组发送到流,但我希望避免不必要的额外分配和复制。

不可能的? 经过进一步思考,byte[] 在堆中有一些元数据,其中包含数组维度和元素类型。简单地将引用(指针)作为 byte[] 传递给 T[] 可能不起作用,因为块的元数据仍然是 T[] 的元数据。而且即使元数据的结构相同,T[] 的长度也会比 byte[] 小很多,因此托管代码对 byte[] 的任何后续访问都会产生错误的结果。

功能要求@Microsoft Connect 请投票支持此请求,希望 MS 会倾听。

4

4 回答 4

3

这种代码永远不能以通用方式工作。它依赖于一个硬假设,即 T 的内存布局是可预测且一致的。仅当 T 是简单值类型时才如此。暂时忽略字节顺序。如果 T 是引用类型,您将死在水中,您将复制永远无法反序列化的跟踪句柄,您必须给 T 结构约束。

但这还不够,结构类型也不可复制。即使它们没有引用类型字段,您也无法限制。内部布局由 JIT 编译器确定。它随意交换字段,选择一个字段正确对齐且结构值占用最小存储大小的字段。您将序列化的值只能由以完全相同的 CPU 架构和 JIT 编译器版本运行的程序正确读取。

框架中已经有很多类可以做你正在做的事情。最接近的匹配是 .NET 4.0 MemoryMappedViewAccessor 类。它需要做同样的工作,在内存映射文件中提供原始字节。那里的主力是 System.Runtime.InteropServices.SafeBuffer 类,看看反射器。不幸的是,您不能只复制该类,它依赖于 CLR 进行转换。再说一次,距离它可用只有一周的时间。

于 2010-04-03T14:05:10.533 回答
0

Check out my answer to a related question: What is the fastest way to convert a float[] to a byte[]?

In it I temporarily transform an array of floats to an array of bytes without memory allocation and copying. To do this I changed the CLR's metadata using memory manipulation.

Unfortunately, this solution does not lend itself well to generics. However, you can combine this hack with code generation techniques to solve your problem.

于 2010-10-28T04:42:06.967 回答
0

因为stream.Write不能带指针,所以无法避免复制内存,所以会有些慢。您可能需要考虑使用 BinaryReader 和 BinaryWriter 来序列化您的对象,但这里的代码可以让您做您想做的事。请记住, T 的所有成员也必须是结构。

unsafe static void Write<T>(Stream stream, T[] buffer) where T : struct
{
    System.Runtime.InteropServices.GCHandle handle = System.Runtime.InteropServices.GCHandle.Alloc(buffer, System.Runtime.InteropServices.GCHandleType.Pinned);
    IntPtr address = handle.AddrOfPinnedObject();
    int byteCount = System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)) * buffer.Length;
    byte* ptr = (byte*)address.ToPointer();
    byte* endPtr = ptr + byteCount;
    while (ptr != endPtr)
    {
        stream.WriteByte(*ptr++);
    }
    handle.Free();
}
于 2010-04-03T03:12:36.973 回答
0

看这篇文章Inline MSIL in C#/VB.NET and Generic Pointers the best way to get dream code :)

于 2012-09-26T21:12:36.687 回答