angie866
angie866

Reputation: 85

Read Memory Memory Mapped File C++ and C#

I am trying to share a structure coming from C++ to C# using memory mapped file. So far I managed to write on the file, but I am unable to read the content in C#.

  1. SendData in C++
struct Bus_1553 // this is the structure to send
{
    string name;
    int directions; 
};

struct Bus_1553* p_1553; // set the pointer to it
HANDLE handle; // create the handle


// here we define the data to send
string name = "IFF";
int directions = 3;

bool startShare() // Open the shared memory
{
    try
    {
        handle = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(Bus_1553), L"DataSend");
        p_1553 = (struct Bus_1553*) MapViewOfFile(handle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, sizeof(Bus_1553));
        return true;
    }
    catch (...)
    {
        return false;
    }

}


int main()
{

    if (startShare() == true)
    {

        while (true)
        {
            if (p_1553 != 0) // populate the memory
            {  

                p_1553->name = name;
                p_1553->directions = directions;
            }

            else
                puts("create shared memory error");
        }
    }
    if (handle != NULL)
        CloseHandle(handle);
    return 0;
} 
  1. Trying to read in C#
namespace sharedMemoryGET
{
    class sharedMemoryGET
    {
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public unsafe struct Bus_Data_1553
        {
            public string name;
            public int directions; // which directions used
        }

        public static MemoryMappedFile mmf;
        public static MemoryMappedViewStream mmfvs;

        static public bool MemOpen() // open the mapped file
        {
            try
            {
                mmf = MemoryMappedFile.OpenExisting("DataSend");
                return true;
            }
            catch
            {
                return false;
            }

        }

        public static void readData()
        {
            if (MemOpen())
                {
                    using (var accessor = mmf.CreateViewAccessor())
                    {
                    accessor.Read(0, out Bus_Data_1553 a);
                    Console.WriteLine(a.name);
                    Console.WriteLine(a.directions);
                    }
                }
            
        }
    }
} 

When a string is present in the structure to share, I have the following error: The specified Type must be a struct containing no references.

When I remove the string and share only the int directions, i get a value of 0. Can someone help me figure this out?

Upvotes: 3

Views: 2014

Answers (3)

AndyB
AndyB

Reputation: 101

Done some more digging on this one, and whilst under the surface its a big messy and going to be a nightmare unless all your ASCIIZ strings are the same size.

Create a struct that will be your string field. In my case, all strings are 16 bytes long and add a override for ToString():

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
internal struct String16
{
    byte b0;
    byte b1;
    byte b2;
    byte b3;
    byte b4;
    byte b5;
    byte b6;
    byte b7;
    byte b8;
    byte b9;
    byte b10;
    byte b11;
    byte b12;
    byte b13;
    byte b14;
    byte b15;

    public override string ToString()
    {
        var bytes = new byte[] { b0, b1, b2, b3, b4, b5, b6, b7, 
            b8, b9, b10, b11, b12, b13, b14, b15 };
        return Encoding.ASCII.GetString(bytes).TrimEnd('\0');
    }
}

Add the struct as a field to the struct you want to read:

[StructLayout(LayoutKind.Explicit, Pack = 1, CharSet = CharSet.Ansi)]
internal struct Header
{
    [FieldOffset(0)]
    public uint Size;

    [FieldOffset(4)]
    public String16 Name;

    [FieldOffset(0x71)]
    public String16 MutexName;
}

The memory mapped view accesor can now read this successfully and using the magic of .Net's ToString(), the following works:

    mdb.Accessor.Read<Header>(0, out var header);
    Console.WriteLine($"{header.Name}");
    Console.WriteLine($"{header.MutexName}");

It doesn't look like there's a more elegant solution as everytime you put byte[], Memory<T> or Span<T> the runtime throws.

Upvotes: 0

AndyB
AndyB

Reputation: 101

I had this same problem and I suspect that MemoryMappedViewAccessor.Read<T> blindly doesn't accept strings when a structure is passed.

Our problem is similar, legacy C/C++ code using memory mapping files. I got over this by using the following for my structure:

    [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
    unsafe internal struct MdbHeader
    {
        [FieldOffset(0)] public Int32 Size;
        [FieldOffset(4)] public fixed byte _name [16];
        [FieldOffset(0x71)] public fixed byte MutexName[16];
    }

And then create extension methods to access the fields as strings:

    internal static class MdbExtensions
    {
        unsafe public static string Name(this MdbHeader header)
            => Encoding.ASCII.GetString(header._name, 16).TrimEnd('\0');
    }

Finally to read the structure in:

            _viewAccessor.Read<MdbHeader>(0, out var header);
            var name = header.Name();

Not ideal. The alternative is to read each field in using the various Readxx methods, and create your own method to handle strings or make it an extension method to MemoryMappedViewAccessor i.e.:

        internal string GetString(long offset, int length)
        {
            var chars = new byte[length];
            _viewAccessor.ReadArray(offset, chars, 0, length);
            return Encoding.ASCII.GetString(chars).TrimEnd('\0');
        }

Upvotes: 0

Blindy
Blindy

Reputation: 67447

Let's start with what's wrong with the C++ version. I'll bold this to make sure nobody ever passes over this, it's very important: NEVER WRITE POINTERS TO DISK

std::string is a wrapper around a pointer (2 pointers actually) that handle allocation and reallocation for you as needed. You absolutely cannot write them to a "file" anywhere, you must write the contents of those pointers instead.

One simplistic way (and prevalent in C) to do this is to simply define a buffer large enough to hold your data and then use as much of it as needed:

struct Bus_1553 // this is the structure to send
{
    char name[128];
    int directions; 
};

To write to name, use strcpy_s or your OS equivalent.

Now once you write this structure in C++ to your shared file, reading it in C# is about letting the system (the marshaller) decode that soup of bytes into useful managed objects. You do this by using attributes on your structure and field definition:

    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
    public struct Bus_Data_1553
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string name;
        public int directions; // which directions used
    }

Also you don't need unsafe for this if you use the marshaller properly.

Upvotes: 1

Related Questions