5

What is the best practice to share memory of a struct from a C# program to a C++ win32 DLL?

I've used structs in managed shared memory using Boost between two C++ programs and it worked great. I'm lost on the best way to accomplish this between where the struct gets populated in the C# program and the C++ DLL that is an SNMP subagent.

Here's the C++ DLL:

//====================           Code Excerpt from the main cpp file              ====================== 
#include "stdafx.h" 
//=================           Here we are setting up the shared memory area   =====================
#pragma data_seg (".SHAREDMEMORY")
    struct sharedData {
     int sharedA;
     int sharedB;
    };
    static sharedData A;

#pragma data_seg() 
#pragma comment(linker,"/SECTION:.SHAREDMEMORY,RWS")

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) 
{ 
     return TRUE; 
} 
//=============================================================================================
//====================== Here we are writing wrappers to the shared memory area ===========================
//=You must declare it as an Extern "C" to prevent name mangling. This is absolutely necessary in order to import it into c#  =
//=============================================================================================
extern "C" __declspec(dllexport) sharedData __stdcall getMyData() 
{
    A.sharedA = 1237;
    A.sharedB = 31337;
     //return gshared_nTest; 
    return A;
} 
extern "C" __declspec(dllexport) void __stdcall setMyData( sharedData buff ) 
{ 
     A = buff; 
}

Here's the calling C# function:

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;
using System.Runtime.InteropServices;

namespace sharedMemTestCS
{
    public partial class frmSharedMemTestCS : Form
    {
    struct sharedData {
     int sharedA;
     int sharedB;
    };
    static sharedData A;

    //==============  here we are importing the methods from the win32 dll into the c# console application =================
    [DllImport(@"C:\Documents and Settings\My Documents\Visual Studio 2010\Projects\sharedMemTestCPP\Debug\sharedMemTestCPP.dll")]  
    public static extern sharedData getMyData();  
    [DllImport(@"C:\Documents and Settings\My Documents\Visual Studio 2010\Projects\sharedMemTestCPP\Debug\sharedMemTestCPP.dll")]  
    public static extern void setMyData(int data);

        public frmSharedMemTestCS()
        {
            InitializeComponent();
            //==============  here i am incrementing the value =================

            //== i use a message box so that i can have multiple console applications running at once and it will pause at the messagebox (if i don't click ok)
            //== i do this so i can see the values changing in the shared memory.
            //MessageBox.Show( getMyData().ToString() );
            getMyData();
            //txtBoxA.Text = (getMyData().ToString() );
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
            //setMyData( getMyData() + 100 );
            //txtBoxA.Text = (getMyData().ToString() );
        }
    }
}

The error message I get is:

Error 1   Inconsistent accessibility: return type

'sharedMemTestCS.frmSharedMemTestCS.sharedData' is less accessible than method 'sharedMemTestCS.frmSharedMemTestCS.getMyData()' c:\documents and settings\mconrad\my documents\visual studio 2010\Projects\sharedMemTestCS\sharedMemTestCS\Form1.cs 23 37 sharedMemTestCS

EhevuTov
  • 20,205
  • 16
  • 66
  • 71

3 Answers3

3

The best practice for sharing memory would be to use the MemoryMappedFile class in C# and CreateFileMapping/MapViewOfFile in C++.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • So, to be clear, both of those classes in their respective languages use a standard or understood protocol in interacting with such a file? I'm assuming it's an OS level standard/protocol? – EhevuTov Jul 25 '11 at 17:01
  • @Ehevu: At the OS level, it's one or more pages of memory (raw bytes) which are owned by the disk cache manager, and mapped into the address space of both processes (possibly at different addresses). The `MemoryMappedFile` class allows you to treat that block of bytes as a array of some other data structure, in C++ you can do the same thing with a cast. Just don't store any (absolute addressing) pointers inside the shared memory region. – Ben Voigt Jul 25 '11 at 17:06
  • Ah, that's very helpful, thank you. So, what is the major advantages/disadvantages of doing it the way I tested above? – EhevuTov Jul 25 '11 at 17:16
  • @Ehevu: Advantage of your original approach: very little code needed. Disadvantages: may not work with other compilers, can't share data between different DLLs, just one shared area (file mappings are named, so you can make one per user, etc.), no control, no chance to capture errors if something goes wrong. – Ben Voigt Jul 25 '11 at 17:22
  • casting raw data seems like it should have no guarantee to work. alignment and padding assumptions as well as type sizes have no reason to match between C# and C++. this would be a great way to store serialized data for sure, but direct memory representations, much less – v.oddou Jun 27 '14 at 01:01
  • @v.oddou: .NET has a concept of blitable types; these are guaranteed to have the same memory layout as corresponding native types. – Ben Voigt Jun 27 '14 at 01:23
  • @BenVoigt: this is interesting, but it cannot guarantee anything wrt C++, which standard says not enough about type sizes or memory layout. Native is just microsoft way of saying "taken outside the VM". For a glimpse about all the stuff that are undecided : http://stackoverflow.com/questions/7054176/data-alignment-in-c-standard-and-portability . More conditions are needed. like "you can share fundamental types after you have checked that your compiler excpects the same size than C#" or "you can share blittables provided they are also C++ aggregates, and packing and padding is determined" etc. – v.oddou Jun 27 '14 at 02:24
2

First thing, you cannot straightway use Boost for data sharing. You need to have some well-defined data-structures that you share between the managed and unmanaged worlds.

You may start here

Ajay
  • 18,086
  • 12
  • 59
  • 105
  • How do you share between the managed and unmanaged worlds? In the context of my question, I don't understand your response. – EhevuTov Jul 24 '11 at 21:00
  • Well, Boost library is strictly C++ only - it it complicated enough to use it between different C++ projects. First start with exporting functions/structs from native C++, import and utilize in C# program. In short, if a C++ struct of size N bytes can be successfully imported by C# program, and you achieve "data-sharing" on top of this, you can move ahead. Please search for "C++, C#, CLI" etc to get started. – Ajay Jul 25 '11 at 02:10
  • Yeah, I know Boost is C++, but one thing I don't know is if I could insert it into C# (I know there's a PInvoke, but not sure if it could be applied here). Also, I don't think you can use user-defined datatypes such as structs in shared mem easily (this is why i was thinking to use Boost::Interprocess)? I'm just trying to share a simple user-defined struct from a C# program with my C++ DLL that will use the struct (actually, an SNMP extension). – EhevuTov Jul 25 '11 at 14:40
  • I don't see any Boost here anyway. – Ben Voigt Jul 25 '11 at 16:42
  • I was using Boost::Interprocess in a C++ to C++ mem share for a proof-of-concept, but then learned I'll need to do C# to C++ – EhevuTov Jul 25 '11 at 18:28
2

Well your actual problem is your p/invoke expression is public but your struct is private, which is what the error is telling you. Making your p/invoke expression private or your struct public will resolve the immediate issue.

As for the actual data sharing, I've never tried doing it quite like that so I can't tell you if it will or won't work. All the pieces I've worked with are marshalled back and forth. Looking at your example code, it's quite possible it could work. You'll probably want to either copy the data to a new struct for c# or pin your struct that you get back so the GC won't move it around in memory.

Joshua
  • 8,112
  • 3
  • 35
  • 40
  • The struct should work this time, but unintentionally. When trying to pass structs back and forth within P/Invoke, you need to pay close attention to the layout and packing .Net uses for your structs. The StructLayout attribute (http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.structlayoutattribute.aspx) lets you specify how your structure is laid out in memory so you can explicitly match what your non-managed code is expecting to see. – The Moof Jul 25 '11 at 16:48
  • Ah, I got it working thank you! I didn't know the struct defaulted to private. I typed in "public" everywhere inside the C# struct declaration and it worked! Now, is this the best practice though? – EhevuTov Jul 25 '11 at 17:02