3

The following code produces a stack overflow error. It creates a shared memory space and then attempts to copy the shared memory contents to a local buffer. I have written several programs to do this in unmanaged C++, but C# is foreign to me... I have allocated a buffer on the heap and am attempting to copy the shared memory buffer into my local buffer. This is where the stack overflow error triggers: accessor.Read<my_struct>(0, out ps.hi);. Perhaps the accessor.Read function attempts to create a local copy of the shared memory buffer before copying it into the reference I provide it? If so, what is the recommended way to transfer large memory chunks to and from shared memory in C#? I have not found this issue in my internet searches so any help would be appreciated...

The exact error message reads: "An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll"

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

}

MJPD
  • 33
  • 3

2 Answers2

2

Maybe it can be helpful. Here is C++ native writer:

#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;
}

And here is .NET reader:

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

For simplifying I used console input for synchronization. Also if your OS >= Windows Vista, you should run these apps under elevated command prompt.

Community
  • 1
  • 1
Artavazd Balayan
  • 2,353
  • 1
  • 16
  • 25
1

Your ps.hi = new my_struct(); is not placing the data on the heap.

A C# struct is always a value type, in this case a very big one. Too big.
At 2*1280*960 it will overrun the default 1MB stack.

I'm not familiar with the MMF API but it looks like you should be able to read it as a uint[] array without the surrounding struct.

H H
  • 263,252
  • 30
  • 330
  • 514
  • If it wasn't on the heap, wouldn't I get a stack overflow error once I allocated it? If I comment out `accessor.Read(0, out ps.hi)'`, there is no error. I might need more clarification on this. Concerning using uint[], yes your right I can although the actual struct I am sending over shared memory has some meta data associated with it, so it won't just be the image. – MJPD Jun 06 '16 at 17:40
  • I'm sure it's on the stack. The timing may be due to the optimizer moving/delaying the allocation. I'm not sure what effects `out` has here. – H H Jun 06 '16 at 17:45
  • Likely the call to `Read` is creating a temporary of type T on its stack, filling in the temporary, and then copying the result to the out parameter. That could certainly blow the stack. – Eric Lippert Jun 06 '16 at 17:53
  • @MJPD: Regardless of what is blowing the stack, the best practice here is to ensure that structs are less than 16 bytes, and yours is certainly larger than that! If you need a large pinned block of memory then allocate an array and have the GC pin it. – Eric Lippert Jun 06 '16 at 17:53
  • 1
    I note also that the specification states that the `new` operator creates the value type on the local storage and then copies it to the location identified by the left side of the assignment, so the runtime would be within its rights to blow up on the `new`. However it is not *required* to blow up on the `new` -- the C# compiler does copy elision in many cases; it is likely doing so here. – Eric Lippert Jun 06 '16 at 17:56
  • I allocated an array of bytes and performed the shared memory transfer within a `fixed` block using `ReadArray`. This does not give the stackoverflow error. Thank you both for your help. – MJPD Jun 06 '16 at 18:57
  • I don't think you have to pin (fix) the data when calling ReadArray<>. It's a managed wrapper. – H H Jun 06 '16 at 19:54
  • @HenkHolterman: That's correct. It was not clear to me from my first read of the question whether the OP was looking for a fixed-in-place block or a fixed-in-size block or both. – Eric Lippert Jun 06 '16 at 19:57
  • 1
    @HenkHolterman: Ok, I just tried it without and your right. And now I don't need to compile with the `unsafe` flag! – MJPD Jun 07 '16 at 12:48