读取内存映射文件在C++和C#中的实现

3

我正在尝试使用内存映射文件将来自C++的结构共享到C#,到目前为止,我已经成功地向文件中写入了数据,但是我无法在C#中读取内容。

  1. C++中的SendData
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. 尝试在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);
                    }
                }
            
        }
    }
} 

当结构中存在字符串时进行共享,我遇到了以下错误:指定的类型必须是不包含引用的结构体。
当我移除字符串并仅共享整数方向时,我得到了一个值为0。是否有人可以帮我解决这个问题?

7
简单来说,C++中的std::string和C#中的string不同。由于std::string是成员之一,所以你传递的struct不是平凡可复制类型。此外,sizeof(Bus_1553)并不像你想的那样工作。如果std::string有一千个字符,sizeof(Bus_1553)的大小不会改变,因为sizeof是编译时的值,而不是运行时的值。 - PaulMcKenzie
你试图解决的一般问题被称为“序列化”(有时也称为“编组”)。基本上,你需要设计某种通用数据格式。也许这可以通过确保Bus_1553是一个标准布局结构体来实现,然后你可以在C#端使用Marshal.PtrToStructure - Etienne de Martel
@PaulMcKenzie 如果您有答案,请将其放在答案部分。 - Asteroids With Wings
这次找对了保罗! - Asteroids With Wings
就像下面的评论所说,我改成了char name[128]和在C#中相应的封送字符串[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string name;。然而,我仍然得到了指定的类型必须是不包含引用的结构体。参数名: type的错误提示。 - angie866
1个回答

1
让我们从C++版本的问题开始。我会用粗体强调一下,以确保没有人会忽略这个,这非常重要:永远不要将指针写入磁盘 std::string是一个包装器,包装了一个指针(实际上是两个指针),可以根据需要处理分配和重新分配。您绝不能将它们写入任何“文件”中,而必须写入这些指针的内容。
一种简单的方法(在C中普遍使用)是简单地定义一个足够大以容纳数据的缓冲区,然后使用所需的部分。
struct Bus_1553 // this is the structure to send
{
    char name[128];
    int directions; 
};

使用strcpy_s或您的操作系统等效函数来写入name
现在,一旦您在C++中将此结构体写入共享文件中,在C#中读取它就是让系统(marshaller)将这些字节解码为有用的托管对象。您可以通过在结构体和字段定义上使用属性来实现这一点:
    [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
    }

此外,如果您正确使用编组程序,则不需要使用unsafe

要明确的是,“永远不要将指针写入磁盘”并不是用粗体大写字母来强调会使您的计算机爆炸或其他什么原因。它是用粗体大写字母来强调这样做是行不通的,Blindy知道这一点,他/她感到沮丧。 - user253751
当你在以后阅读并尝试取消引用它时,它会使你的程序崩溃。我不确定你想表达什么意思。 - Blindy
我认为这并不糟糕到需要那种格式。从网络读取输入永远不要使用GETS是可以的,但在这种情况下使用它似乎有些过度。 - user253751
注意,你只需将指针写入磁盘,其他人(包括原帖作者一旦修复此问题)将负责编写可工作的软件。 - Blindy
我按照你的建议尝试使用marshal,但是我仍然遇到以下错误:“指定的类型必须是不包含引用的结构体。参数名称:type”。现在,当代码中没有字符或字符串时,我可以共享数字,但是当我在C#中设置“char name [128]”和相关的marshalled字符串时,就会出现错误。我还将名称写为“strcpy_s(p_1553->name,“IFF”);”。 - angie866
你需要调用marshaller,而不是映射文件访问器的struct版本。首先获取结构体大小(Marshal.SizeOf<Bus_Data_1553>()),然后从文件中读取相同数量的字节,接着使用Marshal.PtrToStructure()获取实际结构体。因为后者需要指针,所以你需要stackalloc一个临时缓冲区,大小与字节数相同,并将字节数据复制到其中(因为访问器没有指针重载)。 - Blindy

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