Arvo Bowen
Arvo Bowen

Reputation: 4939

Getting the size of a structure meant for memory mapping

The Question In a Nutshell:

How can I get the size of a user defined structure passed as an object to a class library?

Overview:

The point of this test project is to build a class library that wraps up memory sharing using memory mapping within .Net 4 (Or less). In the end I want to be able to define a structure in my main application, pass it to the class library wrapper and let the class library determine the size of the structure.

Definitions:

  1. MyAppA: Main application, the one that will create the memory map instance initially.
  2. MyAppB: Second application used to communicate with MyAppA. This will tap into the existing memory map.
  3. MemoryMapTool: This will be the class library that wraps up all the memory sharing.
  4. TestStruct: This will be the structure defined in MyAppA and MyAppB that will be the exact same in both applications but might change from time to time. MemoryMapTool would NOT know the structure layout at any time, it will simply see it as an object.

Initial Idea:

I want the class library wrapper to have no knowledge of the TestStruct generated by MyAppA other than it is an object that the class library wrapper needs to keep up with and use for memory sharing...

I was thinking I would create the TestStruct in MyAppA and add as many variables to it as I needed (in this case just 1, a string). Then pass it into the MemoryMapTool constructor and let the MemoryMapTool class determine the size of the struct. This is currently the issue. Working with memory I tend to be cautious and research before I just try something that will possibly fail killing my IDE or OS... ;)

I was originally going to just pass the TestStruct straight to the MemoryMapTool constructor but ran into this issue...

long lMapSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(oData));

Error: The type or namespace name 'oData' could not be found (are you missing a using directive or an assembly reference?)

I then was thinking of trying to just use...

long lMapSize = System.Runtime.InteropServices.Marshal.SizeOf(oData);

... and it seems to work (at least the IDE likes it). But for some reason I don't feel like that's the correct way to do it.

UPDATE: After trying that I get a new error...

Error: Type 'MyAppA.Form1+TestStruct' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed.


Current Source

MemoryMapTool.cs Contents

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.MemoryMappedFiles;
using System.Threading;

namespace SharedMemoryWorker
{
    public class MemoryMapTool : IDisposable
    {
        #region Private class variables
        private string m_sLastError = "";
        private MemoryMappedFile mmf = null;
        private string m_sMapName = "";
        private object m_oData = null;
        #endregion

        #region Public properties
        public string MapName
        {
            get
            {
                return m_sMapName;
            }
            set
            {
                m_sMapName = value;
            }
        }

        public object Data
        {
            get
            {
                return m_oData;
            }
            set
            {
                m_oData = value;
            }
        }
        #endregion

        #region Constructor
        private MemoryMapTool(string sMapName, object oData)
        {
            long lMapSize = System.Runtime.InteropServices.Marshal.SizeOf(oData);

            try
            {
                //Save the map name
                m_sMapName = sMapName;

                //Create new map or use an existing one
                //mmf = MemoryMappedFile.CreateOrOpen(m_sMapName, lMapSize);


            }
            catch (Exception ex)
            {
                m_sLastError = ex.Message;
                throw new NullReferenceException("Error creating new object!");
            }
        }

        public void Dispose()
        {
            //Deconstructor
        }
        #endregion

        #region Public class methods
        public string GetLastError()
        {
            return m_sLastError;
        }
        #endregion

    }
}

MyAppA, Form1.cs Contents

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MyAppA
{
    public partial class Form1 : Form
    {
        #region Public structures
        public class TestStruct
        {
            #region Private class variables
            private string m_sTest = null;
            #endregion

            #region Public properties
            public string Test
            {
                get
                {
                    return m_sTest;
                }
                set
                {
                    m_sTest = value;
                }
            }
            #endregion
        }
        #endregion

        public Form1()
        {
            InitializeComponent();
        }
    }
}

MyAppB, Form1.cs Contents

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MyAppB
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

Upvotes: 2

Views: 475

Answers (1)

Marcel N.
Marcel N.

Reputation: 13976

I think the error is pretty clear. You need to tell the CLR how you want the structure or class members to be laid out in memory. See the StructLayoutAttribute - it needs to be applied explicitly to classes (whereas for structs Sequential is the default).

From your description it appears that you want to do IPC between two or more managed processes. You may also want to establish a unified marshaling strategy for strings (see the MarshalAsAttribute). You can choose one and stick with it throughout your classes.

Finally, I would like to say that this isn't really appropriate for what you're trying to do (too much overhead and room for error). Instead you could:

  1. Still use MMFs but serialize your classes using binary serialization or even JSON.
  2. Design a service oriented architecture based on either WCF or WebAPI (can be selfhosted now, via OWIN/Katana).
  3. Ultimately you can use raw TCP/IP sockets as well and design a small protocol for your apps.

My choice would be #2. Performance can be very good, especially with WCF and on the same computer with named pipes or net.tcp bindings and it just works.

Upvotes: 1

Related Questions