读取共享内存时出现堆栈溢出错误(C#)

3
以下代码会产生堆栈溢出错误。它创建了一个共享内存空间,然后试图将共享内存内容复制到本地缓冲区。我已经编写了几个使用非托管C++来完成此操作的程序,但C#对我来说是陌生的...我在堆上分配了一个缓冲区,并试图将共享内存缓冲区复制到我的本地缓冲区。这就是触发堆栈溢出错误的地方:accessor.Read<my_struct>(0, out ps.hi);。也许accessor.Read函数在将其复制到我提供的引用之前尝试创建共享内存缓冲区的本地副本?如果是这样,那么在C#中传输大内存块的推荐方法是什么?我在我的互联网搜索中没有找到这个问题,所以任何帮助都将不胜感激...
确切的错误消息为:"mscorlib.dll中发生了未处理的类型为'System.StackOverflowException'的异常"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
namespace ConsoleApplication2
{
unsafe struct my_struct
{
    public fixed UInt16 img[1280 * 960];
}
class Program
{
    my_struct hi;
    static void Main(string[] args)
    {
        Program ps = new Program();
        ps.hi = new my_struct();

        using (var mmf = MemoryMappedFile.CreateOrOpen("OL_SharedMemSpace", System.Runtime.InteropServices.Marshal.SizeOf(ps.hi)))
        {
            using (var accessor = mmf.CreateViewAccessor())
            {
                //Listen to event...
                EventWaitHandle request_event;
                EventWaitHandle ready_event;
                try
                {
                    request_event = EventWaitHandle.OpenExisting("OL_ReceiveEvent");
                }
                catch (WaitHandleCannotBeOpenedException)
                {
                    Console.WriteLine("Receive event does not exist...creating one.");
                    request_event = new EventWaitHandle(false, EventResetMode.AutoReset, "OL_ReceiveEvent");
                }
                try
                {
                    ready_event = EventWaitHandle.OpenExisting("OL_ReadyEvent");
                }
                catch (WaitHandleCannotBeOpenedException)
                {
                    Console.WriteLine("Ready event does not exist...creating one.");
                    ready_event = new EventWaitHandle(false, EventResetMode.AutoReset, "OL_ReceiveEvent");
                }
                System.Console.WriteLine("Ok...ready for commands...");
                while (true)
                {
                    accessor.Read<my_struct>(0, out ps.hi);
                    request_event.WaitOne();
                }
            }
        }
    }
}

}


1
使用ReadArray<uint>并在堆上分配数组。 - Eric Lippert
2个回答

2
也许这会有所帮助。这是C++本地编写器:
#include <windows.h>
#include <memory>

const int BUFFER_SiZE = sizeof(uint8_t) * 1024 * 1024;
const wchar_t MMF_NAME[] = L"Global\\SharedMemoryExample";

int main()
{
    wprintf(L"Shared Memory example. Native writer\r\n"); 
    wprintf(L"BUFFER_SIZE: %d bytes\r\n", BUFFER_SiZE);
    wprintf(L"MMF name: %s\r\n", MMF_NAME);

    HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUFFER_SiZE, MMF_NAME);
    if (hMapFile == NULL)
    {
        wprintf(L"CreateFileMapping failed with error: %d", GetLastError());
        return -1;
    }
    std::shared_ptr<void> mapHandleGuard(hMapFile, &::CloseHandle);
    uint8_t* pBuffer = (uint8_t*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFFER_SiZE);
    if (pBuffer == NULL)
    {
        wprintf(L"MapViewOfFile failed with error: %d", GetLastError());
        return -2;
    }
    std::shared_ptr<void> bufferGuard(pBuffer, &::UnmapViewOfFile);

    wprintf(L"Press 'Enter' to write some data to shared memory");
    getchar();

    // Write magic data :)
    memset(pBuffer, 0xFA, BUFFER_SiZE);

    wprintf(L"Press 'Enter' close application");
    getchar();

    return 0;
}

这里是.NET阅读器:

class Program
    {
        private const string MMF_NAME = "Global\\SharedMemoryExample";
        const int BUFFER_SIZE = sizeof(byte) * 1024 * 1024;

        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Shared Memory example. .NET reader");
                Console.WriteLine("BUFFER_SIZE: {0} bytes", BUFFER_SIZE);
                Console.WriteLine("MMF name: {0}", MMF_NAME);

                Console.WriteLine("Press 'Enter' to open Shared memory");
                Console.ReadLine();

                using (var mmf = System.IO.MemoryMappedFiles.MemoryMappedFile.OpenExisting(MMF_NAME))
                {
                    Console.WriteLine("{0} was opened.", MMF_NAME);

                    using (var accessor = mmf.CreateViewAccessor())
                    {
                        Console.WriteLine("ViewAccessor was created.");

                        byte[] buffer = new byte[BUFFER_SIZE];

                        Console.WriteLine("Press 'Enter' to read from Shared memory");
                        Console.ReadLine();

                        int cntRead = accessor.ReadArray(0, buffer, 0, BUFFER_SIZE);
                        Console.WriteLine("Read {0} bytes", cntRead);

                        if (IsBufferOk(buffer, cntRead))
                            Console.WriteLine("Buffer is ok");
                        else
                            Console.WriteLine("Buffer is bad!");
                    }
                }
            }
            catch(Exception  ex)
            {
                Console.WriteLine("Got exception: {0}", ex);
                Console.ReadLine();
            }
        }
        private static bool IsBufferOk(byte[] buffer, int length)
        {
            for(int i = 0; i < length; i++)
            {
                if (buffer[i] != 0XFA)
                    return false;
            }
            return true;
        }
    }

为了简化,我使用控制台输入进行同步。此外,如果您的操作系统>= Windows Vista,则应在提升的命令提示符下运行这些应用程序。


1
你的ps.hi = new my_struct();没有将数据放在堆上。
C#结构体始终是值类型,这里是一个非常大的值类型。太大了。在2*1280*960的情况下,它会超出默认的1MB堆栈大小。
我不熟悉MMF API,但看起来你应该能够将其读取为uint[]数组,而不需要周围的结构体。

如果它不在堆上,那么一旦我分配它,我不会得到堆栈溢出错误吗?如果我注释掉accessor.Read<my_struct>(0, out ps.hi),就没有错误了。我可能需要更多的澄清。关于使用uint[],是的,你是对的,虽然我发送到共享内存的实际结构体有一些元数据与之相关联,所以它不仅仅是图像。 - MJPD
很可能调用 Read<T> 在其堆栈上创建了一个类型为 T 的临时变量,填充该临时变量,然后将结果复制到 out 参数。这肯定会导致堆栈溢出。 - Eric Lippert
1
我还注意到规范说明new运算符在本地存储上创建值类型,然后将其复制到赋值左侧标识的位置,因此运行时有权在new上中断。但是,运行时不一定要在new上中断 - C#编译器在许多情况下都会进行复制省略; 在这里很可能也是这样做的。 - Eric Lippert
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - H H
1
@HenkHolterman:好的,我刚刚尝试了一下,你是对的。现在我不需要使用unsafe标志进行编译了! - MJPD
显示剩余4条评论

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