将C#的void*转换为byte[]

5
在C#中,我需要将T[]写入流中,最好不使用任何额外的缓冲区。我有一个动态代码,可以将T[](其中T是一个无对象结构)转换为void*并将其固定在内存中,这很好用。当流是文件时,我可以使用本机Windows API直接传递void *,但现在我需要写入一个接受byte[]的通用Stream对象。
问题:有人能建议一种hack方法来创建一个虚拟数组对象,它实际上没有任何堆分配,而是指向已经存在(并固定的)堆位置吗?
以下是我需要的伪代码:
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[]的引用(指针)作为byte[]传递可能行不通,因为块的元数据仍然是T[]的元数据。即使元数据的结构相同,T[]的长度也远小于byte[],因此任何由托管代码对byte[]的后续访问都会生成不正确的结果。 请求微软连接的功能 请投票支持此请求,希望微软能听取您的声音。

2
你应该查看BinarySerializer类。 - Joel Coehoorn
1
@Joel - 如果你指的是BinaryFormatter,我会强烈反对它;-p - Marc Gravell
4个回答

3
这种代码无法以通用的方式工作。它依赖于一个硬性假设,即T的内存布局是可预测和一致的。只有当T是一个简单的值类型时才成立。暂时忽略字节序。如果T是引用类型,那么你就陷入了困境,因为你将复制无法反序列化的跟踪句柄,你必须给T添加结构约束。
但这还不够,结构类型也无法被复制。即使它们没有引用类型字段,这是你无法限制的。内部布局是由JIT编译器确定的。它随意交换字段,选择一个字段,使得字段正确对齐且结构值占用最小的存储空间。你将序列化的值只能被具有完全相同的CPU架构和JIT编译器版本的程序正确读取。
框架中已经有很多类可以完成你正在做的事情。最接近的匹配是.NET 4.0 MemoryMappedViewAccessor类。它需要完成相同的任务,使原始字节在内存映射文件中可用。那里的工作马是System.Runtime.InteropServices.SafeBuffer类,请使用Reflector查看。不幸的是,你不能只复制这个类,因为它依赖于CLR进行转换。不过,再等一个星期就可以用了。

Nobugz,感谢您提供的所有4.0信息 - 我会深入研究。至于T-我的代码仅检查结构体,不涉及引用类型,并且使用显式或顺序包1结构体。这应该解决了您提出的所有问题。请查看http://code.google.com/p/timeseriesdb/以获取工作实现。 - Yuri Astrakhan
如果这已经起作用了,那问题的意义在哪里?是垃圾邮件吗? - Hans Passant
不,当然不可以 :). 我只能在堆中修复T类型的数组并获得一个void*指针,这在处理内存映射文件或其他win API时非常好用。但它不能与任何需要byte[]类型的方法一起使用。 - Yuri Astrakhan

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

Jeff,Marshal.Copy非常慢,而且很容易避免。我在http://www.codeproject.com/KB/cs/ReadingStructuresEmit.aspx的文章中进行了详细描述。我可以随时创建一个byte[],并使用我的方法从一个固定指针快速复制字节到另一个指针,然后将该数组发送到流中,但我希望避免不必要的分配和复制。 - Yuri Astrakhan
编辑过了,虽然我不确定逐字节写入的性能如何。 - jjxtra
对于.NET 4.0,我会选择nobugz的答案,否则在.NET 4.0之前,我认为这可能是你能得到的最好的了... - jjxtra

0

请查看我对相关问题的回答: 如何将float[]转换为byte[]的最快方法是什么?

在我的回答中,我使用内存操作临时将浮点数数组转换为字节数组,而无需进行内存分配和复制。 为此,我通过内存操作更改了CLR的元数据。

不幸的是,这种解决方案不适用于泛型。但是,您可以将此技巧与代码生成技术相结合来解决您的问题。


Omer:你的技巧在这里行不通,因为它需要元数据就位。如果你有一个指向内存映射文件的void*,没有修改文件内容就无法在那里获取元数据。 - Gabe

0

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